20172308 《程序設計與數據結構》第八週學習總結

教材學習內容總結

第 十二 章 優先隊列與堆

1、堆:具備兩個附加屬性的一顆二叉樹html

  • 它是一顆徹底樹
  • 對每一結點,它小於或等於其左右孩子(或大於等於其左右孩子)
    最小堆:對每一結點,它小於或等於其左右孩子
    最大堆:對每一結點,它大於或等於其左右孩子
    最小堆將其最小元素存儲在二叉樹的根處,且其根的兩個孩子一樣也是最小堆
    最大堆將其最大元素存儲在二叉樹的根處,且其根的兩個孩子一樣也是最大堆

堆是二叉樹的擴展,繼承了二叉樹的全部操做git

  1. addElement操做
  • addElcment方法將給定的元素添加到堆中的恰當位置處,且維持該堆的徹底性屬性和有序屬性
  • 若是給定元素不是 Comparable的,則該方法將拋出一個 ClasscastException異常。
  • 徹底屬性是指:若是一棵二又樹是平衡的,即全部葉子都位於h或h-1層,其中h爲log2 n,且n是樹中的元素數目,以及全部h層中的葉子都位於該樹的左邊,那麼該樹就被認爲是徹底的
  • 增長元素的兩種可能性(如圖):
    由於一個堆就是一棵徹底樹,因此對於插入的新結點而言只存在一個正確的位置:
    要麼是h層左邊的下一個空位置(即最後一層未滿,將插入到最左邊的位置)
    要麼是h+1層左邊的第1個位置(若是h層已滿)

當新結點插入到正確位置以後,就要考慮到排序屬性(即將堆調整爲大頂堆或小頂堆)
當調整的類型是小頂堆時,作法以下(下圖爲調整過程):
只需將該新值和其雙親值進行比較,若是該新結點小於其雙親則將它們互換,咱們沿着樹向上繼續這一過程,直至該新值要麼是大於其雙親要麼是位於該堆的根處
算法

一般,在堆實現中,會對樹中的最末一片葉子進行跟蹤記錄編程

  1. removeMin操做
  • removeMin方法將刪除最小堆中的最小元素並返回它
  • 最小元素是存儲在最小堆的根處的,因此咱們須要返回存儲在根處的元素並用堆中的另外一元素替換它
  • 與addElement操做同樣,是要維持該樹的徹底性,那麼只有一個能替換根的合法元素,且它是存儲在樹中最末一片葉子上的元素
  • 該最末一片葉子將是樹中h層上最右邊的葉子
  • 替換以後,還要對堆進行調整操做,即變成最小堆(維持有序屬性)
  1. findMin操做:返回一個指向該最小堆中最小元素的引用(該元素老是被存儲在樹根處,因此直接返回存儲在根處的元素便可)

2、使用堆:優先級隊列數組

  • 優先級隊列就是遵循兩個排序規則的集合:
    第一,具備更高優先級的項目在先
    第二,具備相同優先級的項目使用先進先出方法來肯定其排序
  • 優先級隊列具備多種應用(好比,操做系統中的任務調度,網絡中的通訊調度,甚至是汽車維修處的做業調度)
  • 可使用某一隊列列表(其中每一隊列都表示了給定優先級的項目)來實現一個優先級隊列
  • (這一問題的另外一解決方案是使用一個最小堆)
    按照優先級對堆排序完成了第一次排序(高優先級的項目在先)。可是,咱們必須對具備相同優先級項目的先進先出排序進行操縱:
    解決方案是建立一個 PriorityQueueNode對象,它存儲的是將被放置在隊列中的元素,該元素的優先級,以及元素放進隊列的順序
    而後,咱們只需爲 PriorityNode類定義個 compareTo方法,以便先對優先級進行比較,而後在優先級同樣的時候再對階進行比較

3、用鏈表實現堆緩存

  • 堆的鏈表實現要求在插入元素後可以向上遍歷該樹,因此堆中的結點必須存儲指向其雙親的指針
  • BinaryTreeNode類沒有雙親指針,因此建立一個HeapNode類鏈表實現(對BinaryTreeNode進行擴展並添加一個雙親指針)
  1. addElement操做
    addElement必須完成三個任務:
* 在恰當位置處添加一個新元素
* 對堆進行重排序以維持其排序屬性
* 將lastNode指針從新設定爲指向新的最末結點
  1. removeMin操做
    removeMin方法必須完成三個任務:
* 用存儲在最末結點處的元素替換存儲在根處的元素
* 對堆進行重排序(若有必要)
* 返回初始的根元素

鏈表實現的removeMin方法必須刪除根元素,並用來自最末結點的元素替換它網絡

  1. findMin操做
    findMin方法僅僅返回一個指向存儲在堆根處元素的引用(所以複雜度O(1) )

