常用數據結構與算法:二叉堆(binary heap)

轉自:https://blog.csdn.net/u010224394/article/details/8834969

一:什么是二叉堆

二:二叉堆的實現

三:使用二叉堆的幾個例子

一:什么是二叉堆

1.1:二叉堆簡介

二叉堆故名思議是一種特殊的堆,二叉堆具有堆的性質(父節點的鍵值總是大于或等于(小于或等于)任何一個子節點的鍵值),二叉堆又具有二叉樹的性質(二叉堆是完全二叉樹或者是近似完全二叉樹)。當父節點的鍵值大于或等于(小于或等于)它的每一個子節點的鍵值時我們稱它為最大堆(最小堆)。

? ? ? 二叉堆多數是以數組作為它們底層元素的存儲,根節點在數組中的索引是1,存儲在第n個位置的父節點它的子節點在數組中的存儲位置為2n與2n+1。可以借用網上的一幅圖來標示這種存儲結構。其中數字表明節點在數組中的存儲位置。

? ? ? ? 1? ? ? ? ? ? ?

? ? ? /? \? ? ? ? ?

? ? 2? ? 3? ? ? ? ? ?

? ? / \? / \? ? ?

? 4? 5? 6? 7? ?

? / \ / \? ? ? ?

8? 9 10 11? ? ?

1.2:二叉堆支持的操作

? ? ? ?二叉堆通常支持以下操作:刪除,插入節點,創建二叉堆。這些操作復雜對都是O(log2n)

? ? ? ?二叉堆也可以支持這些操作:查找。O(n)復雜度。

1.3:二叉堆的特點

? ? ? ?二叉堆是專門為取出最大或最小節點而設計點數據結構,這種數據結構在查找一般元素方面性能和一般數組是沒有多大區別的。二叉堆在取出最大或最最小值的性能表現是O(1),取出操作完成之后,二叉堆需要一次整形操作,以便得到下一個最值,這個操作復雜度O(log2n)。這是一個相當理想的操作時間。但是二叉堆也有一個缺點,就是二叉堆對存儲在內存中的數據操作太過分散,這導致了二叉堆在cpu高速緩存的利用與內存擊中率上面表現不是很好,這也是一個二叉堆理想操作時間所需要付出的代價。

1.4:二叉堆的使用范圍

? ? ? ?二叉堆主要的應用擊中在兩個地方一個是排序,一個是基于優先級隊列的算法。比如:

? ? ? ?1:A*尋路

? ? ? ?2:統計數據(維護一個M個最小/最大的數據)

? ? ? ?3:huffman code(數據壓縮)

? ? ? ?4:Dijkstra's algorithm(計算最短路徑)

? ? ? ?5:事件驅動模擬(粒子碰撞。這個比較有意思,從國外的一個網站看到過)

? ? ? ?5:貝葉斯垃圾郵件過濾(這個只是聽過沒怎么了解)

二:二叉堆的實現

2.1:插入

? ? ? 當我們要在二叉堆中插入一個元素時我們通常要做的就是有三步

? ? ? 1.把要插入的節點放在二叉堆的最末端

? ? ? 2.把這個元素和它的父節點進行比較,如果符合條件或者該節點已是頭結點插入操作就算完成了

? ? ? 3.如果不符合條件的話就交換該節點和父節點位置。并跳到第二步。

假設我們有一個如下的最大二叉堆,圓圈內數字代表的是節點,x代表節點插入位置,我們要插入的值是15,則步驟如下:

我們插入的位置為X,X的父節點是8,X與8進行比較,發現X(15)大于8于是8與15互換。

X(15)接著和11比較,發現15比11大于是互換。

15已經是頭結點操作插入操作結束。

? ? ? ?插入節點不停向上比較的過程叫做向上整形。

voidinsert(Data data)

{

if(_last_index==0)//我們的數組從index 1,我們用第一個插入的數填充index 0.

{

_array.push_back(key);

}

_array.push_back(data);//將key插入數組最末

swim_up(++_last_index);//對最后一個插入的數字進行向上整形

}

voidswim_up(size_type n)//向上整形

{

size_type j;//n 代表向上整形的元素,j代表n的父節點

while( (j = n /2) >0&& compare(_array[n],? _array[j]) )//判斷n父節點是否為空&比較n與j大小

{

exchange(n, j);

n=j;

}

}

2.2:刪除

? ? ? ?二叉堆的刪除操作指的是刪除頭結點的操作,也就是最小或者最大的元素。刪除操作分為三步:

? ? ? 1.首先將頭結點與最后一個節點位置互換(互換之后的最后一個節點就不再視為二叉堆的一部分)。

? ? ? 2.將互換之后的新的頭結點與子節點進行比較,如果符合條件或者該節點沒有子節點了則操作完成。

