1. 程式人生 > >堆——神奇的優先佇列(上) 【經典】

堆——神奇的優先佇列(上) 【經典】

 堆是什麼?是一種特殊的完全二叉樹,就像下面這棵樹一樣。

        有沒有發現這棵二叉樹有一個特點,就是所有父結點都比子結點要小(注意:圓圈裡面的數是值,圓圈上面的數是這個結點的編號,此規定僅適用於本節)。符合這樣特點的完全二叉樹我們稱為最小堆。反之,如果所有父結點都比子結點要大,這樣的完全二叉樹稱為最大堆。那這一特性究竟有什麼用呢?

        假如有14個數分別是99、5、36、7、22、17、46、12、2、19、25、28、1和92。請找出這14個數中最小的數,請問怎麼辦呢?最簡單的方法就是將這14個數從頭到尾依次掃一遍,用一個迴圈就可以解決。這種方法的時間複雜度是O(14)也就是O(N)。

1 2 3 4 for(i=1;i<=14;i++) { if(a[ i]<min)    min=a[ i]; }

        現在我們需要刪除其中最小的數,並增加一個新數23,再次求這14個數中最小的一個數。請問該怎麼辦呢?只能重新掃描所有的數,才能找到新的最小的數,這個時間複雜度也是O(N)。假如現在有14次這樣的操作(刪除最小的數後並新增一個新數)。那麼整個時間複雜度就是O(142)即O(N2)。那有沒有更好的方法呢?堆這個特殊的結構恰好能夠很好地解決這個問題。

        首先我們先把這個14個數按照最小堆的要求(就是所有父結點都比子結點要小)放入一棵完全二叉樹,就像下面這棵樹一樣。

        很顯然最小的數就在堆頂,假設儲存這個堆的陣列叫做h的話,最小數就是h[ 1]。接下來,我們將堆頂的數刪除,並將新增加的數23放到堆頂。顯然加了新數後已經不符合最小堆的特性,我們需要將新增加的數調整到合適的位置。那如何調整呢?

        向下調整!我們需要將這個數與它的兩個兒子2和5比較,並選擇較小一個與它交換,交換之後如下。

        我們發現此時還是不符合最小堆的特性,因此還需要繼續向下調整。於是繼續將23與它的兩個兒子12和7比較,並選擇較小一個交換,交換之後如下。

        到此,還是不符合最小堆的特性,仍需要繼續向下調整直到符合最小堆的特性為止。

        我們發現現在已經符合最小堆的特性了。綜上所述,當新增加一個數被放置到堆頂時,如果此時不符合最小堆的特性,則將需要將這個數向下調整,直到找到合適的位置為止,使其重新符合最小堆的特性。

 

        向下調整的程式碼如下。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 void siftdown(int i) //傳入一個需要向下調整的結點編號i,這裡傳入1,即從堆的頂點開始向下調整  { int t,flag=0;//flag用來標記是否需要繼續向下調整  //當i結點有兒子的時候(其實是至少有左兒子的情況下)並且有需要繼續調整的時候迴圈窒執行 while( i*2<=n && flag==0 ) {         //首先判斷他和他左兒子的關係,並用t記錄值較小的結點編號  if( h[ i] > h[ i*2] ) t=i*2; else t=i;  //如果他有右兒子的情況下,再對右兒子進行討論  if(i*2+1 <= n) { //如果右兒子的值更小,更新較小的結點編號   if(h[ t] > h[ i*2+1]) t=i*2+1; } //如果發現最小的結點編號不是自己,說明子結點中有比父結點更小的   if(t!=i) { swap(t,i);//交換它們,注意swap函式需要自己來寫 i=t;//更新i為剛才與它交換的兒子結點的編號,便於接下來繼續向下調整  } else flag=1;//則否說明當前的父結點已經比兩個子結點都要小了,不需要在進行調整了  } }

        我們剛才在對23進行調整的時候,竟然只進行了3次比較,就重新恢復了最小堆的特性。現在最小的數依然在堆頂為2。之前那種從頭到尾掃描的方法需要14次比較,現在只需要3次就夠了。現在每次刪除最小的數並新增一個數,並求當前最小數的時間複雜度是O(3),這恰好是O(log214)即O(log2N)簡寫為O(logN)。假如現在有1億個數(即N=1億),進行1億次刪除最小數並新增一個數的操作,使用原來掃描的方法計算機需要執行大約1億的平方次,而現在只需要1億*log1億次,即27億次。假設計算機每秒鐘可以執行10億次,那原來則需要一千萬秒大約115天!而現在只要2.7秒。是不是很神奇,再次感受到演算法的偉大了吧。

        說到這裡,如果只是想新增一個值,而不是刪除最小值又該如何操作呢?即如何在原有的堆上直接插入一個新元素呢?只需要直接將新元素插入到末尾,再根據情況判斷新元素是否需要上移,直到滿足堆的特性為止。如果堆的大小為N(即有N個元素),那麼插入一個新元素所需要的時間也是O(logN)。例如我們現在要新增一個數3。

 

        先將3與它的父結點25比較,發現比父結點小,為了維護最小堆的特性,需要與父結點的值進行交換。交換之後發現還是要比它此時的父結點5小,因此需要再次與父結點交換。至此又重新滿足了最小堆的特性。向上調整完畢後如下。

        向上調整的程式碼如下。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void siftup(int i) //傳入一個需要向上調整的結點編號i { int flag=0; //用來標記是否需要繼續向上調整 if(i==1)  return//如果是堆頂,就返回,不需要調整了     //不在堆頂 並且 當前結點i的值比父結點小的時候繼續向上調整  while(i!=1 && flag==0) { //判斷是否比父結點的小  if(h[ i]<h[ i/2]) swap(i,i/2);//交換他和他爸爸的位置  else flag=1;//表示已經不需要調整了,當前結點的值比父結點的值要大  i=i/2; //這句話很重要,更新編號i為它父結點的編號,從而便於下一次繼續向上調整  } }

