1. 程式人生 > >20172313 2018-2019-1 《程式設計與資料結構》第八週學習總結

20172313 2018-2019-1 《程式設計與資料結構》第八週學習總結

20172313 2018-2019-1 《程式設計與資料結構》第八週學習總結

教材學習內容總結

    • 堆(heap)就是具有兩個附加屬性的一棵二叉樹。特點:①最小堆是一棵完全樹。②最小堆對每一結點,它小於或等於其左孩子和右孩子。(最大堆的結點大於或等於它的左右孩子)
    • HeapADT的UML描述。
操作 說明
addElement 將給定元素新增到該堆中 
removeMin 刪除堆的最小元素 
findMin 返回一個指向堆中最小元素的引用 
  • 最小堆結點的子樹同樣是最小堆,兩個不同的最小堆可能包含資料相同
  • addElement操作:將給定的元素新增到堆中的恰當位置,由堆的定義可知,這裡的元素要能夠進行比較,所以必須是Comparable型別的,如果不是,則會丟擲異常。如果一棵二叉樹是平衡的,即所有葉子都位於h或h-1層。其中h為log2^n,且n是樹中的元素數目,因為堆是一棵完全樹,所以h層的葉子都位於該樹的左邊。所以插入結點時只有兩種情況:①h層的最後一個位置。②h+1層的第一個位置。將新元素新增至堆的末尾後,保持樹是完全樹,將該元素向根的地方移動,將它與父結點對換,直到其中的元素大小關係滿足要求為止。
    • 在連結串列實現中,新增元素時首先要確定插入結點的雙親。最壞的一種情況是從右下的最後一個葉子節點一直遍歷到根,在遍歷到堆的左下結點 。該過程的時間複雜度為2logn。下一步是插入節點(簡單的賦值,這裡的時間複雜度為O(1))。最後一步是將這棵樹進行重新排序。因為從根到結點的路徑長度為logn,所以最多需要進行logn此操作。因此使用連結串列實現時操作的複雜度為2*logn+1+logn。即O(logn)
    • 在陣列實現中,新增元素時並不需要確定新結點雙親的步驟,但是,其他兩個步驟與連結串列實現的一樣。因此,陣列實現的addElement操作的時間複雜度為1+logn或O(logn)。雖然這兩者實現的複雜度相同,但陣列實現的效率更高一些。

  • removeMin操作:根據堆的定義可知,堆中的最小元素位於根結點,這時我們要把該樹儲存在最末一片葉子中的元素移到根結點處,一旦元素移動,就要對該樹進行重新排序:將該新根元素與較小的那個孩子進行比較,如果孩子更小則將它們互換。從上至下繼續這一過程,直到該元素要麼變成葉子結點,要麼比它的兩個孩子都小。
    • 在連結串列實現中,removeMin必須刪除根元素,並用最後一個結點的元素來替換它(簡單的賦值,時間複雜度為O(1))。下面要對該樹進行重新排序,因為該樹原先是一個堆,所以只需要跟較小的一邊進行比較排序。因為從根到葉子的最大路徑長度為logn,因此該步驟的時間複雜度為O(logn)。到此時,這棵樹已經完成了,但在實際進行的過程中,為了繼續完成接下來的操作,我們還要找到新的最末結點,最壞的情況是進行叢葉子到根的遍歷,然後再從根往下到另一葉子的遍歷。因此,該步驟的時間複雜度為2logn。於是removeMin操作最後的時間複雜度為2
      logn+logn+1,即O(logn)。
    • 在陣列實現中,removeMin也像連結串列實現的那樣,只不過它不需要確定最新的最末結點。因此,陣列實現的removeMin操作的複雜度為logn+1。即O(logn)。
  • findMin操作:findMin操作較為簡單,由堆的定義可知,直接返回根結點的元素即可。
  • 堆和二叉排序樹的區別:
    • ①堆是一棵完全二叉樹,二叉排序樹不一定是完全二叉樹;
    • ②在二叉排序樹中,某結點的右孩子結點的值一定大於該結點的左孩子結點的值,在堆中卻不一定;
    • ③在二叉排序樹中,最小值結點是最左下結點,最大值結點是最右下結點。在堆中卻不一定;
  • 使用堆:優先順序佇列:按照優先順序從大到小進行排序,具有相同優先順序的按照先進先出來進行排序。雖然最小堆根本就不是一個佇列,但是它卻提供了一個高效的優先順序佇列實現。
