(2)排序之堆排序

准备工作


1.堆的概念:

堆通常是一个可以被看做一棵树的数组对象。在队列中,调度程序反复提取队列中第一个作业并运行,因而实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权。堆即为解决此类问题设计的一种数据结构。(wiki上的定义)


在堆排序中,我们用到的是二叉堆(是一棵二叉树的一种)。


2.最大堆(属于二叉堆):

每个父结点的值都大于它的左右两个子结点的值,在根节点存储着堆里所有数的最大值。


堆排序

1.堆修复:
假设现在我们已经得到了一个堆,这个堆除了根节点外均符合最大堆的定义。
   比如:            7(该点不满足条件) 
                       /    \
                     8      5
                   /   \    /  \
                 6    2  1   3
               /   \

              0   4

此时我们要将其修复成一个最大堆,叫做堆修复。

设堆存在数组a[]中,下标从1开始,共有 n 个节点。

我们先比较一下根节点和其左右儿子的大小,找出最大的那个数的下标largest,有两种情况:

(1)largest 不为根节点(即是左右儿子中的一个)

         此时,root (根节点)和 a[largest]交换,之后对largest继续修复(采用递归)。

(2)largest是根节点,此时表明修复堆已经完成。

这样是不是堆就修复完了呢?  当然不是。

注意到我们如果修复到了叶子节点,此时该修复工作也完成了,所以当左右某个儿子的下标大于了n时,本次修复结束(我们是采用递归来修复的,所以本次修复结束不意味着总修复结束)。


接下来我们就可以用代码实现了:

void repair_heap(int a[], int pos, int sz)
{
    int l = pos * 2;
    int r = pos * 2 + 1;
    int largest;

    if(l <= sz && a[l] > a[pos])
        largest = l;
    else largest = pos;

    if(r <= sz && a[r] > a[largest])
        largest = r;

    if(largest != pos)
    {
        swap(a[largest], a[pos]);
        repair_heap(a, largest, sz);
    }
}


不难看出,这一步的复杂度为O(lgn).

sz(size)的作用在后面我们会看到。
2.建立最大堆:
建立最大堆我们也是采用的递归方式。建堆其实是个比较容易的过程,按数组元素的初始顺序直接建立就好。
将堆修复成最大堆也不难:我们从叶子节点开始,逐一向上修复堆。

代码实现:
void build_maxheap(int a[], int pos)
{
    int l = pos * 2;
    int r = pos * 2 + 1;
    if( l <= n )
        build_maxheap( a, l );
    if( r <= n)
        build_maxheap( a, r );
    repair_heap(a, pos, n);
}


这一步的复杂度为 O(n).
3.排序:
我们建立了最大堆以后根节点的值就是最大值,我们将这个最大值存储后排出堆,再找剩下树的最大值,就可以得到第二大值,以此类推,我们将得到有序列。

怎么实现将最大值存储后再排出堆呢?

我们将第 n 个元素和 a[1] 交换,然后再让堆的节点数减1(这个就通过sz来实现,我们尽量不改变n)。

接下来我们从a[ 1 ]开始修复堆就行了,以此循环,最终达到排序的效果。


代码如下:

void heap_sort(int a[])
{
    int heapsize = n;
    while(heapsize > 1)
    {
        swap(a[heapsize--], a[1]);
        repair_heap(a, 1, heapsize);
    }
}
这一步复杂度为 O(nlgn)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值