本篇文章将简要解释什么是二叉堆以及二叉堆的Python实现
什么是二叉堆(binary heap)
二叉堆是一种满足特殊性质的二叉树。具有以下性质:
- 二叉堆是一颗完全二叉树(除叶子节点以外其余节点的左右孩子都不为空)
- 所有节点的值都大于等于(最大堆)孩子节点的值
最小堆刚好相反
二叉堆在排序和求取top N 等问题上都有广泛的应用
实现思路(以最大堆为例)
因为堆是一颗完全二叉树,所以采用 列表 + shift_up + shift_down 的经典方式实现堆会很容易
用列表实现堆可以方便的发现:
- 一个节点 p
索引为pi
的左孩子索引 li = pi * 2 + 1 - 一个节点 p
索引为pi
的右孩子索引 ri = pi * 2 + 2 - 一个节点 p
索引为pi
的父亲索引 parent_i = (pi - 1) // 2 (向下取整)
所以我们可以很方便的封装出获取一个节点的左右孩子和父亲节点的方法
class MaxHeap(object):
def __init__(self):
self.data = []
def __left_child(self, index):
return index * 2 + 1
def __right_child(self, index):
return index * 2 + 2
def __parent(self, index):
if index == 0:
raise Exception('堆顶没有父亲节点')
else:
return (index - 1) // 2
通过上浮操作来向堆中新增元素
上浮操作就是指每次新增元素的时候将新增元素放入数组末尾,之后通过索引获取新增节点的父亲节点的索引
,如果新增节点的数值比父亲节点的数值大,就将新增节点的值和父亲节点进行交换,即是上浮,不断上浮直到到堆顶整个新增操作完成,封装上浮操作如下
class MaxHeap(object):
......
def __shift_up(self, index):
while index > 0 and self.data[index] > self.data[self.__parent(index)]:
self.data[index], self.data[self.__parent(index)] = self.data[self.__parent(index)], self.data[index]
index = self.__parent(index)
通过下沉操作来取出堆中元素
下沉操作就是为了保证取出堆顶元素之后,其余元素可以组成新的最大堆:
- 将堆顶元素和堆尾元素进行互换,弹出堆尾元素作为返回
- 从堆顶开始 和其左右孩子中较大的孩子进行比较,如果比左右孩子中较大的孩子要小,就与左右孩子中较大的孩子进行互换不断下沉,直到比左右孩子中较大的元素要大的时候(堆的第二个性质),新的最大堆就形成了,下沉操作完成
这里注意判断一个节点是否有左右孩子,防止数组越界
class MaxHeap(object):
......
def __shift_down(self, index):
# 先判断是否有左孩子
while self.__left_child(index) < len(self.data):
li = self.__left_child(index)
lr = self.__right_child(index)
# 再判断是否有右孩子
if lr < len(self.data) and self.data[li] < self.data[lr]:
j = lr
else:
j = li
if self.data[index] < self.data[j]:
self.data[index], self.data[j] = self.data[j], self.data[index]
index = j
else:
break
数据结构设计&封装
- def add(word) // return None 向Trie中添加一个单词
- def is_empty() // return bool 询问字典树是否包含该前缀的单词
- def size() // ruturn int 返回字典树存储单词个数 使用__len__()代替
- def extract_max() // return e 弹出堆顶元素
- def find_max() // return e 查看堆顶元素
- def heapify(arr) // return None 将数组序列化为堆
- def replace(e) // return e 替换堆顶元素
为了保证Python语法风格,使用魔法函数实现部分功能
实现
class MaxHeap(object):
def __init__(self, heapify = None):
if heapify:
self.heapify(heapify)
else:
self.data = []
def __len__(self):
return len(self.data)
def is_empty(self):
return len(self.data) == 0
def __left_child(self, index):
return index * 2 + 1
def __right_child(self, index):
return index * 2 + 2
def __parent(self, index):
if index == 0:
raise Exception('堆顶没有父亲节点')
else:
return (index - 1) // 2
def add(self, e):
self.data.append(e)
self.__shift_up(len(self.data) - 1)
def __shift_up(self, index):
while index > 0 and self.data[index] > self.data[self.__parent(index)]:
self.data[index], self.data[self.__parent(index)] = self.data[self.__parent(index)], self.data[index]
index = self.__parent(index)
def extract_max(self):
if self.is_empty():
self.data[0], self.data[-1] = self.data[-1], self.data[0]
res = self.data.pop()
self.__shift_down(0)
return res
else:
raise Exception('堆为空')
def find_max(self):
if self.is_empty():
raise Exception('堆为空')
return self.data[0]
def __shift_down(self, index):
while self.__left_child(index) < len(self.data):
li = self.__left_child(index)
lr = self.__right_child(index)
if lr < len(self.data) and self.data[li] < self.data[lr]:
j = lr
else:
j = li
if self.data[index] < self.data[j]:
self.data[index], self.data[j] = self.data[j], self.data[index]
index = j
else:
break
def replace(self, e):
res = self.data[0]
self.data[0] = e
self.__shift_down(0)
return res
def heapify(self, arr):
self.data = arr
pi = self.__parent(len(arr) - 1)
while pi >= 0:
self.__shift_down(pi)
pi -= 1
转载注明出处