20172303 2018-2019-1《程序設計與數據結構》第8周學習總結

20172303 2018-2019-1《程序設計與數據結構》第8周學習總結

教材學習內容總結

本週的內容又是一次延續上一週學習內容掌握新知識的過程,本週學習了一種特殊形式的樹——堆,學習了兩種實現堆的方法:用鏈表實現和用數組實現,同時還學習了使用堆來實現一種特殊隊列——優先隊列以及基於堆實現的另外一種排序方法:堆排序。html

1、堆的概述

  • 概念:堆是一種具備兩種附加屬性的特殊二叉樹。
    • 附加屬性一:堆是一顆徹底樹(複習:徹底樹:底層葉子都位於樹的左邊的平衡樹稱爲徹底樹)
    • 附加屬性二:對於堆中的每個結點,該結點都小於或等於(大於或等於)它的左右孩子。
  • 類型:
    • 最小堆(小頂堆)——堆中每個結點都小於等於其左右孩子的堆
    • 最大堆(大頂堆)——堆中每個結點都大於等於其左右孩子的堆
  • 特色:
    1. 堆的最小值/最大值存儲在根處
    2. 堆的每一顆子樹也是一個堆

2、堆的操做

(一)堆的構造(以構造大頂堆爲例)

  • 第一步:將元素按照層序遍歷的順序構造一顆二叉樹
  • 第二步:從樹中的第一個非葉子結點/非終端結點(尋找方法:樹中的第[樹中元素個數/2]個元素)開始調整,判斷該結點與其孩子的大小,若是不知足堆的附加屬性二則進行交換。如在下圖中,第一個非葉子結點爲6,它和它的右孩子9與堆的附加屬性二衝突,因此將二者進行交換。
  • 第三步:向上繼續尋找下一個非葉子結點直至根結點,重複上一步操做,若是在交換以後存在新的不平衡,那麼針對與孩子交換以後的非葉子結點,與其新的左右孩子再次進行對比和交換。如在下圖中,第二個非葉子結點爲4,它比它的兩個孩子都小,因此與其中較大的9進行交換,而在交換以後,4與它新的左孩子5和右孩子6進行對比,仍然與堆的附加屬性二衝突,因此再次對46進行交換操做。

(二)添加操做(以小頂堆中的插入爲例)

  • 第一步——插入
    • 對於插入堆中的元素來講,插入時只會有一個正確的插入位置,而這個插入位置有兩種可能,假設堆的層數爲h,插入元素的位置要麼在第h層的下一個靠左的空位置,要麼在第h+1層的第一個位置。
  • 第二步——調整
    • 在插入以後,將插入的元素與其父結點進行對比,若是不知足堆的附加屬性二,則將其元素與其父結點進行對比,在必要時進行交換,直至該元素與其父結點知足附加屬性二或位於該堆的根處。

(三)刪除操做(以小頂堆中的刪除爲例)

  • 堆中的刪除操做是針對堆的根結點來進行的。
  • 第一步——刪除
    • 首先將堆的根結點刪除,而後將堆中原來的最後一個元素替換到根結點處。
  • 第二步——調整
    • 從根結點處向下,將根結點與其左右孩子進行對比,若是不符合堆的附加屬性二,則繼續交換,交換以後繼續與新的左右孩子進行比較交換,直至該元素與其左右孩子知足附加屬性二或位於該堆的葉子處。

3、堆的實現

