堆排步骤:
1、放数据
2、建堆(大根堆或者小根堆)
第一次创建大根堆从中间节点开始,依次到第一个节点,以后都从第一个节点开始,因为其他的之前排好序了。
注:
大根堆:所有的父节点都大于它自己本身的子节点。
小根堆:所有的父节点都小于它自己本身的子节点。
为了使数组下标和堆下标对应,数组a[0]不存数据,堆的根节点下标为1,子节点下标依次递增。
3、排序
复杂度和稳定性情况:
- 最好的时间复杂度:O(nlog n)
- 最坏的时间复杂度:O(nlog n)
- 平均的时间复杂度:O(nlog n )
- 空间复杂度:O(1)
- 稳定性:不稳定
例:(图片转自:https://blog.csdn.net/fenger3790/article/details/82837891)
大根堆:
这就是从初始数据建立大根堆的过程。可以看到经过一次建堆之后,数组中最大的数来到了堆顶,这时我们把堆顶元素和末尾元素交换,然后把末尾元素输出。(可以理解成跟原始数据斩断关系)
把堆顶元素交换然后输出之后,我们发现堆又不平衡了,不是大根堆了,但是因为我们只交换了堆顶,只有堆顶是不平衡的,所以我们只需要调整堆顶就能再得到平衡的大根堆。
调整完之后输出了当前的最大数8,我们发现堆顶又不平衡了,所以我们再调整堆顶,然后再输出最大数,堆排序的步骤就是重复调整堆、输出到末尾这两个过程。下面是排序的全过程。
C实现代码如下:
#include<iostream>
using namespace std;
//创建大根堆函数
//a为数组首地址,root为根节点下标,len为数组长度
void CreateHeap(int *a, int root, int len)
{
int i = 2*root; //i为一个标记,记录子节点中较大值的下标
int t = a[root]; //t为根节点的值
while(i <= len) //当前节点后至少有两个子节点
{
if(i < len) //说明有两个子节点
{
if(a[i] < a[i+1])
{
++i; //若右孩子值大于左孩子的值,则标记后移,指向大的
}
}
if(t >= a[i]) //若根节点值大于子节点中较大的值,则其已经是大根堆了,结束循环
break;
else //若根节点值小于子节点中较大的值,则将子节点值较大的给根节点
{
a[i/2] = a[i]; //把子节点较大值给父节点
i = 2*i; //用于下一次循环判断子节点作为父节点时是否有两个子节点
}
}
a[i/2] = t; //将根节点值给循环前的值较小的子节点
}
//堆排序函数
void Sort(int *a, int len)
{
int i;
int t;
//第一次创建大根堆,从中间节点开始创建
for(i = len/2; i >= 1; --i)
{
CreateHeap(a,i,len);
}
for(i = len; i >= 1; --i)
{
//交换根节点值和最大的叶节点值
t = a[1];
a[1] = a[i];
a[i] = t;
CreateHeap(a,1,i-1); //每次交换值后都重新建立大根堆
}
}
//打印函数
void Print(int *a, int len)
{
for(int i = 1; i < len; ++i)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
//测试
int main()
{
int a[] = {3,7,5,1,2,6,4,8,9,0};
int len = sizeof(a)/sizeof(a[0]);
Sort(a,len);
Print(a,len);
return 0;
}
其他排序算法: