模拟过程详见代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e5 + 10;
int a[N], sz = 0;
// 堆排序的down操作
// 每次找到当前结点和两个子节点的最小值
// 并将最小值置于放在当前结点的位置
// 每次把当前结点下沉成为down操作
void down(int u)
{
int t = u;
// 如果左儿子最存在并且左儿子的值小于当前结点值
if (u*2 <= sz && a[u*2] < a[t]) t = u*2;
// 如果右儿子存在并且右儿子小于当前结点值
if (u*2 + 1 <= sz && a[u*2 + 1] < a[t]) t = u*2 + 1;
// 此时t存放的就是当前三个结点中最小元素的下标
if (t != u) {
swap(a[t], a[u]);
down(t); // 递归操作下层最小值
}
}
int main()
{
int n,m;
cin >> n >> m;
sz = n;
for (int i = 1; i<=n; i++) scanf("%d", a+i);
// 时间复杂度位O(n)的建堆操作
// 每次从倒数第二层开始down操作,因为最后一层不需要进行down操作
// 一共有n个结点,最后层有n/2个结点, 倒数第二层有n/4个结点
/**
证明过程:
一共需要的down操作步骤为:
S = n/(2^2) * 1 + n/(2^3) * 2 + n/(2^4) * 3 + ....
故 2S = n/(2^1) * 1 + n/(2^2) * 2 + n/(2^3) * 3 + ....
错位相减可以得到:
S = n/(2^1) * 1 + n/(2^2) * 1 + n/(2^3) * 1 + n/(2^4) * 1 + ....
< 1 (等比数列求和) 故时间复杂度为O(n)
*/
// 建堆, 时间复杂度为O(n), 如果直接向堆中插入元素建堆时间复杂度就是O(nlogn)
for (int i = n/2; i ; i--) down(i);
// 输入前m个最小元素
while(m--) {
// 输出当前最小值
cout << a[1] << endl;
// 将最小值从堆中删除
a[1] = a[sz--];
down(1); // 递归
}
return 0;
}