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

20172301 《程式設計與資料結構》第八週學習總結

20172301 《程式設計與資料結構》第八週學習總結

教材學習內容總結

  • 堆:是具有兩個附加屬性的一棵二叉樹。
    • 是一棵完全樹。
    • 最小堆對每一結點,它小於或等於其左孩子和右孩子。反之,最大堆對每一個結點大於或等於它的左右孩子。
    • ,最小堆將其最小元素儲存在該二叉樹的根處,且最小堆根結點的子樹同樣也是最小堆。
  • addElement操作:將給定的元素新增到堆中的恰當位置,維持該堆的完全性屬性和有序屬性
    • 如果元素不是Comparable型別的,則會丟擲異常。這是為了讓元素可比較,可以維持堆的有序屬性。
    • 而為了維護堆的完全性,就決定了堆插入結點時只有兩種情況:

      1.h層的左邊下一個位置。
      2.h+1層的第一個位置。(h層為滿)

    • 將新元素新增到堆的末尾後,考慮到有序屬性,將該元素與父結點進行比較,若小將它與父結點對換,直到大於父結點或者位於根結點處。
  • removeMin操作:刪除堆的最小元素:刪除堆的最小元素並且返回。
    • 最小元素位於根結點,刪除掉根結點,為了維持樹的完全性,要找一個元素來替代它,那麼只有一個能替換根的合法元素,且它是儲存在樹中最末一片葉子上的元素。最末的葉子是h層上最右邊的葉子。
    • 考慮到有序屬性,對該堆重新排序。將新的根元素和其較小的孩子比較,如果孩子更小,那麼他們互換,直到該元素位於某個葉子中或者比他的兩個孩子都小。
  • findMin操作:指向最小堆的最小元素。
    • 返回存在根處的元素。

用連結串列實現堆

  • 因為要求插入元素以後能夠向上遍歷,所以堆中的結點必須儲存指向雙親的指標。繼承BinaryTreeNode類,並且新增雙親指標和對應的set方法。
  • 有一個例項資料lastNode作用是跟蹤記錄堆中的最後一個葉子,也就是指向末結點的引用。

  • addElement操作:
    • 在適當位置新增一個元素。
    • 對堆進行重排序,以保持其有序屬性。
    • lastNode指標重新設定為指向新的最末結點。
    • 在最壞的情況下,確定要插入結點的雙親,需要從堆的右下結點往上遍歷到根,然後往下遍歷到堆的左下結點。時間複雜度為2 * logn。插入新結點,簡單賦值,時間複雜度為O(1)。如果需要重排序,最多需要比較logn次,因為路徑最長為logn。所以addElement
      操作的複雜度
      為2 * logn + 1 + logn,為O(logn)
  • removeMin操作:
    • 用儲存在最末結點處的元素替換儲存在根處的元素。
    • 對堆進行重排序。
    • 返回原來的根元素。
    • 在最壞的情況下,替換結點,簡單賦值,時間複雜度為O(1),然後重排序,,因為路徑最長為logn,所以還是比較logn次。確定新的最末結點,從葉子到根的遍歷,再從根到另一個葉子的遍歷。所以removeMin操作的複雜度為2 * logn + logn + 1,為O(logn)
  • findMin操作
    • 直接返回根元素,複雜度為O(1)

用陣列實現堆

  • 樹的根位於位置0處,對於每一結點n,n的左孩子將位於陣列的2n+1位置處,n的右孩子將位於陣列的2(n+1)位置處。
  • addElement操作:
    • 在恰當位置處新增新結點。
    • 對堆進行重排序以維持其排序屬性。
    • 將count值遞增1。
    • 時間複雜度為 1 + log ,為 O(logn)。
  • removeMin操作
    • 用儲存在最末元素處的元素替換儲存在根處的元素。
    • 對堆進行重排序。
    • 返回初始的根元素,並將count值減1。
    • 時間複雜度為 1 + log ,為 O(logn)。
  • findMin操作
    • 指向索引為0,時間複雜度為O(1)

使用堆:優先順序佇列

  • 遵循兩個排序規則:
    • 具有更高優先順序的專案在先。
    • 具有相同優先順序的專案使用先進先出方法來確定順序。
  • 雖然最小堆根本就不是一個佇列,但是它卻提供了一個高效的優先順序佇列實現。

