1. 程式人生 > >最大堆(二)

最大堆(二)

背景知識:

  1. 堆是一棵完全二叉樹,什麼是完全二叉樹?就是除了最後一層節點之外,其他層的節點個數必須是最大值,並且最後一層的節點必須都集中在左側。
  2. 堆分為最大堆和最小堆。最大堆就是父節點大於等於子節點,從而導致根節點是最大值。最小堆就是父節點小於等於子節點,從而導致根節點是最小值。本文基於最大堆講解,並且提供python程式碼實現。

1.在第一節中我們詳細講解了什麼是最大堆,以及基於列表這種資料結構的最大堆的python實現方案,聰明的小夥伴可能已經發現在第一節中,我們將原列表資料變成堆的過程是循壞執行了一個叫做Shift Up的方法,那這麼做效率是不是不高呢?答案是肯定,為此我們對原來的堆類進行優化,使用Heapify演算法

2.首先簡單介紹一下Heapify演算法:一個最大堆,從上而下從左到右,依次從1開始標序直到最後一個節點,這時候你可以發現一個規律:從下往上,第一個非葉子節點的序號是節點總個數除以二取整。Heapify演算法就是以這個規律為基礎,對於原列表構建堆的過程不在是迴圈執行Shift Up,而是直接忽略葉子節點,從第一個非葉子節點開始,向上追溯,把每一部分當作是一棵完全二叉樹執行Shift Down來構建最大堆,直至追溯到根節點,那麼整棵完全二叉樹就是一個最大堆了。具體的圖解如下:

3.經過了第二步的分析,相信讀者對於Heapify演算法已經很清楚了,下面提供python的具體實現:

import random
# 使用heapify演算法優化(heapify演算法其實就是隻考慮非葉子節點,並向上追溯將每一部分看成一個完全二叉樹並執行shiftDown變成最大堆,
# 直至向上追溯到下標為1,那麼整個完全二叉樹就是一個最大堆了。
class maxStack1:
    def __init__(self, list, n):
        self.data = [0]
        for i in range(n):
            self.data.append(list[i])
        self.count = n
        # heapify演算法
        # self.count//2得到的就是第一個非葉子節點的下標,-1表示向上追溯
        for key in range(self.count // 2, 0, -1):
            self.__shiftDown(key)


    # 首先比較當前節點的兩個子節點的大小,如果大的那個子節點比父元素大就交換位置
    def __shiftDown(self, key):
        # 在完全二叉樹中,只要保證有左節點那麼這個節點就是有孩子的,因為完全二叉樹並不存在有右節點而沒有左節點
        while 2 * key <= self.count:
            # 預設change指向左節點
            change = 2 * key
            # 如果存在右節點並且右節點比左節點大,那麼將change加1,此時change指向了右節點
            if change + 1 <= self.count and self.data[change + 1] > self.data[change]:
                change += 1
            # 經過上面兩步,此時change指向的值是key父節點最大的孩子節點
            if self.data[change] <= self.data[key]:
                break
            self.data[change], self.data[key] = self.data[key], self.data[change]
            key = change


if __name__ == '__main__':
    n=10
    list=[random.randint(0,n) for i in range(n)]
    print(list)
    maxStack = maxStack1(list,n)
    print(maxStack.data)

4.堆排序的時間消耗主要在初始堆的構建和反覆重建堆這兩部分上,第一節中我們使用迴圈執行Shift Up來構建初始堆,時間複雜度是O(NlogM),經過Heapify演算法優化之後,構建初始堆的時間複雜度變為了O(N)