(一)用鏈表實現堆

  • 由於咱們要求在插入元素後可以向上遍歷該樹,因此堆中結點必須存在指向其雙親的指針。除此以外,爲了可以追蹤堆中的最末一片葉子,還要設置一個lastNode來存儲最末結點。
  • addElement
    • 該操做有三個步驟:
      • 添加新的元素到末尾,在該步驟中要肯定插入結點的雙親,在最壞的狀況下,它可能將整個堆遍歷,這個過程的時間複雜度爲O(logn)。
      • 對堆進行從新調整,在調整過程當中最多要進行logn次比較,及從下至上遍歷每一層(若是堆中有n個元素,則其高度爲logn),因此其時間複雜度爲O(logn)。
      • lastNode指針再次指向最末的結點,該步的時間複雜度爲O(1)。
    • 因此添加操做的時間複雜度爲O(logn)。
    • 在這個方法中應用了兩個私有方法getNextParentAdd(用於返回插入結點如今的父結點)和heapifyAdd(從該結點開始,對剩餘堆進行從新調整直至根處)。
  • removeMin
    • 該操做有三個步驟:
      • 用堆中的最末結點替換根結點,該步的時間複雜度爲O(1)。
      • 在必要的狀況下,對堆進行從新排序。該步操做與添加中的第二步相似,所以其時間複雜度爲O(logn)。
      • 肯定新的最末結點,在最壞的狀況下,咱們須要遍歷整個堆才能找到最末結點,此時它的時間複雜度爲O(logn)。
    • 因此刪除操做的時間複雜度爲O(logn)。
    • 在這個方法中一樣應用了兩個私有方法getNewLastNode(用於返回最末結點的引用)和heapifyRemove(從根結點開始,對下面的堆進行從新調整直至葉子結點)。

(二)用數組實現堆

  • 在用數組創建的堆中,根位於數組的0處,對於每個結點n,n的左孩子位於數組的2n+1處,右孩子位於2(n+1)處。
  • addElement
    • 該操做有三個步驟:
      • 在適當位置處添加新結點,在這一步中,與鏈表實現不一樣,它不須要肯定雙親的位置(由於對於結點n,其雙親的位置是固定的,位於數組的(n-1)/2處),因此時間複雜度爲O(1)。
      • 對於堆進行從新調整,該步的時間複雜度爲O(logn)。
      • 將count增長1,該步的時間複雜度爲O(1)。
    • 因此刪除操做的時間複雜度爲O(logn)。
    • 在這個方法中應用了一個私有方法heapifyAdd(在必要時,從該結點開始,對剩餘堆進行從新調整直至根處)。
  • removeMin
    • 該操做有三個步驟:
      • 用堆中的最末結點替換根結點,該步的時間複雜度爲O(1)。
      • 在必要的狀況下,對堆進行從新排序。這一步與其餘方法中的調整排序相似,所以其時間複雜度爲O(logn)。
      • 返回初始的根元素,在數組中,根元素存儲在數組的0處,因此這一步的時間複雜度爲O(1)。
    • 因此刪除操做的時間複雜度爲O(logn)。
    • 在這個方法中應用了一個私有方法heapifyRemove(在必要時,對下面的堆進行從新調整直至葉子結點)。

4、堆的應用

(一)使用堆:優先級隊列

  • 雖然最小堆不是一個隊列,可是它卻提供了一個高效的優先級隊列實現
  • 優先級隊列:遵循兩個排序規則的集合。
    • 具備更高優先級的元素在先。
    • 具備相同優先級的項目使用先進先出方法來肯定其排序。
  • 實現方法:實現方法:定義結點類保存隊列中的元素、優先級和排列次序。而後,經過實現Comparable接口定義compareTo方法,先比較優先級,再比較排列次序。

(二)堆排序

  • 堆排序由兩部分構成:添加列表中的每一個元素,而後一次刪除一個元素。
  • 時間複雜度分析:在插入操做中,每個元素的插入操做(即便用addElement方法)的時間複雜度爲O(logn),所以n個結點的時間複雜度爲O(nlogn)。在刪除操做中,每個元素的刪除操做(即便用removeMin方法)的時間複雜度也爲O(logn),所以刪除n個結點的時間複雜度爲O(nlogn)。因此堆排序的時間複雜度爲O(nlogn)。
  • 堆排序的過程
    • 第一步:將根結點與最末結點交換,將新的最末結點輸出,對剩餘的樹進行調整使之從新成爲堆。如在下圖中,將根結點9與最末結點4進行交換,而後將9輸出;對於剩下的部分,重新的根結點開始,4比它的左右孩子都小,所以與兩者之間較大的8進行交換,交換後4變爲葉子結點,開始進行下一步操做。

    • 第二步:重複第一步的操做直至輸出全部元素。

  • 規律:使用大頂堆排序後生成的是升序排列,使用小頂堆排序後生成的是降序排列。

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

  • 問題1:在查資料的過程當中發現不少博客都喜歡把堆和棧放到一塊來說,感受這二者並無什麼關聯,可是爲何會把它們兩個放在一塊兒比較呢?
  • 問題1解決方案:由於棧與堆都是Java用來在Ram中存放數據的地方。Java把內存劃分紅兩種:一種是棧內存,一種是堆內存。因此棧和堆常常被放在一塊兒比較。
    • 棧內存:在函數中定義的一些基本類型的變量和對象的引用變量都在函數的棧內存中分配。 當在一段代碼塊定義一個變量時,Java就在棧中爲這個變量分配內存空間,當超過變量的做用域後,Java會自動釋放掉爲該變量所分配的內存空間,該內存空間能夠當即被另做他用。
    • 堆內存:堆內存用來存放由new建立的對象和數組。
  • 棧內存與堆內存的區別
    • 堆內存用來存放由new建立的對象和數組,棧內存用來存放方法或者局部變量等。
    • 堆是先進先出,後進後出;棧是後進先出,先進後出。

