數據結構與算法-堆

堆是一種特殊類型的二叉樹,具備如下兩個性質:
  • 每一個節點的值大於等於(或小於等於)其每一個子節點的值
  • 堆屬於徹底二叉樹
就像這樣:
上圖是大頂堆,若是每一個節點小於等於其每一個子節點的值,那它就是小頂堆。
有趣的是,堆能夠經過數組來實現。例如,數組 data = [50 43 49 15 28 40 30 5 10 23 15 20] 能夠表示上面的堆。數組中元素的排放順序表示節點按照從頂到底、每一層從左到右的順序放置。堆之因此能夠放到數組中,是由於它是一顆徹底二叉樹。能夠根據任意節點的下標,經過公式計算出子節點的下標。假設節點P的下標爲i,那麼節點P的左子節點下標爲2i + 1,右子節點下標爲2i + 2,相關證實過程能夠看這裏數據結構與算法-二叉樹性質。咱們這裏就以數組來實現堆結構,探討相關的建立、插入、刪除、運用的邏輯。
  • 建立
堆的建立有兩種方式。第一種是自上向下從一個空數組中建立堆,第二種是自下向上將已有的數組調整爲堆。首先探討自上向下的邏輯。
一、自上向下
從一個空數組開始,每個新節點都放到當前堆的末尾,若是發現插入節點以後破壞了堆的特性,那麼將新節點與其父節點進行交換,直至從新調整爲堆。假如如今有一段數據流:
49 50 43 15 28 40 5 25 10 23 30 20 55
自上向下的過程:
核心邏輯在於每次插入新節點以後,若是破壞了堆的結構,只要和父節點進行交換,直至調整爲堆便可。該算法的時間複雜度有O(nlgn),不是很理想,數據量比較大時不適合這種方案。
二、自下向上
一顆完整的二叉樹能夠分解成根節點,左子樹,右子樹。對於左右子樹又能夠分解成更多的子樹。若是咱們將樹中的每一個節點都看作一顆樹,只要每棵樹符合堆的特性,那麼整棵樹也是符合堆的特性的。基於以上思想,前人提出了一種自下向上的建立堆地方式。樹中最小的子樹實際上是葉子節點,因爲葉子節點沒有子樹,能夠認爲是符合堆的特性的。因此,咱們就從最後一個非葉子節點開始,按照從下向上,從右到左的順序,調整每一個子樹符合堆的特性,直到根節點,那麼整棵樹就成了堆。
假設一共有n的元素,最後一個非葉子節點的下標是多少呢?答案是n/2 -1。證實以下:
假設堆中度爲0、度爲一、度爲2的節點分別有n0、n一、n2個,那麼n = n0 + n1 + n2,又知道n0 = n2 +1,證實方式在數據結構與算法-二叉樹性質。那麼n = 2*n2 + n1 +1。
  • n1若是爲0
那麼最後一個非葉子節點的下標爲n2 - 1,即(n - n1 - 1)/2 - 1= n/2 - 3/2,因爲n = 2*n2 + 1是奇數,因此n/2 - 1向下取整等於n/2 - 3/2。
  • n1若是爲1
那麼最後一個非葉子節點的下標爲n2,即(n - n1 -1)/2 = n/2 - 1。
對於徹底二叉樹來說,n1要麼爲0要麼爲1。所以,綜上所述,最後一個非葉子節點的下標爲n/2 - 1。
既然找到了最後一個非葉子節點P,那就從P節點開始自下向上,自右向左的調整每個遇到的子樹爲堆,最終整棵樹成爲堆。
假設如今存在數組data = [39 50 43 15 28 40 5 25 10 23 30 20 55],展開以後是這樣的:
按照自下向上的算法調整是這樣的:
該算法的時間複雜度是O(n),一般狀況下優於自上向下建立堆的方式。
  • 添加
在堆中插入一個新的元素,一般會放入到數組的末尾,若是插入到頭部或者中間某個位置,有兩個缺陷。第一,會移動數組裏大量數據,第二,嚴重破壞堆結構,只能依靠自下向上的方式調整全部子樹,才能保持整棵樹的堆結構,得不償失。
若是將新元素插入到數組末尾,僅僅有可能破壞少許子樹的堆結構,也能在短期內調整完畢。假若有如下堆結構:


在末尾插入60節點,調整方式以下:

能夠發現,不管是自上向下建立堆仍是自下向上調整堆,又或者在堆中插入新的元素,核心邏輯老是調整堆的過程。關鍵在於,二叉樹中每一個節點老是有兩個身份,既是某顆子樹(P子樹)的根節點,也是其父節點所在樹(Q子樹)的子節點。調整堆的過程其實就是既要保證P子樹是堆,也要保證Q子樹是堆。算法

  • 刪除
在堆中刪除某個元素,一般咱們會刪除堆的根節點,由於它是有價值的,要麼是最大值要麼是最小值。爲了不刪除頭結點以後數組元素大量移動,前人想出一個巧妙的方式。那就是將最後一個葉子節點和根結點進行交換。最後一個葉子節點成了根節點,根節點成了最後一個葉子節點。這時,咱們刪除元素沒必要移動數組數據,可是破壞了堆結構,怎麼辦呢?老樣子,調整堆,調整方式和上面介紹的方法一致,就很少說了。
咱們固然能夠刪除數組中任一元素,方法和刪除根節點是一致的。可是,咱們每每不會這麼作,由於在堆中除了根節點,其餘元素沒有特別的地方,價值不大。
  • 運用
咱們已經瞭解堆的建立、添加、刪除,那它有什麼用處呢?
答案是優先隊列。對於堆來講,新元素老是插入到數組末尾,被刪除元素老是根節點,而且要麼是最大的要麼是最小的,這不就是優先隊列嗎?在數據結構與算法-棧與隊列中,咱們推薦使用鏈表來實現隊列,這對於普通的隊列是能夠的,由於普通隊列保持的是數據插入時的次序。可是對於優先隊列就不合適了,數據插入到優先隊列以後,須要依靠優先級排序,從隊列彈出的數據老是優先級最大(最小)的元素。若是使用鏈表來實現優先隊列,操做複雜度是O(n),可是若是使用數組,將數據按照堆結構排列,那麼操做複雜度僅僅有O(lgn),這是很是吸引人的。而且,標準庫中的優先隊列就是以向量實現的。
堆的另一個運用是對數據進行排序,稱之爲堆排序。
算法邏輯很簡單,就是不停的刪除根節點、調整堆的過程。那麼,數據就會以從大到小或者從小到大的順序從堆中刪除。堆排序的操做複雜度有O(nlgn),算不上很優秀,但至少比冒泡、插入這些算法要好用。
堆的內容到這裏已經探討完畢,更多內容就須要在實踐中摸索了。

數據結構與算法-kd二叉樹(基礎)
數組

相關文章
相關標籤/搜索