操作 說明
addElement 往樹中新增一個元素 
removeElement 從樹中刪除一個元素 
removeAllOccurrences 從樹中刪除所指定元素的任何存在 
removeMin 刪除樹中的最小元素 
removeMax 刪除樹中的最大元素 
findMin 返回一個指向樹中最小元素的引用 
findMax 返回一個指向樹中最大元素的引用 
  • 用連結串列實現二叉查詢樹:每個BinaryTreeNode物件要維護一個指向結點所儲存元素的引用,另外還要維護指向結點的每個孩子的引用。

教材學習中的問題和解決過程

  • 問題一:在使用陣列實現最小堆時,如果要進行removeMin操作需要把根元素刪去,把“count-1”處的元素放到索引為0的地方,那麼“count-1”處的元素不是還放在原位嗎?書上並沒有對該位置進行任何操作。
  • 問題一解決方案:當時看到這裡的時候,我認為是刪除元素時就不對“count-1”處的元素進行操作,因為刪除元素後count變數會自減,到時候遍歷的時候只遍歷到count的位置,這個問題也就先放到這裡了。但是當我做到pp12_8時卻發現不是這樣。當我刪除了最小元素後,在遍歷的時候依舊會把最後一個元素列印。如下圖所示

      我們可以很清楚的看到,5作為最後一個元素並沒有被刪除,仍然會被列印,由於ArrayHeap繼承的是ARRayBinaryTree類,我又查看了ArrayBinaryTree中的遍歷方法。
public String toString() 
    {
        ArrayUnorderedList<T> tempList = new ArrayUnorderedList<T>();
        inOrder(0, tempList);

        return tempList.toString();
    }

  使用陣列實現堆時使用中序遍歷,列印陣列中不為空的所有元素,所以在這裡我覺得書上的內容是有問題的,在ArrayHeap的removeMin()方法中應該新增吧“count-1”處的元素清空的操作。這樣問題就得以解決了。

 public T removeMin() throws EmptyCollectionException
    {
        if (isEmpty())
            throw new EmptyCollectionException("ArrayHeap");

        T minElement = tree[0];
        tree[0] = tree[count-1];
        tree[count-1] = null;(新新增的操作)
        heapifyRemove();
        count--;
        modCount--;
        
        return minElement;
    }

  • 問題二:在看連結串列實現堆時,有這樣一段話“heapifyAdd方法並沒有執行雙親與孩子的完整互換。它只是把雙親元素往下平移到正確的插入點,然後把新值賦給該物質。這並沒有真正改變演算法的複雜度,即使執行完整的元素互換,其複雜度也是O(logn)。但是,它的確提高了效率,因為它減少了左堆的每一層上要執行的賦值次數。”我對於這段話不是很理解。
  • 問題一解決方案:我是這樣理解的,先用temp儲存插入節點的元素,與父結點內儲存的元素進行比較,如果父結點的元素大於temp,則把父結點的元素賦給子結點。注意:此時並沒有把temp的值賦給父結點,沒有執行完整的元素互換。這時繼續進行遍歷,找到父結點的父結點,讓父結點的父結點的元素與temp進行比較,重複以上步驟,直到根結點或有一結點的元素小於temp,這時,該結點的子結點就是新插入元素的位置,即把temp賦給它。由於沒有進行完成的元素互換,效率自然也就提高了。

程式碼除錯中的問題和解決過程

  • 問題1:對使用陣列實現堆中的removeMin操作的heapifyRemove方法不是特別理解。程式碼如下:
