堆 排 序

008 堆排序

一、算法介绍

堆的概念

堆可看成是一颗满足顺序存储的完全二叉树。满足任何分支结点的关键字都大于其左右孩子结点关键字的,称为大根堆;满足任何分支结点的关键字都小于其左右孩子结点关键字的,称为小根堆。

完全二叉树

完全二叉树的定义
若二叉树中最多只有最下面两层的结点度数可以小于2,并且最下面一层的叶子结点都依次排列在最左边的位置上,这样的二叉树成为完全二叉树。

完全二叉树的性质
我们把二叉树的各个结点按照层数的大小,同一层从左到右的次序进行编号。对于层序遍历编号为i的结点,有如下性质:

1.若该结点有左右孩子,则其左右孩子的编号分别为 2i+1 和 2i+2
2.除根结点外,该结点的的双亲结点的编号⌊i/2⌋

堆排序的基本思想是:将一个待排序列构造成为一个大顶堆,这样就使得根结点为关键字最大的结点。将根节点与最后一个叶子节点交换位置,末尾位置就成为最大关键字结点。再用同样的方法将除去末尾结点的完全二叉树构造成一个大顶堆,交换根结点和末尾结点。如此重复操作,直到只剩下最后一个结点,就能是原序列有序了。

二、算法分析

在将堆的结点发生改变后,为了维持堆的性质,需要从修改的结点开始,对以其为根的树经行维护。先以分支结点关键字最大(初始化),依次与其左右孩子a[2i]和a[2i+1]的关键字做比较(当然比较需要在有左右孩子的前提下),然后根据比较结果对当前最大值做修改,最后做判断,若根节点不为最大值,则交换其根节点和最大值结点的位置。同样的,因为原本是最大值结点的位置(可能)由原来的根结点代替,则对其子树需要进行同样的操作。
在将一个给定的乱序序列构造成大顶堆时,可以从倒数第二层开始。根据二叉树的性质,对于结点数为n的完全二叉树,倒数第二层的最后一个分支结点编号为⌊n/2⌋。而下一次只需要将 i -1就可到其上一个分支结点,直到i=0。这样,对于倒数第二层以上的结点,在每次构造时,由于下面的子树已经构造过了,结点位置发生改变时,只需要对发生变化的孩子结点的子树进行维护就行了。
由于在做堆排序的时候需要频繁对根节点做修改,所以在每次归位一个结点后,就须对堆进行维护,以保证最大值在根结点上。
如图所示

堆的维护

假设堆的1号结点关键字变为4,要维护堆,就从1号结点开始

在这里插入图片描述
发现最大值为3号结点,则把1和3号结点做交换,这样3号结点的子树同样需要维护

在这里插入图片描述
同理,把3和8号做交换,接着8号结点没有孩子,则停止维护
在这里插入图片描述

堆的构建

对于一个乱序序列

4214781031619
0123456789

其二叉树:
在这里插入图片描述
从最后一个结点的双亲结点开始构建堆
对4号结点,和9号交换;对3号结点,和7号交换:
在这里插入图片描述
因为改变的是叶子节点,显然不需要维护
对2号结点,不需要交换;对1号结点,和3号结点交换
在这里插入图片描述
最大值结点位置3号上的结点发生改变,对其子树进行维护,和7号结点交换
在这里插入图片描述
然后作为叶子节点的7号发生改变,但不需要再进行维护
对0号结点,和交换1号交换
在这里插入图片描述
同理,1号发生改变,维护其子树,和4号结点交换
在这里插入图片描述
再维护4号的子树,和9号交换
在这里插入图片描述
这样我们就把一个乱序序列构建成了一个堆

排序
大顶堆的根结点一定是最大值,将其与最后一个结点进行交换再把最后一个结点从堆里拿出来,这样就把当前堆里的最大值归位了

在这里插入图片描述
同样的,根结点发生改变就对整个堆进行维护
在这里插入图片描述
这样就保证根结点总是最大值
重复以上操作
最大值归位
在这里插入图片描述
维护堆
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这样就把一个乱序序列通过堆排序成功排好序了

三、算法代码

void Swap(int a[], int i, int j)
{
    int temp;
    temp = a[i];
    a[i] = a[j];
    a[j] = temp;
}

void Heapify(int a[], int n, int i) //维护堆
{
    //最大值及左右孩子的编号
    int max = i;
    int lchild = 2*i + 1;
    int rchild = 2*i + 2;

    if(lchild < n && a[lchild]>a[max])
        max = lchild;
    if(rchild < n && a[rchild]>a[max])
        max = rchild;
    if(max != i)  //若最大值不是指定结点则交换,并对子树进行维护
    {
        Swap(a, i, max);
        Heapify(a, n, max);
    }
}

void BuildHeap(int a[], int n) //构建堆
{
    int i;
    for(i=n/2-1; i>=0 ; i--)
        Heapify(a, n, i);
}

void HeapSort(int a[], int n) //堆排序
{
    BuildHeap(a, n);
    int i;
    for(i=n-1; i>0; i--)
    {
        Swap(a, 0, i); //第0个和最后一个交换
        Heapify(a, i, 0); //交换后根节点变化,结点数减1
    }
}
//构建堆和排序的算法可整合到一个函数里
void HeapSort(int a[], int n) //堆排序
{
    int i;
    for(i=n/2-1; i>=0 ; i--)
        Heapify(a, n, i);
    for(i=n-1; i>0; i--)
    {
        Swap(a, 0, i); //第0个和最后一个交换
        Heapify(a, i, 0); //交换后根节点变化,结点数减1
    }
}

运行结果
在这里插入图片描述

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值