什么是堆
堆是一种数据结构,它可以看做一棵完全二叉树。但是它又是被存储成数组形式的。堆又分为最大堆和最小堆。
最大堆:一棵完全二叉树的任意一个节点都大于等于它的左右子节点(如果有的话)
最小堆:一棵完全二叉树的任意一个节点都小于等于它的左右子节点(如果有的话)
我们以最小堆为例。
如图:
上图是一棵完全二叉树,同时也是最小堆。它的任意节点都小于等于它的子节点。如果用数组表示的话,就用层次遍历的方式,表示为[5, 9, 11, 14, 18, 19, 21, 33, 17, 27]。
因为是一棵完全二叉树,所以堆有一些特性。
1.最小(大)堆的根节点(第一个元素)是整棵数最小(大)的。
2.除根节点外,节点k的父节点索引是k/2,左右子节点(如果有的话)为2k和2k+1。注:索引从1开始,而非0
所以堆也是一种优先队列,我理解的优先队列,就是它不是一个完全有序的队列,但是又有一定的顺序,例如最大(小)能一下找到。所以是一种有着优先级别的队列。
堆的实现(以最小堆为例,最大堆同理):
1.插入(insert)
插入的过程是,先将新的节点放在最末尾,然后依次与它的父节点比较,如果比父节点小,那么就交换位置。一直到比它的父节点大为止。这个过程称为上浮。
上图的过程:
1.原数组为[5, 9, 11, 14, 18, 19, 21, 33, 17, 27],插入新的元素[5, 9, 11, 14, 18, 19, 21, 33, 17, 27,7]
2.比较7和它的父节点18, 7<18,交换它们的位置。[5, 9, 11, 14, 7, 19, 21, 33, 17, 27,18]
3.再比较7和它的父节点9. 7<9,交换。[5, 7, 11, 14, 9, 19, 21, 33, 17, 27,18]
4.再比较7和它的父节点5. 7>5, 停止交换,7已经到了正确的位置。所以最终的树为[5, 7, 11, 14, 9, 19, 21, 33, 17, 27,18]
用代码实现:
class BinHeap:
def __init__(self):
self.heapList = [0]
self.currentSize = 0
def percUp(self, index): #实现上浮
while index // 2 > 0 and self.heapList[index//2] > self.heapList[index]:
self.heapList[index//2], self.heapList[index] = self.heapList[index], self.heapList[index//2]
index = index // 2
def insert(self, k):
self.heapList.append(k)
self.currentSize += 1
self.percUp(self.currentSize)
2.删除(delMin)
堆的删除一般都是删除根节点。具体实现是,先把根节点(最小值)删除,然后把列表的最后一项移到根节点的位置。这样还是一棵完整的二叉树。接下来调节这个新的根节点的位置。与它的左右子节点比大小。这个过程叫做下沉
如上图过程:
1. 删除根节点5,然后把末尾的节点27移到根节点。由[5, 9, 11, 14, 18, 19, 21, 33, 17, 27]变成[27, 9, 11, 14, 18, 19, 21, 33, 17]
2. 根节点27,和左右节点9,11相比,最小的是9,所以27与9交换位置。变为[9, 27, 11, 14, 18, 19, 21, 33, 17]
3. 27和它的左右节点14,18相比,最小的是14,所以27和14交换。变为[9, 14, 11, 27, 18, 19, 21, 33, 17]
4. 27和它的左右节点17,33相比,最小的是17,所以27和17交换。变为[9, 14,11, 17, 18, 19, 21, 33, 27]
5. 27没有子节点了。到达了最终位置。
实现代码:
def minChild(self, i):
if i * 2 + 1 > self.currentSize:
return 2 * i
return 2 * i if (self.heapList[2*i] < self.heapList[2*i + 1]) else 2*i+1
def percDown(self, index):
while index * 2 <= self.currentSize:
mc = self.minChild(index)
if self.heapList[index] > self.heapList[mc]:
self.heapList[index], self.heapList[mc] = self.heapList[mc], self.heapList[index]
index = mc
def delMin(self):
self.heapList[1] = self.heapList[-1]
self.heapList.pop()
self.currentSize -= 1
self.percDown(1)
3.构建堆(buildHeap)
这个函数的含义是,传入一个列表,将它变成一个最小堆。我们可以一个一个地插入(insert),但是那样太低效了。还有一种比较高效的方式如下:
先将整个列表传入self.heapList。变成一棵树,然后开始调节树中节点位置。假设根节点的位置是k,也就是说k的父节点是k//2。同理,最后一个有子节点的节点就是k//2。这个过程有点绕。下面用一个例子来解释,如图:
我用红色标注的是它们的编号,最末的节点编号是5,它的父节点是2。也就是说2以后的节点都是叶节点。我们调整节点位置的时候,以父节点+两个子节点为整体。因为在堆中,节点的关系都是根据父节点来定义的。
1.找到最末节点位置k,它的父节点是i = k//2。这个时候我们开始以下沉的方式调节它的父节点。
2.然后依次调节i-1、i-2….1。这样就能保证所有的父节点都在正确的位置上了。
实现代码:
def buildHeap(self, alist):
self.currentSize = len(alist)
self.heapList = [0] + alist
i = self.currentSize // 2
while i > 0:
self.percDown(i)
i -= 1
总代码:
class BinHeap: #定义堆
def __init__(self):
self.heapList = [0] #堆有两个属性,堆的元素和长度,其中先把位置0用0填充。这样就能使堆从1开始索引
self.currentSize = 0
def percUp(self, index): #实现堆的上浮,目的是一直跟自身的父节点比较。
while index // 2 > 0 and self.heapList[index//2] > self.heapList[index]:
self.heapList[index//2], self.heapList[index] = self.heapList[index], self.heapList[index//2]
index = index // 2
def insert(self, k): #借助上浮实现插入
self.heapList.append(k)
self.currentSize += 1
self.percUp(self.currentSize)
def minChild(self, i): #查找子节点中最小的那个
if i * 2 + 1 > self.currentSize:
return 2 * i
return 2*i if (self.heapList[2*i] < self.heapList[2*i + 1]) else 2*i+1
def percDown(self, index): #实现下沉
while index * 2 <= self.currentSize:
mc = self.minChild(index)
if self.heapList[index] > self.heapList[mc]:
self.heapList[index], self.heapList[mc] = self.heapList[mc], self.heapList[index]
index = mc
def delMin(self): #删除最小的元素
res = self.heapList[1]
self.heapList[1] = self.heapList[-1]
self.heapList.pop()
self.currentSize -= 1
self.percDown(1)
return res
def buildHeap(self, alist): #由列表创建二叉堆
self.currentSize = len(alist)
self.heapList = [0] + alist
i = self.currentSize // 2
while i > 0:
self.percDown(i)
i -= 1