? ? ? 3.將它和子節點互換,并重復步驟2。(如果它的兩個子節點都比它大/小,那么它與其中較大/小的一個互換位置。最大堆的話與較大的互換,最小堆的話與較小的互換。)

假設我們有如下一個最大堆,圓圈內數字表示節點的值:

現在我們刪除頭結點11,我們將11頭結點與最末一個節點4互換。

互換之后我們剔除了最后一個節點。我們將4與它的子節點進行比較,發現它比它的兩個節點都小,不滿足條件跳到步驟3。

我們將4與它的子節點中較大的一個進行互換(最小堆則和最小的一個互換)。然后繼續進行步驟2,但是我們發現節點4已經沒有子節點于是操作結束。

? ? ? ?這個不停向下比較的操作我們稱作向下整形。

constT&get_min()//不允許修改值,這樣會造成堆被破壞.

? ? ? ? ? ? ? ? {

return_array[1];

? ? ? ? ? ? ? ? }

voidpop_min()//如果沒有數據在隊列中,這個行為是未定義的.

? ? ? ? ? ? ? ? {

_array[1]=_array[_last_index--];

? ? ? ? ? ? ? ? ? ? ? ? _array.pop_back();

sink_down(1);

? ? ? ? ? ? ? ? }

voidsink_down(size_type n)

