堆的实现、堆排序、优先队列

本文介绍了堆的两种实现——小根堆和最大堆,以及对应的堆排序方法。小根堆用于第一种堆排序,通过创建小根堆并不断调整,将数据从小到大排序。最大堆则用于第二种排序,通过大根堆的特性,将最大元素放到末尾,实现排序。同时,堆作为优先队列的实现,支持插入和查找最大(小)值元素的操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、小根堆的实现、第一种堆排序

  1. 输入一组数据;
  2. 创建一个小根堆:从最后一个非叶子结点到第1个结点依次进行向下调整;
  3. 新增一个值:将新元素插入到末尾,再根据情况判断新元素是否需要上移,直到满足堆的特征为止;
  4. 【第一种堆排序】从小到大排序:对于小根堆,每次将顶部元素输出并删除顶部元素(最后一个元素移至顶部,堆的大小减1,继续向下调整),直到堆空。

测试示例
14
99 5 36 7 22 17 46 12 2 19 25 28 1 92
Y
55

#include <stdio.h>

int h[101];//用来存放堆的数组
int n;//用来存储堆中元素的个数,也就是堆的大小

//交换函数,用来交换堆中的两个元素的值
void swap(int x,int y){
    int t=h[x];
    h[x]=h[y];
    h[y]=t;
}

//向下调整函数
void siftdown(int i){//传入一个需要向下调整的结点编号i.这里传入1,即从堆的定点开始向下调整
    int t,flag=0;//flag用来标记是否需要继续向下调整
    //当i结点有儿子(至少有左儿子)并且有需要继续调整的时候循环就执行
    while(i*2<=n&&flag==0){
        //首先判断它和左儿子的关系,并且用t记录较小的结点编号
        if(h[i]>h[i*2])
            t=i*2;
        else
            t=i;
        //如果它有右儿子,再对右儿子进行讨论
        if(i*2+1<=n)
            if(h[i*2+1]<h[t])
                t=i*2+1;
        //如果最小的结点编号不是自己,说明子结点有比编号比父结点小的
        if(t!=i){
            swap(t,i); //交换它们
            i=t;//更新i为刚才与它交换的儿子结点的编号,以便继续向下调整
        }
        else
            flag=1;//否则说明当前的父结点已经比两个子结点都要小了了,不需要继续调整了
    }
}

void siftup(int i){//传入一个需要向上调整的结点编号i
    int flag=0;//用来标记是否需要继续向上调整
    if(i==1)    return;//如果是堆顶就返回
    //不是堆顶,并且当前结点比父结点小就继续向上调整
    while(i!=1&&flag==0){
        //判断是否比父结点小
        if(h[i]<h[i/2])
            swap(i,i/2);//交换它和它爸爸的位置
        else
            flag=1;//表示已经不需要继续调整了
        i=i/2;//更新编号i为它父结点的编号,从而便于下一次继续向上调整
    }
}

//建立堆的函数
void creat(){
    int i;
    //从最后一个非叶子结点到第1个结点依次进行向下调整
    for(i=n/2;i>=1;i--)
        siftdown(i);
}

//删除最大的数
int deletemax(){
    int t=h[1];//用一个临时变量记录堆顶点的值
    h[1]=h[n];//将堆的最后一个点赋值到堆顶
    n--;//堆的元素减少1;
    siftdown(1);//向下调整
    return t;//返回之前记录的堆的顶点的最小值
}
int main()
{
    int i,num;
    //读入要排序的数字的个数
    scanf("%d",&num);
    n=num;
    for(i=1;i<=num;i++)
        scanf("%d",&h[i]);
    //建堆
    creat();
    //增加一个新元素
    printf("是否增加一个新元素(Y/N): ");
    char ch;
    getchar();
    scanf("%c",&ch);
    if(ch=='Y'){
        num++;n++;
        scanf("%d",&h[n]);
        siftup(n);
    }
    //删除顶部元素,连续删除n次,其实就是从小到大把数输出来
    printf("排序后:\n");
    for(i=1;i<=num;i++)
        printf("%d ",deletemax());
    return 0;
}

二、最大堆、第二种堆排序

  1. 输入一组数据;
  2. 创建一个大根堆:从最后一个非叶子结点到第1个结点依次进行向下调整;
  3. 【第二种排序】从小到大排序:建立大根堆,因为希望从小到大排序,所以最大的放在最后。每次将h[1]和h[n]交换,堆大小减1(n–),然后h[1]向下调整,直至堆的大小为1。

测试示例
14
99 5 36 7 22 17 46 12 2 19 25 28 1 92

#include <stdio.h>

int h[101];//用来存放堆的数组
int n;//用来存储堆中元素的个数,也就是堆的大小

//交换函数,用来交换堆中的两个元素的值
void swap(int x,int y){
    int t=h[x];
    h[x]=h[y];
    h[y]=t;
}

//向下调整函数
void siftdown(int i){//传入一个需要向下调整的结点编号i.这里传入1,即从堆的定点开始向下调整
    int t,flag=0;//flag用来标记是否需要继续向下调整
    //当i结点有儿子(至少有左儿子)并且有需要继续调整的时候循环就执行
    while(i*2<=n&&flag==0){
        //首先判断它和左儿子的关系,并且用t记录较大的结点编号
        if(h[i]<h[i*2])
            t=i*2;
        else
            t=i;
        //如果它有右儿子,再对右儿子进行讨论
        if(i*2+1<=n)
            if(h[i*2+1]>h[t])
                t=i*2+1;
        //如果最小的结点编号不是自己,说明子结点有比编号比父结点大的
        if(t!=i){
            swap(t,i); //交换它们
            i=t;//更新i为刚才与它交换的儿子结点的编号,以便继续向下调整
        }
        else
            flag=1;//否则说明当前的父结点已经比两个子结点都要大了,不需要继续调整了
    }
}

//建立堆的函数
void creat(){
    int i;
    //从最后一个非叶子结点到第1个结点依次进行向下调整
    for(i=n/2;i>=1;i--)
        siftdown(i);
}

//堆排序
void heapsort(){
    while(n>1){
        swap(1,n);
        n--;
        siftdown(1);
    }
}

int main()
{
    int i,num;
    //读入要排序的数字的个数
    scanf("%d",&num);
    n=num;
    for(i=1;i<=num;i++)
        scanf("%d",&h[i]);
    //建堆
    creat();
    //堆排序
    heapsort();
    //输出
    printf("排序后:\n");
    for(i=1;i<=num;i++)
        printf("%d ",h[i]);
    return 0;
}

三、优先队列

     像这样支持插入元素和寻找最大(小)值元素的数据结构称为优先队列。而堆就是优先队列的实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值