此中拿最小堆来举例
(一)n个数中取出最小的k个数;
(二)n个数中取出最大的k个数(TOPK 问题);
(三) 优先级队列
**********************************
(1)n个数中取出最大的k个数(TOPK问题):
分析:
用前k个数构建大小为K的最小堆;那么堆顶的元素意味着这k个数的最小值;
为了留下最大的k个数,则后续的n-k个数,每个数和堆顶的值(堆中的最小值)进行比较;
当前值比堆顶值大,则当前值和堆顶替换,调整堆;
注意:有些类似于n个数找最大值,一般是每个数和最大值进行比较,当前值比最大值大,则替换最大值;
比如:
100亿个数中找出最大的前k个数(海量数据topk问题)
思路分析:乍一看,100亿个数,确实很大,一个数占四个字节,那么100亿个数就需要40G的存储空间,这对与普通电脑来说确实是不可能的。但是,这道题肯定不可能让我们创建一个具有100亿个数据的堆,这样不说存储空间不够大,时间复杂度也是很大的;
正确·的做法是,
1.根据前k个数 ,创建一个有k个元素的小堆
2.利用循环,剩余的每个数和第一个元素进行比较:
如果这个数比根节点都小,那就直接舍弃;否则进行该数替换堆顶的数,然后进行调整,直到这个堆里面所有的数据都是你要找的最大的那几个数为止,这样再进行打印就可以了 ;
3. 代码实现如下:
#include"Heap.h"
void MakeHeap(DataType* a, size_t n)//构建堆
{
int i=(n-1)>>1;
for(;i>=0;i--)
{
AdjustDown(a,n,i);
}
}
/*
以某个节点为根节点往下进行调整;
注:往下调整一般是构建最小堆,或者取走堆顶元素,需要往下调整;
其他:
此中最小堆是用数组表示的二叉树;
a: 数组;
n:数组的大小;
root: 从某处开始为跟开始往下调整;
*/
void AdjustDown(DataType* a, size_t n, int root)//向下调整
{
int parent =root;
int child=2*parent+1;
while(child<n)
{
if((child+1)<n&&a[child+1]<a[child])//右孩子存在且右孩子大于左孩子
{
++child;//指向右孩子
}
if(a[child]<a[parent])
{
// 构建最小堆时,父节点比左右孩子节点中较小者要大,则替换父节点和孩子中较小节点的值;
// 然后孩子节点成为父节点,再找孩子节点的子节点和自己比较;
DataType tmp;
tmp=a[child];
a[child]=a[parent];
a[parent]=tmp;
parent=child;
child=2*parent+1;
}
else
{
break;
}
}
}
/*
向上调整,一般用于最小堆构建完成之后,还需要往堆中插入元素;
说明:
a:堆表示的数组;
n:数组的大小;
child: 新插入的元素的位置;
*/
void AdjustUp(DataType* a, size_t n, int child)//向上调整
{
int parent=(child-1)>>1;
while(child>0)
{
if(a[child]<a[parent])
{
DataType tmp;
tmp=a[child];
a[child]=a[parent];
a[parent]=tmp;
child=parent;
parent=(child-1)>>1;
}
else
{
break;
}
}
}
void TopK(DataType* a, size_t n, size_t k)
{
int i;
MakeHeap(a,k);//建小堆
for(i=k;i<n;i++)
{
a[0]=a[i];
AdjustDown(a,k,0);
}
for(i=0;i<k;i++)
{
printf("%d ",a[i]);
}
-------------
(二)优先级队列
(2.1)背景
知道普通队列是:先进先出
而 优先队列:出队顺序和入队顺序无关;和优先级相关
实际生活中有很多优先队列的场景,如医院看病,急诊病人是最优先的,虽然这一类病人可能比普通病人到的晚,但是他们可能随时有生命危险,需要及时进行治疗. 再比如 操作系统要"同时"执行多个任务,实际上现代操作系统都会将CPU的执行周期划分成非常小的时间片段,每个时间片段只能执行一个任务,究竟要执行哪个任务,是有每个任务的优先级决定的.每个任务都有一个优先级.操作系统动态的每一次选择一个优先级最高的任务执行.要让操作系统动态的选择优先级最高的任务去执行,就需要维护一个优先队列,也就是说所有任务都会进入这个优先队列.
(2.2)队列和优先队列的区别:
1)队列:
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)
2)优先队列:
优先队列的规则不是先进先出了、而是优先级高的排在前面、插入一个元素,根据其优先级将其插入到堆的合适的位置;
常常用建立大堆的方法解决在一个数组中查找。堆排序的时间复杂度为nlogn。
(2.3) 优先队列的思想
优先队列:一般使用的是最大堆,即堆顶的元素的优先级最高,一般需要最新被执行;
1)可以自定义比较函数,来实现优先级的比较;
2)先被pop的通常是优先级最高的;
--------------
(三)n个数中取出最小的k个数
构建大小为n的最小堆,然后从从堆顶取k次,就得到了最小的k个数;