使用堆:堆排序

  • 原理:根據堆的有序屬性,將列表的每一個元素新增到堆中,然後一次一個的把他們從根中刪除。
  • 堆排序是一種選擇排序,整體主要由構建初始堆+交換堆頂元素和末尾元素並重建堆兩部分組成。其中構建初始堆經推導複雜度為O(n),在交換並重建堆的過程中,需交換n-1次,而重建堆的過程中,根據完全二叉樹的性質,[log2(n-1),log2(n-2)...1]逐步遞減,近似為nlogn。所以堆排序時間複雜度一般認為就是O(nlogn)。
  • 堆排序時間複雜度O(nlogn)。
  • 步驟:
    • 步驟一:構造初始堆。將給定無序序列構造成一個堆(升序採用小頂堆,降序採用大頂堆)。
    • 步驟二:將堆頂元素與末尾元素進行交換,使末尾元素最大。然後繼續調整堆,再將堆頂元素與末尾元素交換,得到第二大元素。如此反覆進行交換、重建、交換。

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

  • 問題1:連結串列堆和陣列堆的優缺點。
  • 問題1解決方案:
    • 在之前章節的樹的實現中,我們大多數用的都是連結串列實現的。主要是因為,用陣列實現樹的時候,可能會因為沒有左右孩子而浪費了大量的空間。但是,在堆的實現中,因為考慮其向上遍歷的特殊操作,我們需要其雙親結點 。而又因為堆是一個完全樹,所以,不會存在大量浪費空間的情況 。所以,針對堆來說,陣列實現的效率更高。
    • 根據書P268 和 P270,

      因為陣列不需要確定新結點雙親的步驟,以及陣列不需要確定新的最末結點。所以,雖然他的時間複雜度和用連結串列實現時是一樣的,但是陣列實現的效率更高一些。

    • 同樣,我們不必拘泥於一種結構,一種實現方式。平常編寫程式碼時也要注意其效率,程式碼的相關優化和美觀。不要只把實現和完成任務當成標準。這是我以後也應該注意的。
  • 問題2:對於課上將的堆排序,還有課上的堆排序實踐沒有熟練掌握。重點歸納記憶一下。
  • 問題2解決方案:
    • 首先,要清楚堆排序的思想,堆排序是一種選擇排序 。如何將一個雜亂排序的堆重新構造成最大堆,它的主要思路就是

      從上往下,將父節點與子節點以此比較。如果父節點最大則進行下一步迴圈,如果子節點更大,則將子節點與父節點位置互換,並進行下一步迴圈。注意父節點要與兩個子節點都進行比較。

    • 我們第一步就應該明白,如何將一個無序列表構建成最大堆。

      從最後一個非葉節點開始調整。

    • 如圖,這裡的最後一個非葉子結點是結點4,那麼我們就從這裡進行調整。

      每次調整都是從父節點、左孩子節點、右孩子節點三者中選擇最大者跟父節點進行交換(交換之後可能造成被交換的孩子節點不滿足堆的性質,因此每次交換之後要重新對被交換的孩子節點進行調整)。

    • 如上圖,這裡從結點2開始做調整。左孩子為結點4,右孩子為結點5,將其與父結點做比較,發現左孩子比父結點更大。因此將它們做交換,設結點4為最大的結點,並繼續以結點4開始做下一步運算。
    • 當構建好一個堆之後,我們開始進行排序。將堆頂元素與末尾元素進行交換,使末尾元素最大。然後重新調整堆,一直到最後整個堆都不存在了,那麼陣列就是有序的。

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

  • 問題1:在實現PP12.1的時候,發現使用remove操作之後,雖然刪除了第一個進入的元素,但是又添加了一個元素。

如圖,發現,我插入佇列的依次是,2,10,3。然後中序遍歷就是10,2,3。這是沒有問題的,但是刪除之後,雖然2刪除掉了,但是多了一個3。

  • 問題1解決方案:
    • 這個問題可能是因為我dequeue()方法,呼叫了之前父類ArrayHeap類中removeMin()方法所引起的。
    • 所以就不得不說一下我PP12.1的實現思路。實際上,佇列和優先順序佇列一定程度上是一樣的。只是CompareTo方法的判定條件不同。佇列只需要將進入堆的順序記錄下來就可以然後比較即可。那麼這裡的呼叫的removeMin()方法實際上就是刪除佇列中順序最低的,也就是第一個進來的元素。
    public T removeMin() throws EmptyCollectionException 
    {
        if (isEmpty())
            throw new EmptyCollectionException("ArrayHeap");
    
        T minElement = tree[0];
        tree[0] = tree[count-1];
        heapifyRemove();
        count--;
    modCount--;
        return minElement;
    }
    • 程式碼如上所示,可以發現,在把陣列的最後一個賦給第一個,重排序之後呢,並沒有對其進行清空操作。所以,存在會有兩個3的出現。那麼我只需要在之後新增一串
    tree[count] =null;
    就可以解決問題了。

程式碼託管

上週考試錯題總結

  • 在二叉查詢樹刪除結點時,如果他有孩子,那麼讓他的孩子代替他。應該是升級而不是降級。

結對及互評

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

  • 上週部落格互評情況
    • 20172304
    • 段志軒同學的部落格是不斷進步的,還是應該多多基於書本,務實基礎。在程式碼實現方面不要急躁,耐心除錯,肯定可以發現錯誤。
    • 20172328
    • 部落格內容豐富,並且問題裡討論了上學期研究的棧記憶體和堆記憶體之間的區別。優秀。

其他

堆是基於二叉樹的。這周看程式碼的效率並不是很高,對於程式碼的理解也不是很深入。歸結於學習積極性不高。凡事不要鑽牛角尖,過去的就過去了,一切都會好起來。不要給自己額外的壓力和負擔。
看過別的同學優秀的部落格以後發現自己不能懈怠。比如很多同學都聯絡到了上學期的堆疊記憶體時的學習內容。學習應該有體系,不能夠東拼西揍,撿西瓜丟芝麻。還是要踏實下來,戒驕戒躁。

學習進度條

程式碼行數(新增/累積) 部落格量(新增/累積) 學習時間(新增/累積) 重要成長
目標 5000行 30篇 400小時
第一週 0/0 1/1 10/10
第二週 610/610 1/2 20/30
第三週 593/1230 1/3 18/48
第四周 2011/3241 2/5 30/78
第五週 956/4197 1/6 22/100
第六週 2294/6491 2/8 20/120
第七週 914/7405 1/9 20/140
第八週 2366/9771 2/11 22/162

參考資料