介绍
堆排序(Heap Sort)就是利用堆(假设利用大顶堆)进行排序的方法。它的基本思想是,将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根结点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次小值。如此反复执行,便能得到一个有序序列了。
堆排序真的是一个好东西,不管是待排序序列有序无序,时间复杂度都基本一样。
注:这里堆是一种数据结构,而非内存的那个堆。可以参考完全二叉树,可以表示成一个数组。
代码
//堆排序
/*
这个堆是数据结构堆,不是内存malloc相关的那个堆--我曾经理解错n久
根节点比孩子节点都大的叫大顶堆(包括子树的根),比孩子节点小的叫小顶堆
从小到大排序用的是大顶堆,所以要先构造这种堆,然后把这个根的最大元素交换到最尾巴去
每次拿走一个最大元素,待排序的队列就慢慢变短
主要步骤1,初始化构造这种大顶堆,把堆顶最大的数放到最尾巴,数列长度减少1,再次构建大顶堆
2,这时候只有堆顶元素不满足大顶堆,那么其实只要从堆顶元素开始慢慢微调而已,没必要再完全重新建堆,想要也可以,不过很浪费时间
理解起来确实很难,涉及到完全二叉树
孩子节点i的爸爸是i/2,爸爸节点的儿子是2i和2i+1。
第一次初始化之后充分利用子树已经是大顶堆
*/
//辅助函数:交换两个变量
void swap(int*a,int*p)
{
int temp = *a;
*a = *p;
*p = temp;
}
void adjust(int* arr,int len,int index)
{
//调整函数,把孩子、父亲中的最大值放到父亲节点
//index为待调整节点下标,一开始设它最大
int max = index;
int left = 2*index+1;//左孩子
int right = 2*index+2;//右孩子
if(left<len && arr[left] > arr[max])
{
max = left;
}
if(right<len && arr[right] > arr[max])
{
max = right;
}
//如果父亲节点不是最大
if(max!=index)
{
//一旦上层节点影响了某个孩子节点,还要观察以这个孩子节点为父节点的子树是不是也不是大顶堆了
swap(&arr[index],&arr[max]);
//因为发生了交换,还要继续调整受到影响的孩子节点
//***************************************
adjust(arr,len,max);//这句话非常非常关键
//***************************************
/*
只有父亲和孩子节点发生了交换,才有继续调整孩子的必要,如果无脑在不是这里面递归,堆排序的效果不会比冒泡好到哪去
而如果写在了这里面,虽然还是pk不过快排,但好歹和快排的差距只缩小到个位数倍数的量级(小数据量的时候)
堆排序一个优点是空间复杂度也不高
*/
}
}
//主要排序部分
void heapSort(int* arr,int len)
{
//初始化大顶堆
//initHeap(arr,i,0);
//从最后一个非叶子节点开始
//第一次一定要从下至上一直排,一开始是乱序的
int i = len/2-1;
for(i;i>=0;i--)
{
adjust(arr,len,i);
}
swap(&arr[0],&arr[len-1]);
//第二次之后,只需要从根节点从上到下调整,遇到没发生交换的直接可以退出循环了
//微调得到大顶堆(因为只有堆顶不满足而已)
int j = len -1; //去掉尾节点后的数组长度
//把最大值交换到最后
for(j;j>0;j--)
{
adjust(arr,j,0);
swap(&arr[0],&arr[j-1]);
}
}