一、堆
堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。在堆排序中使用到大顶堆。
完全二叉树的性质:
●如果按照层序遍历的方式给结点从0开始编号,则结点之间满足如下关系:
●如果i = 0;则结点i是二叉树的根,无双亲
●如果i > 0; 对于左孩子,则其双亲是结点i/2;对于右孩子,则其双亲是结点(i/2)-1;那么对于有n个结点的二叉树而言,非叶子结点的i值(也就是由左右孩子的结点)小于等于i/2。
二、堆排序
堆排序的过程:
<1>构建大顶堆(自下而上调整):从非结点开始,比较左右孩子的大小,获得值比较大的孩子结点和父结点做比较,如果父结点大于此孩子结点,则进入下一次循环,否则交换父结点和孩子结点的值。
<2>得到序列的最大值:将大顶堆的根节点(最大值)与堆中的最后一个结点进行交换,下一次循环中,丢弃最后一个结点(i--),也就得到序列的最大值。
<3>再次调整成为大顶堆。
完整代码:
#include <stdio.h>
void Swap(int arr[],int a,int b)
{
int temp;
temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
void HeapSort(int arr[],int i,int len)
{
int j = 2*i + 1;//i是一个子树中的根节点,j是子树中的左孩子
for(j; j < len;j = i*2 +1)
{
if(arr[j+1] > arr[j] && j < len -1)
{
j++;//j指向孩子值比较大的一端
}
if(arr[i] >= arr[j])
{
break;
}
Swap(arr,i,j);
i = j;
}
}
void Heap(int arr[],int len)
{
int i = len/2 - 1;//对于n个结点,非叶子结点的i值为n/2,这里的len是长度,所以要减一
for(i;i >= 0;i--)
{
HeapSort(arr,i,len);//构建大顶堆,要自下而上调整;
}
for(int i = len -1;i >=0;i--)//i--,每次丢弃最后一个最大值元素,也就是每次循环得到一个最大值
{
Swap(arr,0,i);//将堆顶的最大值与最后一个元素交换
HeapSort(arr,0,i);//将交换后的数据重新调整为大顶堆,因为下标为0的位置改变,所以每次从0号元素自顶向下开始调整;
}
}
void Show(int arr[],int len)
{
for(int i = 0;i < len;i++)
{
printf("%d ",arr[i]);
}
}
int main()
{
int arr[] = {8,39,4,809,89,1};
int len = sizeof(arr)/sizeof(arr[0]);
Heap(arr,len);
Show(arr,len);
}
三、堆排序的时间复杂度
在构建堆的过程中,因为我们是完全二叉树从最下层最右边的非终端结点开始构建,将它与其孩子进行比较和若有必要的互换,对于每个非终端结点来说,其实最多进行两次比较和互换操作,因此整个构建堆的时间复杂度为O(n);
在正式排序时,第i次取堆顶记录重建堆需要用O(logi)的时间(完全二叉树的某个结点到根节点的距离为log2i + 1),并且需要取n - 1次堆顶记录,因此,重建堆的时间复杂度为O(nlogn)
所以总体来说,堆排序的时间复杂度为O(nlogn).