排序算法—堆排序

来源:互联网 发布:单片机连接继电器 编辑:程序博客网 时间:2024/05/17 05:58

堆的定义:

堆实际上是一棵完全二叉树,其任何一非叶节点满足性质:即任何一非叶点的值不大于或者不小于其左右孩子节点的值。一个数组看成是一个完全二叉树,利用完全二叉树左右孩子和其父亲节点在数组中的下标特点进行调整后符合堆的定义最后达到整个序列有序的算法:

当数组从0下标开始时,一根节点的左右孩子存在时满足 :

 左孩子下标=父节点*2+1;

 右孩子下标=父节点*2+2;

假设有数组int a[10] = { 50, 10, 90, 30, 70, 40, 80, 60, 20,54 };

看成完全二叉树是:


大小根堆的定义:

 堆分为大顶堆和小顶堆

满足Key[i]>=Key[2i+1]&&key>=key[2i+2]称为大顶堆

满足 Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]称为小顶堆。

由上述性质可知

大顶堆的堆顶的关键字肯定是所有关键字中最大的

小顶堆的堆顶的关键字是所有关键字中最小的。


调整堆的过程:本例中从(数组长度(10) - 1)/2 =4,即下标为0,1,2,3,4的根节点进行调整,原因是根据完全二叉树下标的特点这些节点以下有左右孩子,堆调整操作才有意义,如果没有左右孩子那么最大的就是自己本身了。

依次从a[4],a[3],a[2],a[1],a[0]对以下的堆进行调整:


其中a[4],a[3], a[2]的堆中与左右孩子做比较找出最大值,交换到a[4],a[3], a[2]中

调整后效果图如下:


调整a[1]堆时,取a[1]  a[3]  a[4]中的最大


 

因为a[1]和a[4]发生了交换,所以还需要继续去调整a[4]对应的堆,原理和之前一样取最大的值交换到a[4]中。

调整后


调整a[0]堆时,取a[0]和a[1],a[2]的最大值,交换a[0]和a[2]。

因为和a[2]交换后导致a[2]不是大顶堆,需要继续调整a[2]堆


调整完a[0]堆后,一个大顶堆就调整完了,小顶堆也是这样调整。


堆排序的时间复杂度,最好最坏平均时间都是O(nlogn)。并且是不稳定的排序算法

快的主要原因还是因为数据之间省略了很多多于的比较,比如说你找a[1]堆的最大值,你只需要比较a[1]  a[3] a[4] 三个数,因为a[3]和a[4]已经是a[3]堆,a[4]堆的最大值。

最后附上代码:

#include<stdio.h>void swap(int *a,int *b){int t = *a;*a = *b;*b = t;}void heapadjust(int a[], int root, int last){//将a[root]到a[last]调整为最大堆for (int i = root * 2 + 1; i <= last; i = root * 2 + 1){if (i<last && a[i] < a[i + 1])i++;//i<last保证该根节点有右孩子if (a[root]>a[i])break;//当前根节点值最大,不用调整swap(&a[root], &a[i]);//将最大的孩子登顶root = i;//i指向最大孩子之前的下标,}//i=root*2+1定位到左孩子节点//每次循环的目的是让当前的三个节点:根a[i/2]  左a[i] 右a[i+1] //的最大值作为根节点}void heapsort(int a[], int len){for (int i = (len - 1) / 2; i >= 0; i--)heapadjust(a, i, len - 1);//初始化一个大顶堆for (int i = 0; i < len; i++){swap(&a[0], &a[len - 1 - i]);//把堆顶元素放到数组末尾heapadjust(a, 0, len - 2 - i);//缩小范围(即去掉末尾的元素)继续调整堆}}void main(){int a[10] = { 50, 10, 90, 30, 70, 40, 80, 60, 20,54 };heapsort(a,10);//从a[0]到a[9]进行堆排序for (int i = 0; i < 10;i++){printf("%3d", a[i]);}getchar();}



原创粉丝点击