堆是什么?堆是一种特殊的完全二叉树。

所有父结点都比子结点要小的完全二叉树我们称为最小堆。反之,如果所有父结点都比子结点要大,这样的完全二叉树称为最大堆。

【堆的创建】

把n个元素建立一个堆,首先我们可以将这n个结点以自顶向下、从左到右的方式从1到n编码,即以层序遍历的顺序编码,这样就可以把这n个结点转换成为一棵完全二叉树。紧接着从最后一个非叶结点(结点编号为n/2)开始到根结点(结点编号为1),逐个扫描所有的结点,根据需要将当前结点向下调整,直到以当前结点为根结点的子树符合堆的特性。

#include <stdio.h>
#define maxn 105
int h[maxn]; //用来存放堆
int n; //堆的大小
//交换堆中的两个元素的值
void swap(int x,int y)
{
    int t=h[x];
    h[x]=h[y];
    h[y]=t;
}
//向下调整成最小堆
void siftdown(int pos)
{
    int t,flag=0; //flag用来标记是否需要继续向下调整
    while(!flag)
    {
        int t=pos; //用t记录父结点和左右儿子中值较小的结点编号
        if(pos*2<=n&&h[t]>h[pos*2]) t=pos*2;
        if(pos*2+1<=n&&h[t]>h[pos*2+1]) t=pos*2+1;
        //如果最小的结点不是父结点
        if(t!=pos)
        {
            swap(t,pos);
            pos=t;
        }
        else flag=1;
    }
}
//建堆
void create()
{
    //从最后一个非叶结点到第1个结点依次进行向下调整
    for(int i=n/2;i>=1;i--)
        siftdown(i);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&h[i]);
    create();
    for(int i=1;i<=n;i++) printf(i==n?"%d\n":"%d ",h[i]);
    return 0;
}

【堆的插入与删除】

若最小(大)堆要进行删除最小(大)数并返回最小(大)数的操作,我们只需要删掉堆顶元素即最小(大)数,将最后一个数放在堆顶,通过比较与左右儿子的大小向下调整即可恢复为最小(大)堆。
最小堆为例

//向下调整成最小堆
void siftdown(int pos)
{
    int t,flag=0; //flag用来标记是否需要继续向下调整
    while(!flag)
    {
        int t=pos; //用t记录父结点和左右儿子中值较小的结点编号
        if(pos*2<=n&&h[t]>h[pos*2]) t=pos*2;
        if(pos*2+1<=n&&h[t]>h[pos*2+1]) t=pos*2+1;
        //如果最小的结点不是父结点
        if(t!=pos)
        {
            swap(t,pos);
            pos=t;
        }
        else flag=1;
    }
}
//返回并删除最大的元素
int deletemax()
{
    int t=h[1]; 
    h[1]=h[n]; //将堆的最后一个点赋值到堆顶
    n--; //堆的元素减少1
    siftdown(1); //向下调整
    return t; //返回最大值
}

若最小(大)堆要进行删除最小(大)数并插入一个新的数的操作,我们只需要删掉堆顶元素即最小(大)数,将新的数放在堆顶,通过比较与左右儿子的大小向下调整即可恢复为最小(大)堆。

若最小(大)堆只需进行插入一个新的数,我们只需要把新的数放在末尾,通过与父亲的比较向上调整即可恢复最小(大)堆。

下面以最小堆为例。

//向上调整成最小堆
void siftup(int pos) 
{
    int flag=0; //用来标记是否需要继续向上调整
    if(pos==1)  return; //如果是堆顶,就不需要调整了    
    //不在堆顶 并且 当前结点pos的值比父结点小的时候继续向上调整 
    while(pos!=1&&!flag)
    {
        //判断是否比父结点的小 
        if(h[pos]<h[pos/2]) swap(pos,pos/2); 
        else flag=1;
        pos=pos/2; //更新编号pos为它父结点的编号 
    }
}

【堆排序(Heap Sort)】

堆排序在寻找最小值(或最大值)的过程中使用了堆这种数据结构,提高了效率。堆是一个近似二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

那么我们要如何实现堆排序呢?下面以升序排序为例讲解。

算法一:

将需要排序的序列建堆,调整成最小堆。

每次删除并输出堆顶结点的值即最小值,再将最后一个数放置于堆顶结点的位置,最小堆的大小-1,向下调整成最小堆。重复上述步骤直到堆为空。

算法二:

将需要排序的序列建堆,调整成最大堆。

每次把堆顶结点的值即最大值与最后一个结点交换位置,最大堆的大小-1,向下调整成最大堆。重复上述步骤直到结束。

最后层序遍历输出堆排序后的堆,即为升序序列。

下面为算法二的代码,以升序堆排序为例:

#include <stdio.h>
#define maxn 105
int h[maxn];
int n;
//交换函数
void swap(int x,int y)
{
    int t=h[x];
    h[x]=h[y];
    h[y]=t;
}
//向下比较调整成最大堆
void siftdown(int pos,int num) 
{
    int t,flag=0; //flag用来标记是否需要继续向下调整
    while(!flag)
    {
        int t=pos; //用t记录父结点和左右儿子中值较大的结点编号
        if(pos*2<=num&&h[t]<h[pos*2]) t=pos*2;
        if(pos*2+1<=num&&h[t]<h[pos*2+1]) t=pos*2+1;
        //如果最大的结点不是父结点
        if(t!=pos)
        {
            swap(t,pos);
            pos=t;
        }
        else flag=1;
    }
}
void create()
{
    //从最后一个非叶结点到第1个结点依次进行向下调整
    for(int i=n/2;i>=1;i--)
        siftdown(i,n);
}
//堆排序(升序)
void heapSort()
{
    create(); //建堆
    int num=n; 
    for(int i=n;i>1;i--)
    {
        swap(1,i); //交换最大值与最后一个数
        num--;
        siftdown(1,num); //前num个数调整成最大堆
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&h[i]);
    heapSort();
    for(int i=1;i<=n;i++) printf(i==n?"%d\n":"%d ",h[i]);
    return 0;
}

堆排序是不稳定的排序。最差时间复杂度为O(nlogn),平均时间复杂度为O(nlogn)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值