二叉堆的插入,删除和构建
二叉堆的本质是一个完全二叉树,它分为最大堆,和最小堆(所以学好二叉堆就要学好二叉树的知识点,感觉这些数据结构都是依次递进,有点进化的味道)
二叉堆的插入
插入节点:
也叫上浮
上浮就是孩子节点不断与父节点之间进行比较
最小堆的化,
孩子节点比父节点小,上浮;
最大堆的化,
孩子节点比父节点大,上浮;
其他情况那就不动;当然堆的插入肯定是已经构建好的堆
插入的位置:
就是插入位置是完全二叉树的最后一个位置,也就是最新的一个叶子节点(叶子节点插入从左到右))
def up_adjust(array=[]):
child_index = len(array) - 1 # 左孩子节点
parent_index = (child_index - 1) // 2 # 父节点
# temp保存插入的叶子节点值,用于最后的赋值
temp = array[child_index]
i = 0
while child_index > 0 and temp < array[parent_index]: #孩子节点索引大于0和孩子节点的值小于父节点的值
# 无需真正交换,单向赋值即可
array[child_index] = array[parent_index]
child_index = parent_index
parent_index = (parent_index - 1) // 2 # 在继续寻找父节点的父节点
i += 1 # while就可以用,函数就不能
array[child_index] = temp #把原来孩子节点的值赋值给孩子节点上浮索引的位置
print('上浮了多少%d次'%i)
代码优化的点:
这里的无需真正交换,单向赋值意思是:这里实际变化的是孩子节点的值,而最终退出循环的那个孩子节点的值又被最开始插入的值赋值。并没改变相对于那个孩子节点父节点的值。这样其实降低了时间复杂度和空间复杂度。
二叉堆的下沉
二叉堆的节点下沉操作;
下沉是父节点与左右孩子节点中较小的那个互换;可先假定左孩子小设定左孩子索引为child_index,
在再假设中假设右孩子比左孩子小,成立的话就执行child_index+1操作.不成立则没有操作
接着判断父节点小于任何一个孩子的值,直接跳出
接着进行父节点与最小的孩子节点之间的互换
这里要做的删除要删除的节点,然后把最后一个节点的数据放在该删除的节点位置,然后进行下沉操作
所以这里仅仅是下沉,并没有删除节点的操作
def down_adjust(parent_index, length, array=[]):
# temp保存父节点值,用于最后的赋值
temp = array[parent_index]
child_index = 2 * parent_index + 1
i = 1
while child_index < length: # 循环条件是孩子节点索引小于数组长度
# 如果有右孩子,且右孩子小于左孩子的值,则定位到右孩子
# 没有的话,下面这条if语句就不执行
if child_index + 1 < length and array[child_index + 1] < array[child_index]:
child_index += 1
# 如果父节点小于任何一个孩子的值,直接跳出
if temp <= array[child_index]:
break
# 无需真正交换,单向赋值即可
array[parent_index] = array[child_index] # 父节点的值变为孩子节点的值,这里并没有改变孩子节点的值
parent_index = child_index # 父节点的索引变为孩子节点,这一步才是最重要的,但他省略了孩子节点的值变为父节点的值
child_index = 2 * child_index + 1 # 孩子节点再找出孩子节点
i+=1
array[parent_index] = temp #把父节点赋值给最后定位到的孩子节点
print('下沉了多少%d次'%i)
二叉堆的删除
二叉堆的删除是建立再二叉堆的下称之上的,只是下沉的父节点变为了那个最后一个叶子节点
删除操作的进行:
这里要做的是把要删除的节点删除,然后把最后一个节点的数据放在该删除的节点位置,然后进行下沉操作
下沉操作的临时变量temp也是该值,然后下沉
def down_adjust1(parent_index, length, array=[]):
temp = array[length-1]
# 这里要删除最后一个元素,并把最后一个元素的值赋值给要堆顶
array[parent_index]=array[length-1]
array.pop()
child_index = 2 * parent_index + 1
i = 1
while child_index < length: # 循环条件是孩子节点索引小于数组长度
# 如果有右孩子,且右孩子小于左孩子的值,则定位到右孩子
# 没有的话,下面这条if语句就不执行
if child_index + 1 < length and array[child_index + 1] < array[child_index]:
child_index += 1
# 如果父节点小于任何一个孩子的值,直接跳出
if temp <= array[child_index]:
break
# 无需真正交换,单向赋值即可;孩子节点索引变为父节点,父节点的值自然变了
array[parent_index] = array[child_index] # 父节点的值变为孩子节点的值,这里并没有改变孩子节点的值
parent_index = child_index # 父节点的索引变为孩子节点,这一步才是最重要的,但他省略了孩子节点的值变为父节点的值
child_index = 2 * child_index + 1 # 孩子节点再找出孩子节点
i+=1
array[parent_index] = temp #把父节点赋值给最后定位到的孩子节点
print('下沉了多少%d次'%i)
二叉堆的构建
构建二叉堆就是把一个无序的完全二叉树调整为二叉堆,本质就是让所有非叶子节点依次下称
首先找到最后一个非叶子节点len(array)-2) // 2,然后按照顺序把其他非叶子节点依次下沉
def build_heap(array=[]):
"""
二叉堆的构建操作
:param array: 原数组
"""
# 从最后一个非叶子节点开始,依次下沉调整
for i in range((len(array)-2) // 2, -1, -1):
down_adjust(i, len(array), array) # 这里调用下沉操作
二叉堆索引的技巧
二叉堆虽然是完全二叉树,但它的存储方式并不是链式存储,而是顺序存储,也就是二叉堆的所有节点都存储在数组中
这里假设父节点的下标是p,那么它左孩子节点就是2p+1.右孩子节点就是2p+2(节点均指索引)
同理假设左孩子节点是c,那么它的父节点是(c-1)//2
还有获取一个列表中所有父节点的索引
len(array)-2) // 2