输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
类似堆排序的思路,维护一个具有k个元素的大顶堆,然后将剩余n-k个数依次与堆顶比较,如果大于堆顶则跳过,否则删除堆顶,将其替换为比起小的当前数,然后调整堆为大顶堆,最后大顶堆中的k个数即为所求数。
大顶堆的特点:
父节点大于等于子节点(但是两个子节点之间的大小关系没有要求),这样可以做到沿着每条从根节点到叶子节点的路径,元素的键值都是以非升序排列的。 堆是一个几乎完全的二叉树,所以具有和完全二叉树一样的特点,即一般是存储在一个数组A[n]中,A[i]的左子节点在A[2i]中,右子节点在A[2i+1]中(当他们存在的时候),A[i]的父亲节点在A[i/2]中(如果存在,i/2向下取整)。
当某个节点(H[i])的值大于他的父亲节点的值时,需要通过SITF-UP将这个节点 沿着从H[i]到H[1]这条唯一的路径 上移到合适的位置以形成一个合格的堆。
将H[i]与其父亲节点H[i/2]比较,如果H[i]大于H[i/2],则将H[i]与H[i/2]互换,直到H[i]没有父节点或者H[i]不大于H[i/2]。
int SiftUp(int *H, int i) {
while (true) {
if (i == 1) {
break;//说明当前i是根节点
}
if (H[i] > H[(int) i / 2]) {//如果当前节点比父亲节点大
int t;
t = H[i];
H[i] = H[(int) i / 2];
H[(int) i / 2] = t;
i = i / 2;
} else {
break;
}
}
return 0;
}
当某个节点(H[i],i<=(int)n/2即 非叶子节点 )的值小于它的两个子节点H[2i]和H[2i+1](如果存在的话)的最大值时,需要将SIFT-DOWN将渗到合适的位置。
将H[i]与其两个子节点中值最大的元素比较,如果小于最大的那个节点,则将H[i]与其最大的那个子节点互换。
int SiftDown(int *H, int i, int n) {
while (true) {
i = 2 * i;
if (i > n) {
break;
}
if (i + 1 <= n) {
if (H[i + 1] > H[i]) {//比较两个子节点哪个最大
i++;
}
}
if (H[i] > H[(int) i / 2]) {
int t;
t = H[i];
H[i] = H[(int) i / 2];
H[(int) i / 2] = t;
}
}
}
给出一个有n个元素的数组H[1….n],创建一个包含这些元素的堆。
类似于分治,首先,H的叶子节点(即最下面的一层单个元素)可以认为是若干个小堆,然后我们从倒数第二层开始,将倒数第二层和倒数第一层的元素进行适当调整,使得调整之后整个二叉完全数的最后两层是若干个子堆,按照这个思路,依次向上走,最终走到第1层的时候就可以保证整个完全二叉树是一个符合要求的堆。
需要注意的是对于一个完全二叉树, 倒数第二层的最后一个元素的下标为int(n/2) ,(因为倒数第二层的最后一个节点的下标x应该满足x*2=n).
senta(n)
void makeheap(int *H,int n){
for (int i = n/2; i >=1 ; --i) {//从倒数第二层到第一层
SiftDown(H,i,n);
}
}