數據結構:堆python實現與堆排序

一、堆的定義

堆是一種完全二叉樹,有最大堆和最小堆兩種。

  • 最大堆: 對於每個非葉子節點 V,V 的值都比它的兩個孩子大,稱爲 最大堆特性(heap order property) 最大堆裏的根總是存儲最大值,最小的值存儲在葉節點。
  • 最小堆:和最大堆相反,每個非葉子節點 V,V 的兩個孩子的值都比它大。

二、python實現

  在我們的堆實現中,我們通過創建一個 完整二叉樹 來保持樹平衡。 一個完整的二叉樹是一個樹,其中每個層都有其所有的節點,除了樹的最底層,從左到右填充。下圖展示了完整二叉樹的示例。

6.10.äºåå å®ç°.figure1

完整二叉樹的另一個有趣的屬性是,我們可以使用單個列表來表示它。 因爲樹是完整的,父節點的左子節點(在位置 p 處)是在列表中位置 2p 中找到的節點。 類似地,父節點的右子節點在列表中的位置 2p + 1。爲了找到樹中任意節點的父節點,我們可以簡單地使用Python 的整數除法。 假定節點在列表中的位置 n,則父節點在位置 n/2。 Figure 2 展示了一個完整的二叉樹,並給出了樹的列表表示。 請注意父級和子級之間是 2p 和 2p+1 關係。 樹的列表表示以及完整的結構屬性允許我們僅使用幾個簡單的數學運算來高效地遍歷一個完整的二叉樹。 我們將看到,這也是我們的二叉堆的有效實現。 

二叉堆實現的基本操作如下:

  • BinaryHeap() 創建一個新的,空的二叉堆。
  • insert(k) 向堆添加一個新項。
  • findMin() 返回具有最小鍵值的項,並將項留在堆中。
  • delMin() 返回具有最小鍵值的項,從堆中刪除該項。
  • 如果堆是空的,isEmpty() 返回 true,否則返回 false。
  • size() 返回堆中的項數。
  • buildHeap(list) 從鍵列表構建一個新的堆。

 我們將開始實現一個二叉堆的構造函數。由於整個二叉堆可以由單個列表表示,所以構造函數將初始化列表和一個 currentSize 屬性來跟蹤堆的當前大小。  你會注意到,一個空的二叉堆有一個單一的零作爲 heapList 的第一個元素,這個零隻是放那裏,用於以後簡單的整數除法。

class BinHeap:
    def __init__(self):
        self.heapList = [0]
        self.currentSize = 0

 我們將實現的下一個方法是 insert 。 將項添加到列表中最簡單,最有效的方法是將項附加到列表的末尾。 它維護完整的樹屬性。但可能違反堆結構屬性。可以編寫一個方法,通過比較新添加的項與其父項,我們可以重新獲得堆結構屬性。 如果新添加的項小於其父項,則我們可以將項與其父項交換。 Figure 2 展示了將新添加的項替換到其在樹中的適當位置所需的操作

def percUp(self,i):
    while i // 2 > 0:
      if self.heapList[i] < self.heapList[i // 2]:
         tmp = self.heapList[i // 2]
         self.heapList[i // 2] = self.heapList[i]
         self.heapList[i] = tmp
      i = i // 2
def insert(self,k):
    self.heapList.append(k)
    self.currentSize = self.currentSize + 1
    self.percUp(self.currentSize)

我們現在可以看 delMin 方法。 因爲堆屬性要求樹的根是樹中的最小項,所以找到最小項很容易。delMin 的難點在根被刪除後恢復堆結構和堆順序屬性。 我們可以分兩步恢復我們的堆。(輸出堆頂元素之後,調整剩餘元素稱爲一個新堆)首先,我們將通過獲取列表中的最後一個項並將其移動到根位置來恢復根項,保持我們的堆結構屬性。 但是,我們可能已經破壞了我們的二叉堆的堆順序屬性。 第二,我們通過將新的根節點沿着樹向下推到其正確位置來恢復堆順序屬性。 Figure 3展示了將新的根節點移動到堆中的正確位置所需的交換序列。 

6.10.äºåå å®ç°.figure3

def percDown(self,i):
    while (i * 2) <= self.currentSize:
        mc = self.minChild(i)
        if self.heapList[i] > self.heapList[mc]:
            tmp = self.heapList[i]
            self.heapList[i] = self.heapList[mc]
            self.heapList[mc] = tmp
        i = mc

def minChild(self,i):
    if i * 2 + 1 > self.currentSize:
        return i * 2
    else:
        if self.heapList[i*2] < self.heapList[i*2+1]:
            return i * 2
        else:
            return i * 2 + 1

 delmin 操作的代碼在 如下所示。注意,有難度的工作由輔助函數處理,在這種情況下是 percDown 

def delMin(self):
    retval = self.heapList[1]
    self.heapList[1] = self.heapList[self.currentSize]
    self.currentSize = self.currentSize - 1
    self.heapList.pop()
    self.percDown(1)
    return retval

 爲了完成我們對二叉堆的討論,我們將看從一個列表構建整個堆的方法。如果我們從整個列表開始,那麼我們可以在 O(n)操作中構建整個堆。

def buildHeap(self,alist):
    i = len(alist) // 2
    self.currentSize = len(alist)
    self.heapList = [0] + alist[:]
    while (i > 0):
        self.percDown(i)
        i = i - 1

6.10.äºåå å®ç°.figure4

三、堆排序 

實現堆排序需要解決兩個問題:

  1. 如何由一個無序序列建成一個堆?
  2. 如何在輸出堆頂元素之後,調整剩餘元素成爲一個新的堆?

第二個問題對應delMin方法,輸出堆頂元素,用堆中最後一個元素代替。自堆頂至葉子調整,這個過程稱爲篩選。(percDown和minChild)

從一個無序序列建堆的過程就是一個反覆篩選的過程,將此序列看成是一個完全二叉樹,則最後一個非終端結點是第\left \lfloor n/2 \right \rfloor個元素。

堆是一種近似完全二叉樹的數據結構,它可以不是完全二叉樹,比如下面這個大根堆。只不過,我們一般喜歡把它構造成完全二叉樹 ,主要是爲了方便用數組來存儲和計算(根據雙親結點來算孩子,和根據孩子算雙親都很方便)。

 

 完整二叉堆代碼github地址:

https://github.com/makang101/python-data-structure/tree/master/Heap

參考:

數據結構,嚴蔚敏

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章