堆排序

# 堆是一棵二叉树 或是一棵 [[完全二叉树]]

这里是小根堆(性质:每一个点都是小于等于左右儿子的)
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1e6 + 10;

int n, m;
int h[N], cnt;

void down(int u){
    int t = u;
    if(u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
    if(u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
    if(u != t){
        swap(h[u], h[t]);
        down(t);
    }
}

int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d", &h[i]);
    cnt = n;
    for(int i = n / 2; i; i--) down(i);
    while(m--){
        printf("%d ", h[1]);
        h[1] = h[cnt];
        cnt--;
        down(1);
    }
    return 0;
}

![[Pasted image 20221221111241.png]]
![[Pasted image 20221221111338.png]]

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1e6 + 10;

int n, m;
int h[N], si;

void down(int u){
    int t = u;
    if(u * 2 <= si && h[u * 2] < h[t]) t = u * 2;//和左下节点比较
    if(u * 2 + 1 <= si && h[u * 2 + 1] < h[t]) t = u * 2 + 1;//和右下节点比较
    if(u != t){
        swap(h[u], h[t]);
        down(t);//递归循环down
    }//此时判断堆顶是不是最小值
}

void up(int u){
    while(u / 2 && h[u / 2] > h[u]){
        swap(h[u / 2], h[u]);
        u /= 2;
    }
}

int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d", &h[i]);
    si = n;
    
    for(int i = n / 2; i; i--) down(i);
    
    while(m--){
        printf("%d ", h[1]);
        h[1] = h[si];
        si--;
        down(1);
    }
    
    return 0;
}

# i 为什么从 n/2 开始 down?

我认为从 n/2 开始,还有一个角度可以理解,因为 n 是最大值,n/2 是 n 的父节点,因为 n 是最大,所以 n/2 是最大的有子节点的父节点,所以从 n/2 往前遍历,就可以把整个数组遍历一遍
1.n/2:就是从最后一个有儿子节点的父节点向前遍历,依次让每个结点找到其合适的位置
2.down(x)是一个递归函数,其含义是为一个相对‘大’的数向下找到其在树中合适的位置,递归式理解的关键

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

开始创建堆的时候,元素是随机插入的,所以不能从根节点开始 down,而是要找到满足下面三个性质的结点:
1. 左右儿子满足堆的性质。
2. 下标最大(因为要往上遍历)
3. 不是叶结点(叶节点一定满足堆的性质)
那这个点为什么时 n/2?看图。

![[Pasted image 20221221114731.png]]