1、堆:具備兩個附加屬性的一顆二叉樹html
堆是二叉樹的擴展,繼承了二叉樹的全部操做git
當新結點插入到正確位置以後,就要考慮到排序屬性(即將堆調整爲大頂堆或小頂堆)
當調整的類型是小頂堆時,作法以下(下圖爲調整過程):
只需將該新值和其雙親值進行比較,若是該新結點小於其雙親則將它們互換,咱們沿着樹向上繼續這一過程,直至該新值要麼是大於其雙親要麼是位於該堆的根處
算法
一般,在堆實現中,會對樹中的最末一片葉子進行跟蹤記錄編程
2、使用堆:優先級隊列數組
3、用鏈表實現堆緩存
* 在恰當位置處添加一個新元素 * 對堆進行重排序以維持其排序屬性 * 將lastNode指針從新設定爲指向新的最末結點
* 用存儲在最末結點處的元素替換存儲在根處的元素 * 對堆進行重排序(若有必要) * 返回初始的根元素
鏈表實現的removeMin方法必須刪除根元素,並用來自最末結點的元素替換它網絡
4、用數組實現堆數據結構
優勢:
堆的數組實現提供了一種比鏈表實現更爲簡潔的選擇。
在鏈表實現中,咱們須要上下將歷樹以肯定樹的最末一片葉子或下一個插入結點的雙親(鏈表實現中的許多複雜問題都與此有關)
這些困難在數組實現中就不存在了,由於經過查看存儲在數組中的最末一個元素,咱們就可以肯定出該樹中的最末結點學習
與鏈表實現堆的不一樣之處:
樹的根位於位置0,對於每一結點n,n的左孩子將位於數組的2n+1位置處,n的右孩子將位於數組的2(n+1)位置處(反過來一樣也是對的)
對於任何除了根以外的結點n,n的雙親位於(n-1)/2位置處,由於咱們可以計算雙親和孩子的位置,因此與鏈表實現不一樣的是,數組實現不須要建立一個 Heap Node類測試
在恰當位置處添加新結點 對堆進行重排序以維持其排序屬性 將count遞增1
跟數組實現同樣,該方法必須首先檢查空間的可用,須要時要進行擴容
數組實現的addElement操做與鏈表的相同(都爲O(logn)),但數組實現的效率更高
用存儲在最末元素處的元素替換存儲在根處的元素 必要時對堆進行重排序 返回初始的根元素
鏈表實現和數組實現的removeMin操做的複雜度都爲O(logn)
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)
首先,先從數組和鏈表自己來看:
一個常見的編程問題: 遍歷一樣大小的數組和鏈表, 哪一個比較快?
若是按照教科書上的算法分析方法,你會得出結論,這2者同樣快, 由於時間複雜度都是 O(n)
可是在實踐中, 這2者卻有極大的差別:
經過下面的分析你會發現, 其實數組比鏈表要快不少
首先介紹一個概念:memory hierarchy (存儲層次結構),電腦中存在多種不一樣的存儲器,以下表
各級別的存儲器速度差別很是大,CPU寄存器速度是內存速度的100倍! 這就是爲何CPU產商發明了CPU緩存。 而這個CPU緩存,就是數組和鏈表的區別的關鍵所在。
CPU緩存會把一片連續的內存空間讀入, 由於數組結構是連續的內存地址,因此數組所有或者部分元素被連續存在CPU緩存裏面, 平均讀取每一個元素的時間只要3個CPU時鐘週期。
而鏈表的節點是分散在堆空間裏面的,這時候CPU緩存幫不上忙,只能是去讀取內存,平均讀取時間須要100個CPU時鐘週期。
這樣算下來,數組訪問的速度比鏈表快33倍!這裏只是介紹概念,具體的數字因CPU而異)
【參考資料】
從cpu和內存來理解爲何數組比鏈表查詢快
第一個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(); }
感受很奇怪,可是仔細想了一下:
隊列只要知足隊尾加元素,隊頭刪除元素就好了,對隊列中的元素順序沒有要求,只要在下次刪除元素前,按照優先級把元素排好就好了把
(就像醫院排隊掛號同樣:要考慮到軍人這一優先級的存在,要先於其餘人輸出)
這一題就不用考慮到優先這一條件了
而後第二個問題,能用隊列來實現堆嗎?
這個問題。。。先留在這裏。。。哈哈哈哈哈。。。我再想一想
如今用堆來實現隊列
正如上面教材問題裏總結的:
在明白了優先級隊列的實現思路後,只要把其類裏的形參Priority刪掉便可,即去掉優先級比較的這一操做
測試結果以下:
【參考資料】
百度百科
優先級隊列和隊列有什麼區別?
代碼行數(新增/累積) | 博客量(新增/累積) | 學習時間(新增/累積) | 重要成長 | |
---|---|---|---|---|
目標 | 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 |