最大最小堆的應用_legend

此中拿最小堆來舉例

(一)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個數;

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章