堆(数据结构)
什么是堆
堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵完全二叉树的数组对象
堆的性质
这种用数组实现的二叉树,假设节点的索引值为index,那么:
节点的左孩子节点是 2*index+1,
节点的右孩子节点是 2*index+2,
左孩子节点的父节点是 (index-1) / 2。
右孩子结点的父结点是 (index-2) / 2
最大堆中,父结点的值比每一个子节点的值都要大
最小堆中,父结点的值比每一个子节点的值都要小
堆的插入,取值
堆是一种数据结构,分为最小堆和最大堆,可以用二叉树来表示。
在二叉树的任意的一个三角结构中(一个父节点,两个子节点),需要满足以下两个条件:
1、父节点要是最小的,就是最小堆(或最大的,就是最大堆),两个子节点之间没有要求
2、数据插入的顺序是一层一层的,只有上一层存满,才会有下一层
一、插入(insert)
假设我们有一个原始的最小堆如下:
插入操作:
当插入一个新值时,首先将值放到树的最后的位置,如下图所示。
然后将这个值与父级元素比较,如果不满足规则1,则与父元素替换(如下图所示)。
第一步
第二步
第三步
二、取值操作
取最小值操作:
在最小堆中,拿出一个最小值,当然就是拿出第一个数啦~不过拿完以后树不就没有“头”了?
不用担心,我们可以把最后一个数放到头的位置,这样树的结构就不会改变,而且操作简单(因为是最后一个数)并且不会改变完全二叉树结构。
当然,因为是最后一个数,必然会出现不满足条件1的情况,所以我们需要把新的树头与子元素比较替换,下面是图片演示:
假设我们有一个原始的最小堆如下所示,接下来我们要取最小值:
不过交换完很可能是不满足条件1的,那么我们就需要比较替换,替换规则是和两个子元素中最小的一个替换
由上面可以明白堆的插入与取值操作,那么我们接下来用代码实现最大堆的操作
最大堆实现操作步骤
首先创建一个堆的类
1、初始化一个空堆,使用数组来存放堆元素,节省存储
2、定义一个查找父结点的方法 get_parent_index 为插入,取值操作做准备
首先判断孩子结点下标:
当孩子结点下标为 0 时,证明此时孩子结点为根节点,根节点没有父结点,返回None
当孩子及诶点大于 当前堆的元素值时,证明此时没有该孩子结点,孩子结点不逊在,那么 该孩子结点没有父结点返回 None
当孩子结点有父结点时,利用位运算符 >> 求出当前孩子结点的父结点下标
3、定义交换两个索引值的方法 swap 方法,利用列表的下标,交换两个数值的位置
4、定义插入结点方法 insert 方法
先把元素放在最后面,然后往前依次堆化 (保证完全二叉树结构)
这里以大顶堆为例,如果插入元素比父结点大,则交换,直到最后
(1) 把新添加的结点插入数组的最后面
(2) 找到新添加(最后一个) 元素的索引
(3) 找到该元素的父结点下标
(4) 定义一个循环,判断当前孩子结点与父结点大小
若当孩子结点的值大于父结点时,不满足最大堆的条件,将孩子结点与父结点交换位置
交换后,子节点上移后,再次判断父结点下标,再次循环判断直到该元素成为堆顶,或小于父结点(对于大顶堆)
5、定义取值 删除堆顶元素 pop 方法
删除堆顶元素,然后将最后一个元素放在堆顶,在从上往下依次堆化,保证完全二叉树结构
(1) 找到堆顶元素
(2) 把最后一个结点放到堆顶
(3) 删除最后一个元素
(4) 从堆顶堆化,利用定义的 heapify 方法
(5) 返回删除的结点元素值
6、定义堆化 heapify 方法
从上往下堆化,从 index 开始堆化操作 (大顶堆)
最大堆代码实现
python
class Heap:def __init__(self):# 初始化一个空堆,使用数组来存放堆元素,节省存储self.data_list = []def get_parent_index(self,child_index : int): # child_index : 孩子结点下标"""返回父结点的下标"""if child_index == 0 or child_index > len(self.data_list) - 1:return Noneelse:return (child_index - 1) >> 1# 如果某个结点的孩子结点空缺,数组对应的位置也空缺# 假设一个父结点的下标是 parent ,左孩子的下标是 2*parent+1 ,右孩子下标是 2*parent+2# 左孩子的下标是 child,父结点的西下表是 (child - 1)/2def swap(self,index_a,index_b):'''交换两个索引对应的值:param index_a: a索引:type index_a: int:param index_b: b索引:type index_b: int:return::rtype:'''self.data_list[index_a],self.data_list[index_b] = self.data_list[index_b],self.data_list[index_a]def insert(self,data : int):"""先把元素放在最后面,然后往前依次堆化 (保证完全二叉树结构)这里以大顶堆为例,如果插入元素比父结点大,则交换,直到最后"""self.data_list.append(data) # 把新添加的结点插入数组的最后面curr_index = len(self.data_list) - 1 # 新添加(最后一个) 元素的索引curr_parent_index = self.get_parent_index(curr_index) # 找到该元素的父结点下标# 循环,直到该元素成为堆顶,或小于父结点(对于大顶堆)while curr_parent_index is not None and self.data_list[curr_parent_index] < self.data_list[curr_index]:self.swap(curr_parent_index,curr_index) # 利用 swap 交换操作curr_index = curr_parent_index # 结点上移curr_parent_index = self.get_parent_index(curr_parent_index) # 再次判断父结点def pop(self):"""删除堆顶元素,然后将最后一个元素放在堆顶,在从上往下依次堆化"""del_data = self.data_list[0] # 找到堆顶元素self.data_list[0] = self.data_list[-1] # 把最后一个结点放到堆顶del self.data_list[-1] # 删除最后一个元素self.heapify(0) # 从堆顶堆化,利用定义的 heapify 方法return del_data # 返回删除的元素def heapify(self,index):"""从上往下堆化,从 index 开始堆化操作 (大顶堆)"""size = len(self.data_list) - 1 # 数组的长度while True:mv_idx = index # mx_idx 代表最大值索引if 2 * index + 1 < size and self.data_list[2*index+1] > self.data_list[mv_idx]:mv_idx = 2 * index + 1 # 如果左孩子结点大于当前最大结点,最大值索引等于左孩子索引if 2 * index + 2 < size and self.data_list[2*index+2] > self.data_list[mv_idx]:mv_idx = 2 * index + 2 # 如果右孩子结点大于当前最大结点,最大值索引等于右孩子索引if mv_idx == index: # 如果当孩子结点与最大值索引相等时,证明此时循环完成,退出循环breakself.swap(mv_idx,index) #交换最大值和当前值index = mv_idx # 当前值等于这一轮的最大值结点if __name__ == '__main__':myheap = Heap()for i in range(10):myheap.insert(i + 1)print("建堆:",myheap.data_list)print("删除堆顶元素:",[myheap.pop() for i in range(10)])