本文介绍堆排序的实现方法,题目选择洛谷的 P1177。

本题使用小根堆,先把所有的元素都 push 进去,然后每次取出堆顶输出并弹掉堆顶,直到堆空为止。

堆的实现需要两个修复操作。当把结点插入堆时,首先将结点放到尾部,这时可能不满足小根堆的性质,因此需要自底向上修复堆。修复的边界条件是 x 已经是根结点或者 x 大于他的父结点。

当删除堆顶元素时,将堆顶元素和尾部元素交换,删除尾部元素,这时也可能不满足小根堆的性质,因此需要自顶向下修复堆。修复的边界条件是 x 已经是叶子结点或者 x 小于他的儿子中的最小值。

堆排序整体的时间复杂度为 O(nlogn),空间复杂度为 O(n)。

#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;

vector<int> w;
int tot = 0; // 目前堆中有多少个结点

// 返回最小值
int top() {
    return w[0];
}

// 插入时,修复 x 号结点
void modify(int x) {
    // x 为根,或已符合小根堆的性质
    if (x == 0 || w[x] > w[(x - 1) / 2]) {
        return;
    }
    swap(w[x], w[(x - 1) / 2]);
    modify((x - 1) / 2);
}

// 加入元素
void push(int x) {
    tot++;
    w[tot - 1] = x;
    // 自底向上修复
    modify(tot - 1);
}

// 删除时,修复 x 号结点
void repair(int x) {
    // x 已经是叶子结点
    if (x * 2 + 1 >= tot) {
        return;
    }
    int tar = x * 2 + 1;
    if (x * 2 + 2 < tot) {
        tar = w[x * 2 + 1] < w[x * 2 + 2] ? x * 2 + 1 : x * 2 + 2;
    }
    // tar 是 x 子结点中 key 最小的,违反小根堆的性质
    if (w[x] > w[tar]) {
        swap(w[x], w[tar]);
        repair(tar);
    }
}

// 删除堆顶
void pop() {
    // 与尾部结点交换,然后删掉尾部
    swap(w[0], w[tot - 1]);
    tot--;
    // 自顶向下修复
    repair(0);
}

int main() {
    ios::sync_with_stdio(false);
    int n;
    cin >> n;
    w.resize(n);
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        push(x);
    }
    for (int i = 0; i < n; i++) {
        int x = top();
        cout << x << " ";
        pop();
    }
    cout << '\n';
}