一、基础知识
下标为i的节点的父节点下标:(i-1)/2
下标为i的节点的左孩子下标: i*2+1
下标为i的节点的右孩子下标: i*2+2
二、堆排序执行过程(大顶堆)
arr[5]={3,5,4,2,9}
![721289577ad7af0997791e714c4f03b0.png](https://i-blog.csdnimg.cn/blog_migrate/760093787078faeb75603b44360ca56b.png)
- 建堆
for(i=n/2-1;i>=0;i--) //i=n-1,下标为=n-1-1=n-2
{
heapify(arr,n,i); //对每个结点的左右孩子以及自身进行排序操作,调整性质
//注意在建堆过程中,和哪个节点发生了交换,就要把交换的节点heapify一次,维持他的性质
}
PS:建堆时从第一个有孩子节点的开始交换,所以令i=n/2,又因为数组下标是从零开始的,所以再减去1
![eee4cfbabcc209f28a94bbcf6de7a014.png](https://i-blog.csdnimg.cn/blog_migrate/278d8764858bc5b83aee3d5aafac4b4d.png)
2.排序
for(i=n-1;i>0;i-- ){
swap(&arr[i],&arr[0])//就是把最大的拿掉,放到数组最后已排好序的序列中。把最小的放到堆顶,下一步调整堆
heapify(arr,i,0)
}
![a4fa400ff5f5d5f5f2578d4aa41f8e55.png](https://i-blog.csdnimg.cn/blog_migrate/2cc9539720f665adde3b24bb1b99dff4.png)
三、堆排序代码
void heapify(int arr[],int n,int i) //维护堆的性质
{
int largest = i;
int lson = i*2+1;
int rson = i*2+2;
if(lson < n&&arr[largest]<arr[lson])
{
largest = lson;
}
if(rson < n&&arr[largest]<arr[rson])
{
largest = rson;
}
if(largest ! =i){
swap(&arr[largest],&arr[i]);
heapify(arr,n,largest) //交换节点后破坏了堆的性质,要重新维护一下
//注意此处传入的是largest,就算法执行第一轮来说是最后一个节点也就是说此处heapify执行不了,无左右孩子不需要维护
}
}
//堆排序入口
void heap_sort(int arr[],int n){
int i;
//建堆
for(i=n/2-1;i>=0;i--) //此处是从第一个有孩子节点的地方开始调整。之所以要减一是因为用数组存储的
{
heapify(arr,n,i); //对每个结点的左右孩子以及自身进行排序操作,调整性质
}
//排序
for(i=n-1;i>0;i-- ){
swap(&arr[i],&arr[0])//就是把最大的拿掉,放到数组最后已排好序的序列中。把最小的放到堆顶,下一步调整堆
heapify(arr,i,0) //交换后要对数组重新进行维护操作
//注意此处传进去的是i,也就是每执行一次,数组长度就减一
}
}
四、时间复杂度和稳定性
时间复杂度为:O(NlogN)
- 建堆复杂度为O(N)
- heapify复杂度为O(logN)
- 堆排序对N个数进行heapify
稳定性:是不稳定的
两个需要注意的地方!
1️⃣建堆是从n/2-1开始heapify
2️⃣排序是是从根节点开始heapify!
参考
排序算法:堆排序【图解+代码】_哔哩哔哩 (゜-゜)つロ 干杯~-bilibiliwww.bilibili.com