PHP利用二叉堆實現TopK 演算法的方法詳解

2022-08-23 09:03:15 字數 2968 閱讀 7211

前言

在以往工作或者面試的時候常會碰到乙個問題,如何實現海量topn,就是在乙個非常大的結果集裡面快速找到最大的前10或前100個數,同時要保證 記憶體和速度的效率,我們可能第乙個想法就是利用排序,然後擷取前10或前100,而排序對於量不是特別大的時候沒有任何問題,但只要量特別大是根本不可能 完成這個任務的,比如在乙個陣列或者文字檔案裡有幾億個數,這樣是根本無法全部讀入記憶體的,所以利用排序解決這個問題並不是最好的,所以我們這裡就用 php去實現乙個小頂堆來解決這個問題.

二叉堆

二叉堆是一種特殊的堆,二叉堆是完全二叉樹或者是近似完全二叉樹,二叉堆有兩種,最大堆 和 最小堆,最大堆:父結點的鍵值總是大於或等於任何乙個子節點的鍵值;最小堆:父結點的鍵值總是小於或等於任何乙個子節點的鍵值

小頂堆-(來自網路)

二叉堆一般用陣列來表示(看上圖),例如,根節點在陣列中的位置是0,第n個位置的子節點分別在2n+1和 2n+2,因此,第0個位置的子節點在1和2,1的子節點在3和4,以此類推,這種儲存方式便於尋找父節點和子節點。

具體概念問題這裡就不在多說了,如果對二叉堆有疑問的可以在好好了解下這個資料結構,下面我們就針對上述topn問題來用php**實現並解決,為了看出區別這裡先用排序的方式去實現下看下效果如何。

利用快速排序演算法來實現 topn

//

為了測試執行記憶體調大一點

ini_set('memory_limit', '2024m'); //

實現乙個快速排序函式

function quick_sort(array

$array)

$key = $array[0];

for($i=1;$i

else }

$left_array = quick_sort($left_array);

$right_array = quick_sort($right_array);

return

array_merge($right_array,array($key),$left_array);}

//構造500w不重複數

for($i=0;$i

<5000000;$i++)

//打亂它們

shuffle($numarr);

//現在我們從裡面找到top10最大的數

var_dump(time

());

print_r(array_slice(quick_sort($all),0,10));

var_dump(time());

執行之後結果

可以看到上面列印出了top10的結果,並輸出了下執行時間,大概99s左右,但這只是500w個數且全部能裝入記憶體的情況,如果我們有乙個檔案裡面有5kw或5億個數,肯定就會有些問題了.

利用二叉堆演算法來實現 topn

實現流程是:

1、先讀取10個或100個數到陣列裡面,這就是我們的topn數.

2、呼叫生成小頂堆函式,把這個陣列生成乙個小頂堆結構,這個時候堆頂一定是最小的.

3、從檔案或者陣列依次遍歷剩餘的所有數.

4、每遍歷出來乙個則跟堆頂的元素進行大小比較,如果小於堆頂元素則拋棄,如果大於堆頂元素則替換之.

5、跟堆頂元素替換完畢之後,在呼叫生成小頂堆函式繼續生成小頂堆,因為需要再找出來乙個最小的.

6、重複以上4~5步驟,這樣當全部遍歷完畢之後,我們這個小頂堆裡面的就是最大的topn,因為我們的小頂堆永遠都是排除最小的留下最大的,而且這個調整小頂堆速度也很快,只是相對調整下,只要保證根節點小於左右節點就可以.

7、演算法複雜度的話按top10最壞的情況下,就是每遍歷乙個數,如果跟堆頂進行替換,需要調整10次的情況,也要比排序速度快,而且也不是把所有的內容全部讀入記憶體,可以理解成就是一次線性遍歷.

//

生成小頂堆函式

function heap(&$arr,$idx)

if($arr[$right] && $arr[$right] < $arr[$left

])else

if ($arr[$idx] > $arr[$l])}

//這裡為了保證跟上面一致,也構造500w不重複數

/*當然這個資料集並不一定全放在記憶體,也可以在

檔案裡面,因為我們並不是全部載入到記憶體去進

行排序*/for($i=0;$i

<5000000;$i++)

//打亂它們

shuffle($numarr);

//先取出10個到陣列

$toparr = array_slice($numarr,0,10); //

獲取最後乙個有子節點的索引位置

//因為在構造小頂堆的時候是從最後乙個有左或右節點的位置

//開始從下往上不斷的進行移動構造(具體可看上面的圖去理解)

$idx = floor(count($toparr) / 2) - 1; //

生成小頂堆

for($i=$idx;$i>=0;$i--)

var_dump(time

());

//這裡可以看到,就是開始遍歷剩下的所有元素

for($i = count($toparr); $i

< count($numarr); $i++)

}var_dump(time());

執行之後結果

可以看到最終的結果也是top10,只不過時間只用了1s左右,而且無論是記憶體還是時間效率都滿足我們的要求,而且跟排序比最好的一點就是不用把所

有的資料集都讀如到記憶體裡面來,因為我們不需要排序,而上面是為了演示,所以直接在記憶體構造了500w元素,然而我們可以把這個全部轉移到檔案裡面去,然

後一行一行讀取進行比較,因為我們這個資料結構的核心點就是線性遍歷跟記憶體裡面很小的小頂堆結構進行比較,最終得到topn.

總結

PHP 利用二叉堆實現TopK 演算法

在以往工作或者面試的時候常會碰到乙個問題,如何實現海量topn,就是在乙個非常大的結果集裡面快速找到最大的前10或前100個數,同時要保證記憶體和速度的效率,我們可能第乙個想法就是利用排序,然後擷取前10或前100,而排序對於量不是特別大的時候沒有任何問題,但只要量特別大是根本不可能完成這個任務的,...

java 使用二叉堆實現 TopK 演算法

首先 本小白是參考這裡寫鏈結內容 和這裡寫鏈結內容 兩位 博主的文章 1.首先,什麼是二叉堆,維基百科上是這麼描述的 當父節點的鍵值總是大於或等於任何乙個子節點的鍵值時為最大堆。當父節點的鍵值總是小於或等於任何乙個子節點的鍵值時為最小堆。2.二叉堆一般用陣列來表示。如果根節點在陣列中的位置是1,第n...

二叉堆實現二

堆可以視為一棵完全二叉樹,樹的每一層都是被填滿的,最後一層可能除外,所以堆可以用陣列來表示。對於陣列中任意位置 i上的元素,其左兒子在位置 i 2 1 其右兒子在位置 i 2 2 上,其父節點在位置 i 1 2 1處。二叉堆有兩種 最大堆和最小堆。最大堆中,除根結點外 其無父結點 每個結點的關鍵字都...