数据结构:堆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

参考:

数据结构,严蔚敏

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