与前面介绍的几种排序方式不同,Heap sort 堆排序是一种原地排序算法,不需要像合并(归并)排序那样要额外的储存空间
其运行时间为 O(nlogn) ,与合并排序相同。
对堆的简单介绍:堆一般为二叉堆是一种数据结构
最小堆:
在数组中,由于下标从0开始,则根i的左右节点分别为2i+1 和2i+2
堆在数组中的储存一般为:从上到下,从左到右,因为二叉堆都是完全二叉树,
则在有n个节点的堆中,n/2……n均为叶子节点,这一性质在后面的堆排序中至关重要
堆排序基本思想:
一、建立一个最大堆 保证首元素为最大元素
二、将首元素data[0]与最末尾元素data[n](较小)的交换,则末尾元素data[n]成为最大元素。
三、此时,最大堆性质被破坏,重新调整data[0……n-1],使其重新成为一个最大堆,
则data[0]又成为最大元素,再将data[0] 与data[n-1]交换,以此类推,交换到data[1]时即可停止循环
因此要解决三个问题:1是如何调整堆,2是如何建一个最大堆,3是如何循环交换调整
先来看第一个问题:如何调整堆
调整堆就是用根节点与其左右孩子节点比较,将较大的值上浮,较小的值下降
1 较小则与最大的 3 交换,由于交换后 右孩子从3变为1,不能保证右孩子为根节点的树为最大堆,则继续调整右孩子,直到叶子节点结束,这样就保持了堆的性质
这样自然而然就产生了递归的方法与结果
代码为:MaxHeapfiy函数实现
/***********Heap sorting**************/
#define leftchild(i) (2*i+1) //下标从0开始 左孩子
#define rightchild(i) (2*i+2) //下标从0开始 右孩子
void max_Heapify(int data[],int heapsize,int i) //保持最大堆性质 i为最大堆根节点
{
int l=leftchild(i);
int r=rightchild(i);
int largest;
if (l<=heapsize&&data[i]<data[l]) //比较树节点
largest=l;
else
largest=i;
if (r<=heapsize&&data[largest]<data[r])
largest=r;
if (largest!=i) //不为最大堆 则交换值
{
int tmp=data[largest];
data[largest]=data[i];
data[i]=tmp; //此时最大堆性质可能破坏
max_Heapify(data,heapsize,largest); //递归调用调整交换后的树节点,保持最大堆性质
}
}
再来看第二个问题:如何建立一个最大堆
这就要用到第一个函数maxHeapfiy,从前面的结束可知,madHeapfiy是从根节点往下调整的,并不能用max_Heapfiy(data,heapsize,0)建立一个最大堆,
因为后面的元素可能比根节点要大,max_Heapfiy只是起到将较小的值下降的作用,使较大的值上浮一次,但不能让最大的值上浮达根节点
前面已经说过 ,二叉堆是一个完全二叉树,在有n个节点的堆中,n/2……n均为叶子节点,剩余节点均不为叶子节点
由此我们就用这种思路,若用由底层向上的节点顺序调用max_Heapfiy函数,即 i 以 n/2……0的顺序调用max_Heapfiy
便可以生成一个最大堆
代码:buildMaxHeap
void buildMaxHeap(int data[],int len) //建造最大堆
{
for (int i=len/2;i>=0;i--) //data[LEN/2……LEN]均为叶子节点
max_Heapify(data,len,i); //对剩余节点从底向上调整(最后是DATA[0]),则可得到最大堆
}
运行结果如图所示:data [ ]={1,0,2,9,3,1,5,8,9,10}
可以看到运行后最大堆已经建立啦,但原数组并没有排好序,只是使得首元素为最大元素10
最后让我们轻松地解决第三个问题:完成堆排序
直接贴代码:
/********堆排序算法,非递减排序*********/
void heapsort(int data[],int len)
{
buildMaxHeap(data,len); //构造最大堆
int heapsize=len;
for (int i=len;i>0;i--) //data[0]为每次调整后最大元素,与末尾元素data[i]交换
{
int tmp=data[i];
data[i]=data[0];
data[0]=tmp;
heapsize--;
max_Heapify(data,heapsize,0);//调整,使data[0]为最大元素
}
}
测试:
#define getArraySize(arrayName) (sizeof(arrayName)/sizeof(arrayName[0])) //获取数组长度
int _tmain(int argc, _TCHAR* argv[])
{
int data_test[10]={1,0,2,9,3,1,5,8,9,10};
prtarray(data_test,getArraySize(data_test));//打印原数组
heapsort(data_test,getArraySize(data_test)-1);//堆排序
printBinTree(data_test,getArraySize(data_test));//以二叉树的形式打印数组
prtarray(data_test,getArraySize(data_test));//打印排好序的数组
system("pause");
return 0;
}
运行结果:
算法分析:
每次调用max_Heapfiy的运行时间为O(logn),即树的深度 然后又O(n)次调用max_Heapfiy
故堆排序的时间复杂度为 O(logn) *O(n) = O(n *logn)