此中拿最小堆來舉例
(一)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個數;