本文是对bilibili视频的一个个人总结,方便后面复习
注意:本文使用的图片均来自于该视频,再次感谢UP主工程部老周
非常建议观看原视频,UP主讲得非常地透彻!
堆必须是一个完全二叉树
进一步的那什么又是完全二叉树呢?
为了完整,本文对满二叉树和完全二叉树进行解释
如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
如果一个二叉树为满二叉树,也可以说深度为k,有2^k+1个节点的二叉树
完全二叉树的定义完全二叉树只允许最后一行不为满
且最后一行必须从左往右排序
最后一行元素之间不可以有间隔
其他定义:
在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点。
举例
1)属于完全二叉树的情况:
2)不属于完全二叉树的情况:
根据堆序性,堆可以被分为两类:
小根堆大根堆每个父节点元素都要小于它的子节点元素
每个父节点元素都要大于它的子节点元素
举例
1)按照层序遍历的顺利来给节点编号
2)把这些编号对应到一个一维数组的下标
3)把树的元素存入相应的下标里
因为堆是一个完全二叉树,所以每个下标和树的每个位置是一一对应的
数组中元素下标和树中节点的对应关系为:
堆的基本 *** 作节点下标为i
左子节点下标为2i+1
右子节点下标为2i+2
堆有两个基本 *** 作(以生成一个大根堆为例):
上滤将子节点与父节点进行比较,如果子节点大于父节点,则将子节点与父节点进行交换
一直交换到小于父节点,或到达树的根节点为止
Python实现:
def heapUp(arr, index):
'''
arr: 一维数组
index: 一个子节点
'''
# 找到输入子节点的父节点
father = (index - 1) // 2
# 如果子节点大于父节点
# 则将父节点与该节点进行交换
if father >= 0 and arr[index] > arr[father]:
arr[index], arr[father] = arr[father], arr[index]
# 交换完后,输入子节点变为父节点
# 它作为上面树的子节点继续上滤
heapUp(arr, father)
下滤
将父节点与其最大子节点进行比较,如果父节点小于其最大子节点,则将父节点与其最大子节点进行交换
一直交换到大于全部子节点,或到达树的叶节点为止
Python实现:
def heapDown(arr, index):
'''
arr: 一维数组
index: 一个父节点
'''
left = 2 * index + 1
right = 2 * index + 2
# 找到父节点、左节点和右节点中最大节点的下标largest
largest = index
if left < len(arr) and arr[left] > arr[largest]:
largest = left
if right < len(arr) and arr[right] > arr[largest]:
largest = right
# 如果最大节点的下标不是父节点
# 则将父节点与最大节点进行交换
if largest != index:
arr[index], arr[largest] = arr[largest], arr[index]
# 交换完后,该父节点变为子节点
# 它作为下面树的父节点继续下滤
heapDown(arr, largest)
两个 *** 作的复杂度都为O(logN)
建堆根据堆的基本 *** 作,我们可以将任意一个一维数组构建为一个大根堆或者小根堆,使用的方式也可以是自顶向下或者自下而上两种方式
本文以自顶向下和自下而上两种方式构建一个大根堆为例
Python实现:
def buildMaxHeap_Up(arr):
N = len(arr)
# 跳过第一元素,因为它没有父节点
for i in range(1, N):
heapUp(arr, i)
print(arr)
arr = [3, 4, 5, 6, 1, 7, 8]
print(arr)
buildMaxHeap_Up(arr)
输入结果:
[3, 4, 5, 6, 1, 7, 8] # 原始输入
[4, 3, 5, 6, 1, 7, 8] # 节点4: 4和3交换
[5, 3, 4, 6, 1, 7, 8] # 节点5: 5和4交换
[6, 5, 4, 3, 1, 7, 8] # 节点6: 6和3交换,再和5交换
[6, 5, 4, 3, 1, 7, 8] # 节点1: 不与任何元素交换
[7, 5, 6, 3, 1, 4, 8] # 节点7: 7和4交换,再和6交换
[8, 5, 7, 3, 1, 4, 6] # 节点8: 8和6交换,再和7交换
这种建堆的复杂度为O(NlogN)
自下而上Python实现:
def buildMaxHeap_Down(arr):
N = len(arr)
# 为什么从(N-1-1)//2开始?
# 我们的目标是找到最后一个元素对应的父节点
# 最后一个元素的下标是N-1
# 节点i的父节点是(i-1) // 2
# 所以最后一个元素对应的父节点为: (N-1-1) // 2
for i in range((N-1-1)//2, -1, -1):
heapDown(arr, i)
print(arr)
arr = [3, 4, 5, 6, 1, 7, 8]
print(arr)
buildMaxHeap_Down(arr)
输入结果:
[3, 4, 5, 6, 1, 7, 8] # 原始输入
[3, 4, 8, 6, 1, 7, 5] # 节点5: 5和8交换
[3, 6, 8, 4, 1, 7, 5] # 节点4: 4和6交换
[8, 6, 7, 4, 1, 3, 5] # 节点3: 3和8交换,再和7交换
这种建堆的复杂度为O(N)
优先队列优先队列有两个 *** 作:
1)插入队列
2)d出最小(大)元素
如果是小根堆,d出根节点就是d出小元素
如果是大根堆,d出根节点就是d出大元素
将新元素添加到数组尾部
再进行上滤 *** 作
上滤的复杂度为O(logN),所以插入的复杂度也是O(logN)
d出最小(大)元素1)先构建一个小(大)根堆,d出根节点即是最小(大)元素
2)将数组最后一个元素添加到根节点
3)再进行下滤 *** 作,将剩余的元素重新构建成小(大)根堆
d出的复杂度为O(logN)
堆排序 d出最小元素可以利用d出最小元素的思想,依次d出所有最小元素即可完成堆排序,但这样 *** 作往往需要一个新空间来存储d出的元素,所以这种方式往往不可取。
大根堆步骤:
1)构建一个大根堆
2)将跟节点,即未排序数组中的最大元素,与未排序数组中的最后一个元素进行交换
3)将交换后的根节点进行下滤 *** 作,即构建新的大根堆
4)重复2,3步骤直到遍历完整个数组
Python实现:
注意这里将heapDown进行了一点改动,即增加了一个arrLen参数,该参数控制考虑的数组长度
def heapDown(arr, index, arrLen=None):
'''
arr: 一维数组
index: 一个父节点
'''
left = 2 * index + 1
right = 2 * index + 2
if arrLen is None: arrLen = len(arr)
# 找到父节点、左节点和右节点中最大节点的下标largest
largest = index
if left < arrLen and arr[left] > arr[largest]:
largest = left
if right < arrLen and arr[right] > arr[largest]:
largest = right
# 如果最大节点的下标不是父节点
# 则将父节点与最大节点进行交换
if largest != index:
arr[index], arr[largest] = arr[largest], arr[index]
# 交换完后,该父节点变为子节点
# 它作为下面树的父节点继续下滤
heapDown(arr, largest, arrLen)
def buildMaxHeap_Down(arr):
N = len(arr)
# 为什么从(N-1-1)//2开始?
# 我们的目标是找到最后一个元素对应的父节点
# 最后一个元素的下标是N-1
# 节点i的父节点是(i-1) // 2
# 所以最后一个元素对应的父节点为: (N-1-1) // 2
for i in range((N-1-1)//2, -1, -1):
heapDown(arr, i)
# print(arr)
def heapSort(arr):
# 构建一个大根堆
buildMaxHeap_Down(arr)
# 从最后一个元素遍历到第2个元素(排除根节点)
for i in range(len(arr)-1, 0, -1):
# 交换根节点和未排序数组的最后一个元素
arr[i], arr[0] = arr[0], arr[i]
# 将交换过后的堆进行下滤 *** 作重新构建为大根堆
# 注意:这里需要限制数组的长度为0到i,即只考虑未排序的数组
# 而后面已经排好序的序列不再考虑
heapDown(arr, 0, i)
return arr
arr = [4, 1, 8, 6, 9, 0, 3, 7, 2, 5]
print(arr)
arr = heapSort(arr)
print(arr)
输入结果:
[4, 1, 8, 6, 9, 0, 3, 7, 2, 5]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
再次感谢bilibili视频
未经授权,禁止转载
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)