? ? ? ? ? ? ? ? {

size_type j;//j 是 n的子節點的索引

while( ( j =2* n) <= _last_index )

? ? ? ? ? ? ? ? ? ? ? ? {

if( j +1<= _last_index && _compare(_array[j+1],_array[j]) )//比較兩個子節點,取出其中較小的.

j=j+1;

if( _compare(_array[j],_array[n]) )//較小的子節點與父節點進行比較

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? exchange(n,j);

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? n=j;

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

2.3:構建二叉堆

? ? ? ?構建二叉堆很簡單只要我們把要構建的元素一個一個的進行插入操作插入到二叉堆中即構建了一個二叉堆。

2.4:堆排序

? ? ? ?堆排序其實分為以下幾步:

? ? ? ?1:首先將待排序的n個元素構建一個二叉堆array[n]

? ? ? ?2:執行刪除操作,只是這里我們并不是刪除頭結點,而是將頭結點換到二叉堆末尾,并形成一個出去隊列末尾的新二叉堆。

? ? ? ?3:重復步驟2,直到刪除了最后一個元素。

這個過程其實就是先構建一個最大/最小二叉堆,然后不停的取出最大/最小元素(頭結點),插入到新的隊列中,以此達到排序的目的。觀察下面這個從wiki上面截取的gif圖。

這幅圖描述的是一個最大堆,柱子的高度代表了元素的大小,可以看出不停的把頭結點換到新形成的二叉堆的最末,然后就形成了一個有序隊列。

三:使用二叉堆的例子

A*尋路:

? ? ? ? ? ? 這里只是舉一個相對于來說比較簡單的例子,用A*尋路來解決8-PUZZLE(8格數字拼圖),當然更為經典的一種是15-puzzle,它們道理都是一樣的。下面來看看這個問題的描述。

? ? ? ? ? ? 在一個九宮格里面,有1-8 8個數字和一個空格,我們可以移動空格上下左右相鄰的數字到空格,然后通過這種移動方式我們最終要求9宮格里面的數字,形成1-8的順訊排列。類似如下

1 3? ? ? 1? 3? ? 1 2 3? ? ? 1 2 3? ? ? 1 2 3

4 2 5 =>? 4 2 5 =>? 4? 5 =>? 4 5? ? =>? 4 5 6

7 8 6? ? 7 8 6? ? 7 8 6? ? ? 7 8 6? ? ? 7 8

初始 第1次移動 第2次移動 第3次移動 第4次移動

? ? ? ? ? ?這個問題在我小時候玩圖片拼板的時候很難,幾乎很久都拼不成功,但是我們只要找到決竅就行了。有兩種訣竅是廣泛使用的一種稱作Hamming priority function,而另外一種就是Manhattan priority function。這里我們使用更為廣泛使用的Manhattan方法作為講解。

? ? ? ? ? ?Manhattan方法:我們用這個9宮格里面每個數字到達自己指定位置的距離加上我們目前總共移動的步數來表示一個度量值M。這里所指的每個數字到達自己指定位置的距離指的是通過橫向移動和縱向移動到達自己規定位置的距離。舉例:

? ? ? ? ?1 ? 3 ??

? ? ? ? ?4 ? 2 ? 5

? ? ? ? ?7 ? 8 ? 6?

在這里圖中數字“1”在位置1上于是距離為0。數字“3”到達自己的指定位置需要右移一步于是距離為1,“4"在位置4上于是距離為0,"2"需要向上移動一步到達自己的制定位置距離為1,”5“需要左移一步距離為1,”7“”8“在指定位置上距離0,6需要向上移動一步距離1,于是這個圖形的總距離為4。 ? ? ??

? ? ? ? ? 我們從上圖的”初始“狀態開始,有兩種移動方法,一種是”3“移動到空格,一種是”5“移動到空格。我們應該選擇哪種移動方法呢,這個時候就需要使用我們剛才所說的度量值了,我們選擇度量值小的一種移動方式。”3“移動到空格的方法距離3,移動步數1,度量值M=4。”5“移動到度量空格的距離5,移動步數為1,度量值M=6。我們選擇”3“移動到空格的這種方式。這里的具體過程是我們把記錄下“3”和“5”移動的這兩種節點的父節點,然后分別計算他們的M值,然后放入到min bianry heap中,取出最小M值節點作為移動節點,并從min

binary heap中刪除這個節點。

1? 3? ? ? ? ? ? ? ? ? ? ? ? ? ? 1 3 5

4 2 5? ? ? ? ? ? ? ? ? ? ? ? ? ? 4 2

7 8 6? ? ? ? ? ? ? ? ? ? ? ? ? ? 7 8 6

"3"移動到空格,M=4? ? ? ? ? ? ? ? “5”移動到空格,M=6

當我們選出了第一次的移動節點之后,我們就要在第一次的移動節點上再決定下一次的移動節點,下一次怎么走一共有3+1種節點,3種是基于上一次移動后我們新加入的移動節點,1種是上一次我們并沒有沿用的移動節點,我們計算3種新節點的M值并記錄他們的父節點然后再把它們加入取出最小的作為下一次的移動節點,直到我們得到距離等于0的節點位置。

當我們找到距離等于0的節點之后,我們遞歸查找該節點父節點直到查找到根節點位置,這個查找的順續的逆序便是我們移動節點到達最終目的地的順序

這里有一個A*尋路中需要注意的地方,我們并不會刪除我們沒有沿用的節點,而是仍然留住它在min binary heap中作為備選節點以防現有路線不是最優解或是不能到達終點。

? 這種數字拼盤程序還有一種非常值得注意的地方,即是這種數字拼盤總是存在著一種無法求解的可能,比如8-puzzle中,這種排序和它的變種都無法解:

1 2 3?

4 5 6?

8 7

面對這種難題,有一種較為合理的解決方法來判斷,我們只需要交換我們初始節點中同一排的相鄰兩個節點位置(兩個都為非空節點)得到另外一種初始化節點,在這兩種方案中只有一種方案能夠解。所以我們只需要同時計算兩種初始節點,只要其中一個得出解了那么另外一個即可以判斷是無解的了。

好奇的你或許會問為什么交換了同一排相鄰的兩個非空節點的位置之后,新得到的節點的可解性與舊節點的可解性相反。這個問題嚴謹的數學解釋需要參考較早的研究論文,并且對于非專業學生也比較晦澀難懂。我能想到的比較容易解釋方式及是“同一排兩個節點交換了位置之后,你永遠也無法通過移動還原到交換前的模樣。”這也即是

1 2 3? ? ? ? 1 2 3

4 5 6 得不到=>4 5 6 的原因。

8 7? ? ? ? ? 7 8

實現:

? ? ? ? ? 下面是這個8-puzzle問題的代碼實現,零零散散寫了2,3百行的code,寫得比較隨意,代碼的泛型沒有做,所以暫時只支持8-puzzle問題的求解,但是只要稍微改動下就能支持n puzzle問題了。因為寫的較快,注釋暫時忽略了.........。代碼的輸出在標準輸出上,運用了上面講到的技術判斷不能求解的情況,二叉堆底層使用的vector,支持泛型。

代碼下載地址

---------------------本文來自 JoeyMIao 的CSDN 博客 ,全文地址請點擊:https://blog.csdn.net/u010224394/article/details/8834969?utm_source=copy

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內容

  • 一些概念 數據結構就是研究數據的邏輯結構和物理結構以及它們之間相互關系,并對這種結構定義相應的運算,而且確保經過這...
    Winterfell_Z閱讀 5,885評論 0 13
  • 瀝瀝的小雨帶著絲絲的歡愉,同學們來到了西安。這次旅行是我們即將上初二的第一次旅行。我們第一個來到的是芙蓉樓...
    豆言瑤語閱讀 311評論 0 0
  • 本周小影給大家推薦的是一部科普動漫——《工作細胞》。 這是一個關于你的故事。 人的細胞數量,約為37兆2千億個。 ...
    靜雅課堂閱讀 1,257評論 0 0
  • 文件和目錄操作 改變目錄 cd path/to/dir:到指定目錄 cd ..:到父目錄 cd -:到上次所在目錄...
    心智萬花筒閱讀 893評論 4 3
  • 這是一個長發的女人,直直頭發垂到肩旁。圓圓臉,單眼皮,見我注視著她,靦腆一笑,眼神透出簡單與善良。她上著白色潑墨襯...
    曾經是小黑閱讀 233評論 2 1