zoukankan      html  css  js  c++  java
  • 堆排序(改进的简单选择排序)

      在介绍堆排序之前,先介绍一下这种数据结构:

      堆是一颗完全二叉树,且具有如下性质,每个结点的值都大于等于其左右孩子结点的值,称为大顶堆;每个结点的值都小于等于其左右孩子结点的值,称为小顶堆

      我们以大顶堆为例来介绍堆排序,且采用顺序存储结构(物理结构)数组来存储堆的层序遍历结果。

      堆排序的基本思想:将待排序的n个记录构造成一个大顶堆,将根结点与堆数组的倒数第一个元素交换位置,得到最大值;将剩余的(n-1)个记录调整为大顶堆,将根结点与堆数组的倒数第二个元素交换位置,得到次最大值如此反复执行,最终可以将一个顺序表排为有序表。

      以顺序表L = {3,1,5,9,8}为例,length = 5,下标从1开始。

      堆排序的步骤:(1)对于顺序表L,调用调整堆算法将其构建为一个大顶堆;

             (2)输出堆顶记录以后,调用调整堆算法将剩余记录调整为一个大顶堆,如此反复执行。

      堆排序代码如下所示:

     1 //堆排序算法
     2 //将待排序的序列构造成一个大顶堆
     3 //将大顶堆的根结点与堆末尾元素交换(层序遍历的最后一个结点)
     4 //将剩余的序列重新调整为一个大顶堆
     5 //将大顶堆的根结点与堆末尾元素交换
     6 //如此反复执行
     7 void HeapSort(SqList* L)
     8 {
     9     int i,j;
    10 
    11     //结点i = 1... (length / 2);都是有孩子的结点
    12     //构造大顶堆的过程,就是将每个非叶子结点当作根结点,将其和其子树调整为大顶堆的过程
    13     //叶子结点均满足堆的定义
    14     for (i = L->length / 2; i > 0; i--)//将L中的记录构造成一个大顶堆
    15         HeapAdjust(L, i, L->length);//调用调整堆算法
    16 
    17     for (j = L->length; j > 1; j--)
    18     {
    19         //将堆顶记录和堆尾记录交换位置
    20         swap(L, 1, j);
    21 
    22         //将剩余的序列重新调整为一个大顶堆
    23         //除了根结点以外,均满足堆的定义,符合调用调整堆算法的条件
    24         HeapAdjust(L, 1, j - 1);
    25     }
    26 }

      顺序表中的记录变化如下:

      其实,堆排序的原理还是很简单明朗的,重点在于如何对堆进行调整。在调整堆算法中,要求待调整的完全二叉树除了根结点以外,其余部分均满足堆的性质。在调用调整堆算法构建堆的过程中,对于一棵完全二叉树,只有叶子节点一定满足堆的定义,其他子树不一定满足,所以我们选择从层序遍历中序号最大的且有孩子结点的结点开始,依次调用调整堆算法,直到到达完全二叉树的根结点,最终,将一个顺序表构建成一个大顶堆。总而言之,构建大顶堆的过程,就是从下往上,由右至左,将每个非叶子节点当作根结点,将其和其子树调整成大顶堆的过程

      调整堆代码如下所示:

     1 //调整堆算法
     2 //L中的记录除L->r[s](根结点)外均满足堆的定义
     3 //调整L->r[s]的位置,使L->r[s,...m]成为一个大顶堆
     4 void HeapAdjust(SqList* L, int s, int m)
     5 {
     6     //temp当作中间变量,存储根结点的值
     7     int temp, j;
     8     temp = L->r[s];//temp初始化为根结点的值
     9 
    10     //结点j为结点(2*j)和结点(2*j+1)的双亲
    11     //第x次for循环找出当前根结点的孩子结点的最大值,并赋值给当前根结点
    12     for (j = 2 * s; j <= m; j *= 2)
    13     {
    14 
    15         //j < m说明还未到达最后一个结点
    16         //若j <= m ,则r[j + 1]超出了索引范围
    17         //L->r[j] < L->r[j + 1]表示沿着关键字较大的结点的方向向下筛选
    18         if (j < m && L->r[j] < L->r[j + 1])
    19             ++j;//j为关键字较大的结点的下标
    20 
    21         if (temp >= L->r[j])//根节点的值大于等于其孩子结点的最大值
    22             break;//跳出for循环
    23 
    24         //当前根节点的值小于其孩子结点的最大值
    25         L->r[s] = L->r[j];//当前根结点的孩子结点的最大值赋值给当前根结点
    26 
    27         s = j;//关键字最大的孩子结点当作新的根结点
    28     }
    29 
    30     //将原来的根结点放在了正确的位置
    31     L->r[s] = temp;
    32 }

      我们以调用调整堆算法构建堆的过程为例,来说明调整堆代码的实现流程,顺序表中的记录变化如下:

      

      注意,红色虚线框内的部分为需要调整为堆的完全二叉树。如上图所示,为for循环条件不满足导致for循环结束的情况,与上述例子相同;为了说明另一种for循环结束的情况(break导致),我们以顺序表L = {8,1,5,9,3}为例,并做了图示说明。

    相关链接:

    冒泡排序 https://www.cnblogs.com/yongjin-hou/p/13858510.html
    简单选择排序 https://www.cnblogs.com/yongjin-hou/p/13859148.html
    直接插入排序 https://www.cnblogs.com/yongjin-hou/p/13861458.html
    希尔排序 https://www.cnblogs.com/yongjin-hou/p/13866344.html

    归并排序 https://www.cnblogs.com/yongjin-hou/p/13921147.html

    快速排序 https://www.cnblogs.com/yongjin-hou/p/13950379.html

    参考书籍:程杰 著,《大话数据结构》,清华大学出版社。

  • 相关阅读:
    ZJNU 1216
    ZJNU 1370
    ZJNU 2018
    python装饰器(简单装饰器、叠加装饰器、防止被装饰函数更名、参数化装饰器)
    etcd学习(6)-etcd实现raft源码解读
    etcd学习(5)-etcd的Raft一致性算法原理
    etcd学习(4)-centos7中部署etcd
    etcd学习(3)-grpc使用etcd做服务发现
    etcd学习(2)-etcd中watch源码解读
    etcd学习(1)-etcd的使用场景
  • 原文地址:https://www.cnblogs.com/yongjin-hou/p/13873770.html
Copyright © 2011-2022 走看看