一、堆:是一种数据结构,一种叫做完全二叉树的数据结构。
二、堆的性质:
1、大顶堆:每个节点的值都大于或者等于它的左右子节点的值。
大顶堆性质:arr[i] >= arr[2i + 1] && arr[i] >= arr[2i + 2]
2、小顶堆:每个节点的值都小于或者等于它的左右子节点的值。
小顶堆性质:arr[i] <= arr[2i + 1] && arr[i] <= arr[2i + 2]
3、堆排序的基本思想:
(1)将带排序的序列构造成一个大顶堆,根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;
(2)将堆顶元素和最后一个元素交换,然后将剩下的节点重新构造成一个大顶堆;
(3)重复步骤2,如此反复;
(4)从第一次构建大顶堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大顶堆的尾部。
(5)最后,就得到一个有序的序列了。
4、非叶子节点的确认:
(1)根据大顶堆的性质,每个节点的值都大于或者等于它的左右子节点的值。
所以我们需要找到所有包含子节点的节点,也就是非叶子节点,然后调整他们的父子关系。
非叶子节点遍历的顺序应该是从下往上,这比从上往下的顺序遍历次数少很多。
因为,大顶堆的性质要求父节点的值要大于或者等于子节点的值,
如果从上往下遍历,当某个节点即是父节点又是子节点并且它的子节点仍然有子节点的时候,
因为子节点还没有遍历到,所以子节点不符合大顶堆性质,当子节点调整后,必然会影响其父节点需要二次调整。
但是从下往上的方式不需要考虑父节点,因为当前节点调整完之后,当前节点必然比它的所有子节点都大,
所以,只会影响到子节点二次调整。
相比之下,从下往上的遍历方式比从上往下的方式少了父节点的二次调整。
(2)如何知道最后一个非叶子节点的位置,也就是索引值?
对于一个完全二叉树,在填满的情况下(非叶子节点都有两个子节点),
每一层的元素个数是上一层的二倍,
根节点数量是1,所以最后一层的节点数量,一定是之前所有层节点总数+1,
所以,我们能找到最后一层的第一个节点的索引,即节点总数/2(根节点索引为0),
这也就是第一个叶子节点,所以第一个非叶子节点的索引就是第一个叶子结点的索引-1。
那么对于填不满的二叉树呢?这个计算方式仍然适用,当我们从上往下,从左往右填充二叉树的过程中,
第一个叶子节点,一定是序列长度/2,所以第一个非叶子节点的索引就是arr.length / 2 -1。
三、参考原地址:https://blog.csdn.net/qq_28063811/article/details/93034625
四、Python排序:
#堆排序
#构建大顶堆
def heapify(arr,n,i):
largest=i # 索引 i 作为比较的节点索引位置
l=2*i+1 #left=2*i+1
r=2*i+2 #right=2i+1
#由左到右:节点值与下级左右节点值比较
#节点 i 小于子左节点值,再用子左节点值与右节点值比较
if l<n and arr[i]<arr[l]: #">"由大到小排序;"<"有小到大排序
largest=l #如果子左节点值大,则用子左节点值与子右节点值比较
if r<n and arr[largest]<arr[r]: #">"由大到小排序;"<"有小到大排序
largest=r #
if largest !=i: #如果发现 i 节点值比左右子节点值小,则把做大的子节点值与 i 位置的值交换,实现大顶堆的性质。
arr[i],arr[largest]=arr[largest],arr[i] #交换
heapify(arr,n,largest) #然后 递归此过程
def heapSort(arr):
n=len(arr)
#建 大顶堆
#从下往上构建大顶堆
for i in range(n,-1,-1): # i 从取最大位置索引开始
heapify(arr,n,i)
#一个个交换元素
#大顶堆构建完成后,跟节点值与最后节点值交换,最大值放到了列表的最后
#然后再从根节点索引位置开始,再次进行大顶堆的构建
#再把跟节点值与 i 索引的值交换,依次递归
for i in range(n-1,0,-1): # i :数列元素的个数逐渐减少
arr[i],arr[0]=arr[0],arr[i] #交换
heapify(arr,i,0) #从根节点开始从已构建的大顶堆再次重构
#测试
arr=[4,5,8,2,3,9,7,1]
heapSort(arr)
n=len(arr)
print("排序后")
for i in range(n):
print("%d" %arr[i])