private void heapifyRemove()
    {
        T temp;
        int node = 0;
        int left = 1;
        int right = 2;
        int next;
        
        if ((tree[left] == null) && (tree[right] == null))
            next = count;
        else if (tree[right] == null)
            next = left;
        else if (((Comparable)tree[left]).compareTo(tree[right]) < 0)
            next = left;
        else
            next = right;
        temp = tree[node];

        while ((next < count) && 
            (((Comparable)tree[next]).compareTo(temp) < 0))
        {
            tree[node] = tree[next];
            node = next;
            left = 2 * node + 1;
            right = 2 * (node + 1);
            if ((tree[left] == null) && (tree[right] == null))
                next = count;
            else if (tree[right] == null)
                next = left;
            else if (((Comparable)tree[left]).compareTo(tree[right]) < 0)
                next = left;
            else
                next = right;
        }
        tree[node] = temp;
    }
  • 問題一解決方案:我們先來一行一行的逐步分析程式碼,我們知道此方法是用來對堆進行重新排序的。首先,判斷刪除最小元素後是否只剩下一個結點,如果是,則跳過後面判斷和迴圈,將剩下的唯一元素設定為根結點。有前面的內容可知,對於每一結點,n的右孩子將位於素組的2(n+1)處,n的右孩子將位於陣列的2(n+1)處。如果對於根結點來說,它沒有右孩子或左孩子的元素小於右孩子的元素,則令next=left進入下一步的迴圈,如果它的右孩子的元素大於左孩子的元素。則令next=right進入下一步的迴圈。(注意:在判斷結束後,temp= tree[node]的作用為儲存此時根節點的元素向下遍歷進行比較,最後賦值給通過迴圈找到的結點。)當進入迴圈以後,將根結點的元素與索引為next處的元素(左孩子或右孩子)進行比較,如果next處的元素比temp小,則令node(next的父結點)處元素為next。繼續向下遍歷,把next的值賦給node,並找到node的孩子的索引,令next重新指向node的孩子。當跳出迴圈時,node結點的位置即為temp的位置,完成排序。

程式碼託管

上週考試錯題總結

  • 錯題1:What type does "compareTo" return?
    A . int
    B . String
    C . boolean
    D . char
  • 解析:這題錯的實在是不應該,當時做的太快了,滿腦子都想著compareTo用來比較兩個物件之間的大小,返回一個boolean型的變數,卻忘了是根據返回int值的大小來判斷的。
  • 錯題2:Insertion sort is an algorithm that sorts a list of values by repetitively putting a particular value into its final, sorted, position.
    A . true
    B . false
  • 解析:概念題,插入排序通過反覆地把某個元素插入到之前已排序的子列表中,實現元素的排序。
  • 錯題3:A binary search tree is a binary tree with the added property that the left child is greater than the parent, which is less than or equal to the right child.
    A . True
    B . Flase
  • 解析:概念題,做的時候理解錯誤,二叉查詢樹是一種含有附加屬性的二叉樹,即其左孩子小於父結點,而父結點又小於或等於右孩子。
  • 錯題4:One of the uses of trees is to provide simpler implementations of other collections.
    A . True
    B . Flase
  • 解析:這道題做的時候沒有理解清楚題意。樹的主要作用之意2是為其他集合提供高效的實現,而不是簡單的實現。

結對及互評

  • 部落格中值得學習的或問題:
    • 排版精美,對教材的總結細緻,善於發現問題,對於問題研究得很細緻,解答也很周全。
  • 程式碼中值得學習的或問題:
    • 程式碼寫的很規範,思路很清晰,繼續加油!

點評過的同學部落格和程式碼

  • 本週結對學習情況

其他(感悟、思考等,可選)

  相較與上週的折磨,這周的內容上可以說是友好了許多,經歷了上週二插查詢樹的洗禮,這周的內容也只不過是新瓶裝舊酒而已,沒有什麼太過新鮮的東西,主要是把一些關鍵的程式碼給弄明白,這一過程還是花了不少時間的。由於這周的實驗也佔用了不少時間,這周的學習內容其實並沒有想象中那麼輕鬆。希望自己能在以後的生活中繼續努力,不斷進步!

學習進度條

程式碼行數(新增/累積) 部落格量(新增/累積) 學習時間(新增/累積) 重要成長
第一週 200/200 1/1 5/20
第二週 981/1181 1/2 15/20
第三週 1694/2875 1/3 15/35
第四周 3129/6004 1/4 15/50
第五週 1294/7298 1/5 15/65
第六週 1426/8724 1/6 20/85
第七週 2071/10795 1/7 20/105
第八週 3393/14188 1/8 20/125
  • 計劃學習時間:20小時

  • 實際學習時間:20小時

參考資料