代碼調試中的問題和解決過程

  • 問題1:在實現PP12.1的時候,輸出的結果並非按照進入隊列的順序輸出的。
  • 問題1解決方法:當初張昊然同窗和我討論PP12.1時,我說應該不能直接用書上的優先級隊列,由於它和隊列仍是有區別的,可是本身實現的時候雖然從新寫了一個,可是感受實際上仍是實現了一個優先級隊列。在查了相關資料以後,有一篇博客中寫到說將生成的堆使用層序遍歷就能夠輸出隊列的結果了,本來我是使用ArrayHeap類,爲了查看堆的構造而改爲了LinkedHeap類,可是從樹的結構來看就算使用層序遍歷它也依舊不是按照元素進入隊列的順序排列的。
  • 後來我又嘗試了前序、中序和後序遍歷,發現都不能夠。
  • 最後上網查了不少資料發現彷佛用堆只能實現特殊的優先級隊列,由於堆自己的性質就限制了它。

代碼託管

  • 上週代碼量:15094

上週考試錯題總結(正確爲綠色,錯誤爲紅色)

  • 錯題1:Since a heap is a binary search tree, there is only one correct location for the insertion of a new node, and that is either the next open position from the left at level h or the first position on the left at level h+1 if level h is full.
    • A .True
    • B .False
  • 錯題1解決方法:只關注了後面說的插入結點的位置的表述是否正確,忽略了前面的「二叉查找樹」,原話說的是徹底樹,而二叉查找樹並不都是徹底樹,因此這道題題乾的表述是錯誤的。
  • 錯題2:The addElement operation for both the linked and array implementations is O(n log n).
    • A .True
    • B .False
  • 錯題2解決方法:添加方法的時間複雜度應該爲O(logn),在上面的教材內容總結裏已經詳細分析過了。

結對及互評

點評模板:

  • 博客中值得學習的或問題:
    • 相比較以前而言博客的內容增長了對代碼的理解,值得誇獎。可是對於在學習過程當中遇到的問題記錄仍是有點草率,不過感受本週的內容比較簡單,確實不像以前會有那麼多問題。

點評過的同窗博客和代碼

  • 本週結對學習狀況
    • 20172322
    • 結對學習內容
      • 討論了PP12.1的實現

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

  • 感受本週的內容比較簡單,代碼方面課本上也給的很全很詳細,因此這周的問題不是不少,寫博客的時候找了好久才找到一個教材中遇到的問題。
  • 本學期的課本內容也基本快學完了,很大的一個感觸是本學期的博客質量總體而言要比上學期好,並且對課本內容的理解,對相關知識的應用也比上學期要進步了,但願能繼續保持吧。

學習進度條

代碼行數(新增/累積) 博客量(新增/累積) 學習時間(新增/累積) 重要成長
目標 5000行 30篇 400小時
第一週 10/10 1/1 10/10
第二週 246/366 2/3 20/30
第三週 567/903 1/4 10/40
第四周 2346/3294 2/6 20/60
第五週 2346/3294 2/8 30/90
第六週 1343/4637 2/8 20/110
第七週 654/5291 1/9 25/135
第八週 2967/8258 1/10 15/150
  • 計劃學習時間:20小時
  • 實際學習時間:15小時
  • 改進狀況:原本覺得堆會很難,但發現比本身想象的要簡單

參考資料

相關文章
相關標籤/搜索