zoukankan      html  css  js  c++  java
  • 堆排序学习(摘自《大话数据结构》)

    伍老师的《大话数据结构》是本思路清晰又讲解形象的书,十分适合入门者学习。我对堆排序以前很是害怕,所以特意仔细阅读了相关堆排序的内容,感觉完全理解啦。摘抄下来存档,方便自己随时查阅和复习。

    堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。

    如果按照层序遍历的方式给结点从1开始编号,则结点之间满足如下关系:k[i]≥k[2i]&&k[i]≥k[2i+1] 或者 k[i]≤k[2i]&&k[i]≤k[2i+1],其中,0<i<n/2。为什么i<n/2呢?现在回忆一条完全二叉树的性质:一棵完全二叉树,如果i=1,则结点 i 是二叉树的根,无双亲;如果i>1,则其双亲是结点⌊i/2⌋。那么对于有n个结点的二叉树而言,它的 i 值自然就是小于等于⌊n/2⌋了。

    堆排序(Heap Sort)就是利用堆(假设利用大顶堆)进行排序的方法。它的基本思想是,将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根结点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次小值。如此反复执行,便能得到一个有序序列了。

    不过要实现它还需要解决两个问题:

    1.如何由一个无序序列构建一个堆?

    2.如果在输出堆顶元素后,如何调整剩余元素成为一个新的堆?

    程序代码说明:

    #define MAXSIZE 10 /* 用于要排序数组个数最大值,可根据需要修改 */
    typedef struct
    {
    int r[MAXSIZE+1]; /* 用于存储要排序数组,r[0]用作哨兵或临时变量 */
    int length; /* 用于记录顺序表的长度 */
    }SqList;

    /* 交换L中数组r的下标为i和j的值 */
    void swap(SqList *L,int i,int j)
    {
    int temp=L->r[i];
    L->r[i]=L->r[j];
    L->r[j]=temp;
    }

    /* 已知L->r[s..m]中记录的关键字除L->r[s]之外均满足堆的定义 */
    /* 本函数调整L->r[s]的关键字,使L->r[s..m]成为一个大顶堆 */
    void HeapAdjust(SqList *L,int s,int m)
    {
    int temp,j;
    temp=L->r[s];
    for(j=2*s;j<=m;j*=2) /*沿关键字较大的孩子结点向下筛选 */
    {
    if(j<m && L->r[j]<L->r[j+1])
    ++j; /*j为关键字中较大的记录的下标*/
    if(temp>=L->r[j])
    break; /* rc应插入在位置s上*/
    L->r[s]=L->r[j];
    s=j;
    }
    L->r[s]=temp; /*插入*/
    }

    /* 对顺序表L进行堆排序 */
    void HeapSort(SqList *L)
    {
    int i;
    for(i=L->length/2;i>0;i--) /* 把L中的r构建成一个大顶堆 */
    HeapAdjust(L,i,L->length);
    for(i=L->length;i>1;i--)
    {
    swap(L,1,i); /*将堆顶记录和当前未经排序子序列的最后一个记录交换*/
    HeapAdjust(L,1,i-1); /* 将L->r[1..i-1]重新调整为大顶堆*/
    }
    }

    复杂度分析:

    它的运行时间主要是消耗在初始构建堆和在重建堆时的反复筛选上。

    在构建堆的过程中,因为我们是完全二叉树从最下层最右边的非终端结点开始构建,将它与其孩子进行比较和若有必要的互换,对于每个非终端结点来说,其实最多进行两次比较和互换操作,因此整个构建堆的时间复杂度为O(n)。

    在正式排序时,第 i 次取堆顶记录重建堆需要用O(logi)的时间(完全二叉树的某个结点到根结点的距离为⌊log2i⌋+1),并且需要取n-1次堆顶记录,因此,重建堆的时间复杂度为O(nlogn)。

    所以总体来说,堆排序的时间复杂度为O(nlogn)。由于堆排序对原始记录的排序状态并不敏感,因此它无论是最好、最坏和平均时间复杂度均为O(nlogn)。这在性能上显然要远远好过于冒泡、简单选择、直接插入的O(n平方)的时间复杂度了。

    空间复杂度上,它只有一个用来交换的暂存单元,也非常的不错。不过由于记录的比较与交换是跳跃式进行,因此堆排序也是一种不稳定的排序方法。

    另外,由于初始构建堆所需的比较次数较多,因此,它并不适合待排序序列个数较少的情况

  • 相关阅读:
    网站统计中的数据收集原理及实现
    启动hadoop报ERROR org.apache.hadoop.hdfs.server.namenode.FSImage: Failed to load image from FSImageFile
    淘宝(大数据库应用)--转载
    MapReduce作业的map task和reduce task调度参数
    Spark和Hadoop作业之间的区别
    分析MapReduce执行过程
    MapReduce框架Partitioner分区方法
    LVS+keepalived实现负载均衡
    Tomcat 详解
    linux Tomcat restart脚本简单版
  • 原文地址:https://www.cnblogs.com/stoneJin/p/2223601.html
Copyright © 2011-2022 走看看