4、用數組實現堆數據結構

  • 優勢:
    堆的數組實現提供了一種比鏈表實現更爲簡潔的選擇。
    在鏈表實現中,咱們須要上下將歷樹以肯定樹的最末一片葉子或下一個插入結點的雙親(鏈表實現中的許多複雜問題都與此有關)
    這些困難在數組實現中就不存在了,由於經過查看存儲在數組中的最末一個元素,咱們就可以肯定出該樹中的最末結點學習

  • 與鏈表實現堆的不一樣之處:
    樹的根位於位置0,對於每一結點n,n的左孩子將位於數組的2n+1位置處,n的右孩子將位於數組的2(n+1)位置處(反過來一樣也是對的)
    對於任何除了根以外的結點n,n的雙親位於(n-1)/2位置處,由於咱們可以計算雙親和孩子的位置,因此與鏈表實現不一樣的是,數組實現不須要建立一個 Heap Node類測試

  • addElement操做
    數組實現的addElement方法必須完成3項任務:
在恰當位置處添加新結點
對堆進行重排序以維持其排序屬性
將count遞增1

跟數組實現同樣,該方法必須首先檢查空間的可用,須要時要進行擴容

數組實現的addElement操做與鏈表的相同(都爲O(logn)),但數組實現的效率更高

  • removeMin操做
    removeMin方法必須完成三項任務:
用存儲在最末元素處的元素替換存儲在根處的元素
必要時對堆進行重排序
返回初始的根元素

鏈表實現和數組實現的removeMin操做的複雜度都爲O(logn)

  • findMin操做
    與鏈表實現的同樣,findMin方法僅僅是返回一個引用,該引用指向存儲在堆的根處或數組的0位置處的元素,其複雜度爲O(1)

5、使用堆:堆排序

  • 使用堆來對某個數字列表進行排序:
    將列表的每一元素添加到堆中,而後次一個地將它們從根中刪除,
    在最小堆的情形下,排序結果將是該列表以升序排列;
    在最大堆的情形下,排序結果將是該列表以降序排列

  • 堆排序的複雜度分析:
    因爲添加和刪除操做的複雜度都爲O(log n),所以能夠得出堆排序的複雜度也是O(log n),可是,這些操做的複雜度爲O(log n)指的是在含有n個元素的列表中添加和刪除一個元素。
    對任一給定的結點,插入到堆的複雜度都是O(log n),所以n個結點的複雜度將是 O(n log n),
    刪除一個結點的複雜度爲O(log n),所以對n個結點的複雜度爲 O(n log n)
    對於堆的排序算法,咱們須要執行addElement和 removeElement兩個操做n次,即列表中每一個元素一次。所以,最終的複雜度爲2 x n x logn,即 O(n log n)

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

問題1:數組實現的addElement操做與鏈表的相同(都爲O(logn)),但數組實現的效率更高

問題1解析:

首先,先從數組和鏈表自己來看:
一個常見的編程問題: 遍歷一樣大小的數組和鏈表, 哪一個比較快?
若是按照教科書上的算法分析方法,你會得出結論,這2者同樣快, 由於時間複雜度都是 O(n)
可是在實踐中, 這2者卻有極大的差別:
經過下面的分析你會發現, 其實數組比鏈表要快不少
首先介紹一個概念:memory hierarchy (存儲層次結構),電腦中存在多種不一樣的存儲器,以下表

各級別的存儲器速度差別很是大,CPU寄存器速度是內存速度的100倍! 這就是爲何CPU產商發明了CPU緩存。 而這個CPU緩存,就是數組和鏈表的區別的關鍵所在。

CPU緩存會把一片連續的內存空間讀入, 由於數組結構是連續的內存地址,因此數組所有或者部分元素被連續存在CPU緩存裏面, 平均讀取每一個元素的時間只要3個CPU時鐘週期。
而鏈表的節點是分散在堆空間裏面的,這時候CPU緩存幫不上忙,只能是去讀取內存,平均讀取時間須要100個CPU時鐘週期。
這樣算下來,數組訪問的速度比鏈表快33倍!這裏只是介紹概念,具體的數字因CPU而異)

【參考資料】
從cpu和內存來理解爲何數組比鏈表查詢快

問題2:用堆來實現優先級隊列的代碼分析,書上只簡單介紹了,沒有具體說說代碼的實現,簡單分析一下

問題2解析:

  • 優先級隊列是在隊列的基礎上增長限制的,因此在理解了優先級隊列的代碼後,PP12.1就很好作了
  • 課本經過兩個類完成了堆實現的優先級隊列

