八大排序算法——堆排序

来源:互联网 发布:登录前映射网络驱动器 编辑:程序博客网 时间:2024/04/30 02:24

八大排序

排序,分为内部排序和外部排序,内部排序是指将数据记录在内存中进行排序,而外部排序因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存,所以称之为外部排序,我们这里讲的八大排序全部属于内部排序。

这里写图片描述

八大排序时间/空间复杂度及稳定性

这里写图片描述

堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。大根堆的要求是每个节点的值都不大于其父节点的值,即A[PARENT[i]] >= A[i]。在数组的非降序排序中,需要使用的就是大根堆,因为根据大根堆的要求可知,最大的值一定在堆顶。这里要注意的是堆排序采用的是完全二叉树的思想,但它的存储结构采用的是数组。

堆排序的过程(大根堆)

  • 调整堆:调整堆即是将最初无规则的堆调整为大根堆。它的每一次调整都是将非叶子结点i与其左孩子left[i]和右孩子light[i]进行比较,若非叶子结点i不是其中最大的,那么将其与最大的结点位置互换。堆的第一次调整是从最后一个非叶子结点开始依次向根节点调整的,这就是为什么它要采用完全二叉树的原因,因为这样可以快速的在数组中定位最后一个非叶子结点,即a[n/2-1],其中a是待排序的数组,n是数组a的下标值。那么就是说,下标为n/2-1的结点就是最后一个非叶子节点。堆的第一次调整函数的调用关系大概可以总结为循环调用+递归调用。其过程如下:
    • 初始完全二叉树
      初始完全二叉树
    • 数值3为最后一个非叶子结点,将其与它的孩子结点相比较,8>3,位置互换
      这里写图片描述
    • 判断被调整后的结点3是否为非叶子结点,3不是,函数调用进入下一层循环,将结点7与它的左右儿子结点相比较,20>17>7,将结点20与结点7位置互换
      这里写图片描述
    • 判断被调整后的结点7是否为非叶子结点,7不是,函数调用进入最后一层循环,将结点16与它的左右儿子结点相比较,20>16>7,将结点16与结点20位置互换
      这里写图片描述
    • 判断被调整后的结点16是否为非叶子结点,16是,函数进行递归调用,将结点16与它的左右儿子结点相比较,17>16>7,将结点17与结点16位置互换
      这里写图片描述
    • 判断被调整后的结点16是否为非叶子结点,16不是,循环结束,堆调整完毕。
  • 堆排序:可以看到,上面的调整堆算法循环调用执行完毕以后,结果是一个大根堆,堆排序所要做的就是将堆顶元素20与最后一个元素3互换位置,即将a[0]与a[n-1]互换位置,换位置之后a[0]至a[n-2]元素又组成一个新的堆,这时的调整堆与第一次调整刚好相反,它是从堆顶元素开始调整,一直往下延伸,是一个纯递归的过程,具体过程如下:
    这里写图片描述这里写图片描述这里写图片描述这里写图片描述这里写图片描述这里写图片描述这里写图片描述这里写图片描述这里写图片描述这里写图片描述这里写图片描述
    到这里,排序就算是执行完了,从上述过程可知,堆排序其实也是一种选择排序,是一种树形选择排序。只不过直接选择排序中,为了从a[1…n]中选择最大记录,需比较n-1次,然后从a[1…n-2]中选择最大记录需比较n-2次。事实上这n-2次比较中有很多已经在前面的n-1次比较中已经做过,而树形选择排序恰好利用树形的特点保存了部分前面的比较结果,因此可以减少比较次数,这就是为什么第一次调整堆是从下往上,而之后都是从上往下。对于n个关键字序列,最坏情况下每个节点需比较log2(n)次,因此其最坏情况下时间复杂度为nlogn。堆排序为不稳定排序,不适合记录较少的排序。下面是堆排序的c语言实现:
#include<stdio.h>#include<stdlib.h>#include<math.h>//堆调整算法void HeapAdjust(int num[],int i,int length){    //定义max保存以num[i]为根的最小堆中最大值的下标,初始值为i    int max=i;    //判断num[i]是否为根结点 (下标i<=length/2-1的结点都是根节点)    if(i<=length/2-1)    {        //判断num[2*i+1]是否在堆内,若在,判断其是否大于num[max],若大于,将其下标2*i+1赋予给max        if((2*i+1)<length && num[2*i+1]>num[max])        {            max=2*i+1;        }        //判断num[2*(i+1)]是否在堆内,若在,判断其是否大于num[max],若大于,将其下标2*(i+1)赋予给max        if((2*(i+1))<length && num[2*(i+1)]>num[max])        {            max=2*(i+1);        }        //若i不等于max,则说明以num[i]为根的最小堆中num[max]是最大值,将num[max]与根结点num[i]交换位置        if(i!=max)        {            num[i]=num[i]^num[max];            num[max]=num[i]^num[max];            num[i]=num[i]^num[max];            //交换后,为防止以num[max]为根结点的最小堆结构发生变换,再次调用堆调整算法            HeapAdjust(num,max,length);        }    }}//堆排序算法void HeapSort(int num[],int length){    int i;    //找到最后一个非叶子结点,先从最后一个非叶子结点组成的最小堆进行堆调整    for(i=length/2-1;i>=0;i--)        //调用调整堆算法将数组从下往上调整为大根堆        HeapAdjust(num,i,length);    for(i=length-1;i>0;i--)    {        //将数组第一个数和最后一个数换位置        num[i]=num[i]^num[0];        num[0]=num[i]^num[0];        num[i]=num[i]^num[0];        //继续调用调整堆算法,将剩余数据组成的堆从上往下调整为大根堆        HeapAdjust(num,0,i);    }}int main(){    int i;    int num[]={8,5,7,12,48,36,4};    HeapSort(num,sizeof(num)/sizeof(int));    for(i=0;i<sizeof(num)/sizeof(int);i++)    {        printf("%d\t",num[i]);    }    printf("\n");}
1 0