二叉堆(Binary Heap)
二叉堆(Binary Heap)是一种节点数据项有序的完全二叉树结构。二叉堆中,子节点数据项必须小于(或大于)其父节点的数据项,这被称为:堆次序(Heap Order)。所以二叉堆中任何一条(从根节点到叶节点的)路径均是一个已排序数组,若堆数据项中的最小值位于根节点,则称为最小堆(min heap)或小根堆;若堆数据项中的最大值位于根节点,则称为最大堆(max heap)或大根堆。
二叉堆(Binary Heap)定义的操作如下:
- BinaryHeap():创建一个空二叉堆对象;
- insert(k):将新key加入到堆中;
- findMin():返回堆中的最小项,最小项仍保留在堆中;
- delMin():返回堆中的最小项,同时从堆中删除;
- isEmpty():返回堆是否为空;
- size():返回堆中key的个数;
- buildHeap(list):从一个key列表创建新堆
代码实现
-
BinaryHeap()方法,二叉堆初始化中采用一个列表来保存堆数据,其中表首下标为0的项无用,但为了后面代码可以用到简单的整数乘除法,仍保留它。
-
insert(key)方法,在列表末尾添加新key后,虽然对其它路径的次序没有影响,但对于其到根的路径可能破坏次序;所以需要将新key沿着路径来“上浮”到其正确位置;注意:新key的“上浮”不会影响其它路径节点的“堆”次序。其时间复杂度是 O(log n)。
-
delMin()方法,移走整个堆中最小的key:根节点heapList[1],为了保持“完全二叉树”的性质,先用最后一个节点来代替根节点;然后再将新的根节点沿着一条路径“下沉”,直到比两个子节点都小。“下沉”路径的选择:如果比子节点大,那么选择较小的子节点交换下沉。其时间复杂度是 O(log n)。
-
buildHeap(lst)方法:从无序表生成“堆”,我们最自然的想法是:用insert(key)方法,将无序表中的数据项逐个insert到堆中,但这么做的总代价是O(nlog n);其实,用“下沉”法,能够将总代价控制在O(n)。
# 二叉堆
class BinaryHeap(object):
""" 小根堆
BinaryHeap():创建一个空二叉堆对象;
insert(k):将新key加入到堆中;
findMin():返回堆中的最小项,最小项仍保留在堆中;
delMin():返回堆中的最小项,同时从堆中删除;
isEmpty():返回堆是否为空;
size():返回堆中key的个数;
buildHeap(list):从一个key列表创建新堆
"""
def __init__(self):
"""
二叉堆初始化中采用一个列表来保存堆数据,其中表首下标为0的项无用,
但为了后面代码可以用到简单的整数乘除法,仍保留它。
"""
self.heap_list = [None]
def findMin(self):
return self.heap_list[1]
def isEmpty(self):
return self.heap_list == [None]
def size(self):
return len(self.heap_list) - 1
def insert(self, key):
""" 时间复杂度是 O(log n) """
# 尾追新节点
self.heap_list.append(key)
# 上浮
self.perUp(self.size())
return self.heap_list[1:]
def delMin(self):
""" 时间复杂度是 O(log n) """
# 验空
if self.isEmpty():
raise ValueError('Heap is empty now!')
# 移除最小项
self.heap_list[1], self.heap_list[-1] = self.heap_list[-1], self.heap_list[1]
temp = self.heap_list.pop()
# 下沉
self.perDown(1, self.size())
return temp, self.heap_list[1:]
def buildHeap(self, alist):
self.heap_list = [None] + alist
size = self.size()
index = size // 2
while index > 0:
self.perDown(index, size)
index -= 1
return self.heap_list[1:]
def perUp(self, index):
while index // 2 > 0:
if self.heap_list[index // 2] > self.heap_list[index]:
self.heap_list[index // 2], self.heap_list[index] = self.heap_list[index], self.heap_list[index // 2]
index = index // 2
else:
break
def perDown(self, index, size):
while 2 * index <= size: # 是否有左子树?
if 2 * index + 1 <= size: # 是否有右子树?
if self.heap_list[2 * index] <= self.heap_list[2 * index + 1]: # 左右子树孰小?
if self.heap_list[index] <= self.heap_list[2 * index]: # 本节点与最小子节点孰小?
break
else:
self.heap_list[index], self.heap_list[2 * index] = self.heap_list[2 * index], self.heap_list[index]
index = index * 2
else:
if self.heap_list[index] <= self.heap_list[2 * index + 1]:
break
else:
self.heap_list[index], self.heap_list[2 * index + 1] = self.heap_list[2 * index + 1], self.heap_list[index]
index = index * 2 + 1
else:
if self.heap_list[index] <= self.heap_list[2 * index]:
break
else:
self.heap_list[index], self.heap_list[2 * index] = self.heap_list[2 * index], self.heap_list[index]
index = index * 2
# test
import numpy as np
heap = BinaryHeap()
heap.buildHeap(list(np.random.randint(1, 30, 10)))[1:]
heap.insert(0)
heap.delMin()
二叉堆的两个经典应用是实现:优先队列(详见作者文章:数据结构(Python版):线性表)和堆排序算法(详见作者文章:常用算法:排序)