838.堆排序 做题笔记

堆排序概念补充
1.堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序, 它的最坏、最好、平均时间复杂度均为 O(nlogn), 它也是不稳定排序。

2.堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值, 这种情况称为大顶堆,注意:没有要求结点的左孩子的值和右孩子的值的大小关系。

3.每个结点的值都小于或等于其左右孩子结点的值, 这种情况称为小顶堆。

大顶堆举例说明:

 

小顶堆举例说明: 

 

本题可以使用堆排序,构造小顶堆,然后输出堆顶,输出后把堆顶和堆尾交换。重复执行m次即可。

如何手写一个堆?完全二叉树 5个操作
1. 插入一个数         heap[ ++ size] = x; up(size);
2. 求集合中的最小值   heap[1]
3. 删除最小值         heap[1] = heap[size]; size -- ;down(1);
4. 删除任意一个元素   heap[k] = heap[size]; size -- ;up(k); down(k);
5. 修改任意一个元素   heap[k] = x; up(k); down(k); 

分析:i为什么从n/2开始down?

首先要明确要进行down操作时必须满足左儿子和右儿子已经是个堆。

开始创建堆的时候,元素是随机插入的,所以不能从根节点开始down,而是要找到满足下面三个性质的结点:

1.左右儿子满足堆的性质。

2.下标最大(因为要往上遍历)

3.不是叶结点(叶节点一定满足堆的性质)

那这个点为什么是n/2?看图。

 

完整代码: 

#include<iostream>
using namespace std;
const int N=100010;
int h[N],siz;
//h[i] 表示第i个结点存储的值,i从1开始,2*i是左子节点,2*i + 1是右子节点
//size 既表示堆里存储的元素个数,又表示最后一个结点的下标
void down(int u){
    int t=u;//t存储三个结点中存在的最小的结点的下标,初始化为当前结点u
    if(u*2<=siz&&h[u*2]<h[t]) t=u*2;// 左子节点存在并且小于当前结点,更新t的下标
    if(u*2+1<=siz&&h[u*2+1]<h[t]) t=u*2+1;//右子节点存在并且小于当前结点,更新t的下标
    if(t!=u){//如果t==u意味着不用变动,u就是三个结点中拥有最小值的结点下标,否则交换数值
        swap(h[t],h[u]);
        down(t);//交换数值后,t这个结点存储原本u的值,u存储存储t的值(三个数中的最小值)。u不用调整了,但t情况不明,可能需要调整。直到它比左右子节点都小
    }
}
int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>h[i];
    siz=n;//初始化size,表示堆里有n个元素
    for(int i=n/2;i>0;i--) down(i);//把堆初始化成小根堆,从二叉树的倒数第二行开始,把数字大的下沉
    while(m--){
        cout<<h[1]<<" ";
        h[1]=h[siz];
        siz--;
        down(1);
    }
    return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值