第一個PrioritizedObject類:
PrioritizedObject對象表示優先隊列中的一個節點,包含一個可比較的對象、到達順序和優先級值
代碼以下:

public T getElement() 
    {
        return element;//返回節點值
    }
    
    public int getPriority() 
    {
        return priority;//返回節點優先級
    }

    public int getArrivalOrder() 
    {
        return arrivalOrder;//返回到節點的距離順序
    }

PrioritizedObject最重要的方法是優先級的比較:
代碼以下:

public int compareTo(PrioritizedObject obj) //若是此對象的優先級高於給定對象,則返回1,不然返回-1
    {
      int result;
                
      if (priority > obj.getPriority())
          result = 1;
      else if (priority < obj.getPriority())
          result = -1;
      else if (arrivalOrder > obj.getArrivalOrder())
          result = 1;
      else
          result = -1;
      
      return result;
    }

先比較元素的優先級,高的先輸出;
若是優先級同樣,再根據先進先出的方式把距離節點近的先輸出
---------------------------------------------------------------------------------------------------------------------------------

第二個就是PriorityQueue類:
首先是添加操做:

public void addElement(T object, int priority)
    {
        PrioritizedObject<T> obj = new PrioritizedObject<T>(object, priority);
        super.addElement(obj);
    }

聲明PrioritizedObject變量以後,即肯定了添加元素及其優先級
這裏就直接調用了堆的addElement方法,完成添加功能

而後是刪除操做:直接調用堆的刪除最小值的方法,由於最小的元素是在堆頂處的,即隊列的頭部

public T removeNext() //從優先級隊列中刪除下一個最高優先級元素並返回對它的引用
    {
        PrioritizedObject<T> obj = (PrioritizedObject<T>)super.removeMin();
        return obj.getElement();
    }

代碼運行中的問題及解決過程

問題1:課後編程PP12.1,用堆來實現隊列,剛看完課本,都是用鏈表啊,數組啊來實現堆的,這題要用堆來實現隊列,是要用鏈表或數組實現的堆來實現隊列?能用隊列來實現堆嗎?課本上也有堆的一個應用:實現優先級隊列,額,優先級隊列和隊列有什麼不同嗎?
問題1解決過程:
  • 首先解決一下最後一個問題:優先級隊列和隊列
    課本的定義:優先級隊列遵循兩個排序規則的集合:具備更高優先級的項目在先,具備相同優先級的項目使用先進先出方法來肯定其排序
    百度的資料:
    普通的隊列是一種先進先出的數據結構,元素在隊列尾追加,而從隊列頭刪除
    在優先隊列中,元素被賦予優先級:當訪問元素時,具備最高優先級的元素最早刪除,優先隊列具備最高級先出

感受很奇怪,可是仔細想了一下:
隊列只要知足隊尾加元素,隊頭刪除元素就好了,對隊列中的元素順序沒有要求,只要在下次刪除元素前,按照優先級把元素排好就好了把
(就像醫院排隊掛號同樣:要考慮到軍人這一優先級的存在,要先於其餘人輸出)

這一題就不用考慮到優先這一條件了

  • 而後第二個問題,能用隊列來實現堆嗎?
    這個問題。。。先留在這裏。。。哈哈哈哈哈。。。我再想一想

  • 如今用堆來實現隊列
    正如上面教材問題裏總結的:
    在明白了優先級隊列的實現思路後,只要把其類裏的形參Priority刪掉便可,即去掉優先級比較的這一操做
    測試結果以下:

【參考資料】
百度百科
優先級隊列和隊列有什麼區別?

本週錯題

本週無錯題

代碼託管

結對及互評

  • 博客中值得學習的或問題:
    • 侯澤洋同窗的博客排版工整,界面很美觀,而且本週還對博客排版、字體作了調整,很用心
    • 問題總結作得很全面:對課本上不懂的代碼會作透徹的分析,即使能夠直接拿過來用而不用管他的含義
    • 對教材中的細小問題都可以關注,而且主動去百度學習
    • 代碼中值得學習的或問題:
    • 對於編程的編寫總能找到角度去解決
  • 本週結對學習狀況
    • 20172302
    • 結對學習內容
      • 第十二章內容:優先隊列與堆

學習進度條

代碼行數(新增/累積) 博客量(新增/累積) 學習時間(新增/累積) 重要成長
目標 5000行 30篇 400小時
第一週 0/0 1/1 4/4
第二週 560/560 1/2 6/10
第三週 415/975 1/3 6/16
第四周 1055/2030 1/4 14/30
第五週 1051/3083 1/5 8/38
第六週 785/3868 1/6 16/54
第七週 733/4601 1/7 20/74
第八週 2108/6709 1/8 20/74
相關文章
相關標籤/搜索