此中拿最小堆来举例

(一)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个数;