题目描述
输入一个长度为 n 的整数数列,从小到大输出前 m小的数。
输入格式
第一行包含整数 n和 m。
第二行包含 n个整数,表示整数数列。
输出格式
共一行,包含 m个整数,表示整数数列中前 m小的数。
数据范围
1≤m≤n≤105 ,
1≤数列中元素≤109
输入样例:
5 3
4 5 1 3 2
输出样例:
1 2 3
前置知识
性质:
(1)堆是一颗采用顺序存储结构的完全二叉树;
(2)堆的根结点是关键字序列中的最小(或最大)值,称为小(或大)根堆;
(3)从根结点到每一叶子上的元素组成的序列为非递减(或非递增)序列;
(4)堆下的子树也是堆。
题目分析
规定从数组下标1开始进行存储,这样子做便于查找左右儿子,即结点i的左儿子为2 * i ,右儿子为2 * i + 1 。
操作主要分为down()、up()、删除、添加、修改和输出最小(或大)操作:
(1)down()操作:从上至下调整堆;
(2)up()操作:从下至上调整堆;
(3)删除操作:
1. 删除最小值:从数组最末端取元素替换堆顶元素,再down();
2. 删除任意值: 从数组最末端取元素替换该位置元素,再down()或up();
(4)添加操作:在数组末端添加元素,再up();
(5)修改操作:修改完元素后,再down()或up();
(6)输出最值:输出堆顶元素值,然后再执行删除操作。
在进行建堆的过程中,由树高为log2n,每次调整堆的时间复杂度为O(log2n),一共需要插入n个结点,故时间复杂度为O(nlog2n)。
可通过对其进行优化,将时间复杂度降为O(n)。
选用的方式是从树的倒数第二层开始自底向上递归的建堆,由于每次一层堆都是在下面层已经建立好的基础上构建出来的因此,可保证自下而上在建堆时,每个儿子已经是建立完整的堆。
设整个树的结点数量为n,
除最后一层外,其余上层元素总和为n / 2。倒数第二层的元素个数为n / 4,至最后一层需要调整堆的层数为1层。倒数第三层的元素个数为n / 8,至最后一层需要调整堆的层数为2层。 倒数第四层的元素个数为 n / 16,至最后一层需要调整堆的层数为3层…
数学证明:
设调整次数为T,结点数量为n,则
得到一个等差和等比结合的数列,对其进行研究,令
让 ⑵ - ⑴ 可得
因此,可知
故从n/2开始建堆,时间复杂度将被优化为为O(n)
算法实现
#include <stdio.h>
const int N = 1e5 + 10;
int h[N], size;
void swap(int &a, int &b) { int tmp = a; a = b; b = tmp; }
void down(int u){
int t = u;
// 找到u、u*2和u*2 + 1中最小的那个元素
if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2;
if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if(t != u){ // 若根结点不是最小的元素,则与其最小的元素进行交换
swap(h[u], h[t]);
down(t);
}
}
void up(int u){
while(u / 2 && h[u / 2] > h[u]){
swap(h[u / 2], h[u]);
u /= 2;
}
}
int main(){
int n, m; scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d", &h[i]);
size = n;
// 建堆,用此方法可将时间复杂度从O(nlog n)优化至O(n)
for(int i = n / 2; i; i--) down(i); // 从倒数第二层开始建堆,由下至上递归,可保证下方的树均为建好的树
//for(int i = 1; i <= n; i++) up(i);
while(m--){
printf("%d ", h[1]); // 输出堆顶元素
h[1] = h[size--]; // 将树中最后一个结点覆盖根结点
down(1); // 调整堆
}
return 0;
}
无注释代码
#include <stdio.h>
const int N = 1e5 + 10;
int heap[N], size;
void swap(int &a, int &b) { int tmp = a; a = b; b = tmp; }
void down(int u){
int t = u;
if(u * 2 <= size && heap[u * 2] < heap[t]) t = u * 2;
if(u * 2 + 1 <= size && heap[u * 2 + 1] < heap[t]) t = u * 2 + 1;
if(u != t){
swap(heap[u], heap[t]);
down(t);
}
}
void up(int u){
while(u / 2 > 0 && heap[u / 2] > heap[u]){
swap(heap[u / 2], heap[u]);
u /= 2;
}
}
int main(){
int n, m; scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d", &heap[i]);
size = n;
for(int i = n / 2; i; i--) down(i);
//for(int i = 1; i <= n; i++) up(i);
while(m--){
printf("%d ", heap[1]);
heap[1] = heap[size--];
down(1);
}
return 0;
}
图示过程参考:
1.7 堆排序