相關推薦

——神奇優先佇列() 經典

 堆是什麼?是一種特殊的完全二叉樹,就像下面這棵樹一樣。         有沒有發現這棵二叉樹有一個特點,就是所有父結點都比子結點要小(注意:圓圈裡面的數是值,圓圈上面的數是這個結點的編號,此規定僅適用於本節)。符合這樣特點的完全二叉樹我們稱為最小堆。反之,如果所有父結點都比子結點要大,這樣的完全二

Codeforces 134C Swaps (bfs+優先佇列模板

There are n players sitting at a round table. All of them have s cards of n colors in total. Besides, initially the first person had ca

資料結構之用實現優先佇列

#include <stdio.h> #include <malloc.h> /** * 使用堆來實現優先佇列 * 堆的最重要性質就是子節點的值>=父節點的值, *

經典動態規劃

答案 意思 行修改 優化 待修改 最長 長度 u+ mem 五道經典動態規劃問題1)最大子序列和題目描述:一個序列,選和最大的子序列轉移方程:sum[i]=max{sum[i-1]+a[i],a[i]}當前元素的狀態是:自己單獨一組還是並到前面最後的答案max{sum[i]

資料結構--6優先佇列

操作 插入:空穴上濾策略,新元素在堆中上濾直到找出正確位置。 刪除最小者(DeleteMin):返回並刪除佇列中最小的元素,將堆中最後一個元素放入合適位置,空穴下濾策略。 限制:不能進行Find操作。  實現方式 1.利用簡單鏈表,在表頭O(1)插入,遍歷刪除最小元;或者

HDU3567 進階搜尋 IDA*演算法 八數碼經典

題意是給你兩個八數碼,讓你輸出從第一個八數碼到第二個八數碼的最短路徑,且要求該路徑也是最短路徑下的字典序最小的路徑。 思路:我一開始以為只是簡單的在結構體判定一下,讓其按照字典序最小的順序去尋路,後來發現這樣做的後果是路徑不是最小,嗯。於是就上網查部落格,然後就學會了A*演算法的升級版IDA

hdu1043 雙向bfs+康拓展開經典

題意大致就是給你一個3X3的矩陣,你要把矩陣轉換成12345678的形式,在矩陣中是有一個空缺處可以供你移動滑塊的,問你是否可以把給你的矩陣轉換成12345678的形式,算了題意自己去看吧,有圖更加直接 思路:這裡主要考慮這幾個方面:第一個如何判斷經過一定轉換的矩陣是規範矩陣?這裡就用到了康

二十三 基於實現優先佇列

複雜度分:     基於堆實現優先佇列:   package com.lt.datastructure.MaxHeap; import com.lt.datastructure.Queue.Queue; /** * 堆實現優先佇列 */ public

基於優先佇列(Java實現)

優先佇列的最重要的操作:刪除最大元素(或最小)和插入元素。資料結構二叉堆能夠很好的實現佇列的基本操作。 二叉堆的結點按照層級順序放入陣列,用長度為N+1的私有陣列pq來表示一個大小為N的堆(堆元素放在pq[1]至pq[N]之間,為方便計數,未使用pq[0]),跟

經典一篇文章初識大資料,及大資料相關框架Hadoop、spark、flink等

今天看到一篇講得比較清晰的框架對比,這幾個框架的選擇對於初學分散式運算的人來說確實有點迷茫,相信看完這篇文章之後應該能有所收穫。 簡介 大資料是收集、整理、處理大容量資料集,並從中獲得見解所需的非傳統戰略和技術的總稱。雖然處理資料所需的計算能力或儲存容量早已超過一

經典5種IO模型 | IO多路複用

上篇回顧:靜態伺服器+壓測 3.2.概念篇 1.同步與非同步 同步是指一個任務的完成需要依賴另外一個任務時,只有等待被依賴的任務完成後,依賴的任務才能算完成。 非同步是指不需要等待被依賴的任務完成,只是通知被依賴的任務要完成什麼工作。然後繼續執行下面程式碼邏輯,只要自己完成了整個任務就

8.基於二叉優先佇列演算法

其實根據已經看到的虛擬碼來說,感覺它的計算方式還是很簡單的,主要還是基於原有二叉堆的實現演算法上進行一定程度的改造,一個是入隊演算法,就是指新增一個元素到原有陣列之中,該陣列本來已經實現了二叉堆,一開始先將新增的元素放置在陣列最尾部,也就是二叉堆的最後一個三角節點處,開始進行

檢測到在整合的託管管道模式下不適用的ASP.NET設定的解決方法(非簡單設定為經典模式)

我們將ASP.NET程式從IIS6移植到IIS7,可能執行提示以下錯誤: HTTP 錯誤 500.23 - Internal Server Error 檢測到在整合的託管管道模式下不適用的 ASP.NET 設定。 為什麼會出現以上錯誤?

二叉樹(優先佇列

堆是一種特殊的二叉樹。 最小值堆:最小值堆的特性。 對於堆的任意非葉節點K,K的值總是小於或者等於左右子節點。 K <= 左節點;K <= 又節點; 堆例項: 堆實際上是一個完全二叉樹(若設二叉樹的深度為h,除第 h 層外,其它各層 (1~h-1)

資料結構與演算法隨筆之------優先佇列

 堆是什麼?是一種特殊的完全二叉樹,就像下面這棵樹一樣。         有沒有發現這棵二叉樹有一個特點,就是所有父結點都比子結點要小(注意:圓圈裡面的數是值,圓圈上面的數是這個結點的編號,此規定僅適用於本節)。符合這樣特點的完全二叉樹我們稱為最小堆。反之,如果

洛谷p3378 --基於優先佇列實現

加入了讀入掛,如果去掉greater<int>,則為最大堆 #include<cstdio> #include<queue> //不要忘記標頭檔案 using namespace std; priority_queue<int,vector<in

經典《Java170道面試筆試題全面含答案》涉及java/資料庫/Spring框架/JVM/資料結構演算法/設計模式相關

《Java170道面試筆試題全集》 -更新版-8.302018/4/7 日常修復2017/12/28 更新文章1、新增二級目錄2、對部分問題進行了補充9/24緊急修改以下問題(存在嚴重錯誤)問題3;完善問題10、11問題目錄:1、面向物件的特徵有哪些方面?2、訪問修飾符pub

xShell連線vmware虛擬機器經典

在剛剛開始學習Linux的同學,在VMware安裝好Linux之後,不知道怎麼去設定Ip,通過xshell去和虛擬機器進行連線,下面就是我自己學習時候總結的連線方法,希望大家在連線後好好學習,我們一起

優先佇列)優化dijkstra(鄰接矩陣)

上篇部落格大家學習了最短路的兩種基本演算法,忘了告訴大家,floyd可以完成有負權值的最短路,而dijkstra則不行。若要想要更優的進行負權值最短路,請期待我的SPFA詳解。 現在開始堆優化dijkstra的講解。 其實只要理解了dijkstra的本質,這

優先佇列

分析與思考 陣列是完全二叉樹的儲存結構,完全二叉樹是陣列的邏輯結構,這樣我們就可以使用樹形結構來解決線性問題。 堆 大頂堆(用於升序排序,根節點大於等於兩個子節點) 小頂堆(用於降序排序,根節點小於等於兩個子節點) 堆的插入與刪除:尾部插入,頭部