堆及堆排序算法(算法导论)

堆是一种二叉树,分为最大堆和最小堆。
最大堆:所有的父亲节点都大于等于其儿子节点,所以根节点最大;
最小堆:所有的父亲节点都小于等于其儿子节点,所以根节点最小;
在这里仅仅考虑最大堆。

如图是一个最大堆:
这里写图片描述

绿色数字为角标 i,而圆圈里的数字为A[ i ]的值。
堆的几个性质:
A[ i ]的父亲节点是A[i/2],左儿子节点是A[2 * i],右儿子节点是A[2 *i+1];
除了最底层外,该树是全满的;
堆的建立与排序均为原址排序,不占用多余空间;
堆的排序复杂度为:O(nlgn),也就是说,堆排序同时具有插入排序和归并排序的优点而没有它们的缺点。

堆的修正
自上而下,如果有儿子节点比父节点大,将最大的儿子节点与父节点互换,并更新父节点重复这个过程直到没有比父节点大的儿子节点。
这里写图片描述

建立堆
由于修正的过程是自上而下,所以堆的建立,也就是堆全树的修正,需要自下而上,才能保证一次到位。也只有自下而上的修正,才能使堆成为一棵全满的树。
这里写图片描述

而这个起点“下”,正好是(int)(n/2),不论n是奇数还是偶数。
自下而上能够保证每一次修正之后该点之下的结构都符合最大堆的性质。

堆排序
由于最大堆只能确定最大值(根节点),因此我们把根节点提出来,对其余的点再做一次一次修正,就得到了第二大的值(新最大堆的根节点),以此反复,最终完成全部排序。
这里写图片描述

这里写图片描述

完成这个排序的关键是约束Heapsize的大小,从N到2(当最后只剩下一个数时,它必定是最小数).

堆排序的代码

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
using namespace std;

#define Left(x) 2*x
#define Right(x) 2*x+1

int N,Heap_size;

void EXCHANGE(int*Heap,int x,int y)
{
    int key=Heap[y];
    Heap[y]=Heap[x];
    Heap[x]=key;
}

void MAX_HEAPIFY(int*Heap,int x)//修正函数,将角标为x的那个节点移动到合适的位置
{
    int L=Left(x),R=Right(x),LGst=x;
    if(L<=Heap_size && Heap[L]>Heap[x]) LGst=L;
    if(R<=Heap_size && Heap[R]>Heap[LGst]) LGst=R;
    if(LGst!=x && LGst<=Heap_size)
    {
        EXCHANGE(Heap,x,LGst);
        MAX_HEAPIFY(Heap,LGst);
    }
}

void BUILD_MAX_Heap(int*Heap)//建立堆的函数,即对所有小于N/2的节点依此做一次修正
{
    for(int i=(N/2);i>=1;i--)
            MAX_HEAPIFY(Heap,i);
}

void Heap_Sort(int*Heap)//排序,关键是控制好新堆的大小Heap_size从N到2
{
    Heap_size=N;
    for(int i=Heap_size;i>=2;i--)
    {
        EXCHANGE(Heap,1,i);
        Heap_size--;
        MAX_HEAPIFY(Heap,1);
    }
}

void OUTPUT(int*Heap)
{
    for(int i=1;i<=N;i++)
        printf("%d  ",Heap[i]);
    printf("\n");
}

int main()
{
    while(scanf("%d",&N)!=EOF && N)
    {
        int*Heap=(int*)malloc(sizeof(int)*(2*N+3));//申请两倍多的空间是为了防止在访问儿子节点时越界
        fill(Heap,Heap+(2*N+3),0);
        for(int i=1;i<=N;i++)
            scanf("%d",&Heap[i]);
        Heap_size=N;
        BUILD_MAX_Heap(Heap);
        OUTPUT(Heap);
        Heap_Sort(Heap);
        OUTPUT(Heap);
    }
    return 0;
}

有什么建议或疑问欢迎留言。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值