動態數據集合中求top k大元素
第1大,第2大 ...第k大
k是這群體里最小的
所以要建立個小頂堆
只需要維護一個大小為k的小頂堆 即可
當來的元素(newCome)> 堆頂元素(smallTop),說明進來的元素有和堆頂競爭的資格,此時的堆頂被踢出 這時把進來的元素放到堆頂
newCome>smallTop,smallTop的左右孩子>smallTop,所以無法確認 newCome和smallTop的左右孩子的大小關系,
在newCome和smallTop的左右子節點找到最小的元素和newCome交換,然后繼續比較newCome與被交換的左右孩子的大小關系
持續這個過程(堆化)即可
如果每次詢問前K大數據,我們都基於當前的數據重新計算的話,那時間復雜度就是O(nlogK),n表示當前的數據的大小
部分代碼
topn.php
$static_data=[2,5,3,1,0,7,6,10];
//第3大
/*
2,5,3 2
2,5,3 1 2
2,5,3,1,0 2
2,5,3,1,0,7 3
2,5,3,1,0,7,6 5
2,5,3,1,0,7,6,10 6
維持1個小頂堆 大小為3即可
*/
$heap=new Heap(3); //建立一個大小為3的小頂堆
foreach ($static_data as $v){
echo $heap->topn($v).PHP_EOL;
}
heap.php
public function topn($data)
{
//堆滿了
if ($this->isFull()) {
if ($data > $this->dataArr[1]) {
$this->dataArr[1] = $data;
$this->smallHeapFirst();
}
} else {
$this->dataArr[$this->count + 1] = $data;
$this->count++;
$this->smallHeapLast();
}
return $this->dataArr[1];
}
動態數據流求中位數
2,3,1,7,5 返回3
1,3,1,7,5,4 返回3,4
數據持續往里面進,每進來一個數,就詢問中位數是誰們
step1 思路分析:
所謂中位數,就是中間大的1個或者2個元素,中位數滿足的性質,中位數之前的數都它,之后的數都大於它
先以奇數個分析,偶數個原理一樣
1.如果是固定的數據集合,比如數據為n個,中位數即為n/2+1 大的元素,此時只需維護一個大小為(n/2+1) 大小的小頂堆即可
為什么不能是大頂堆呢,如果堆頂最大,除了知能找到這群集合的最大值外,其它的都無從知曉了
如果是小頂堆,堆頂最小,數據集合比如為5個,第3大的元素肯定小於已經比較過的前2個數,即為中間元素
但是現在是動態數據流,每次進來1個元素,都會詢問中間元素
和靜態數據的區別是:不知道維護的小頂堆的大小了
這時需要維護2個堆了,來了數據,分別放到這2個堆
1個大頂堆,1個小頂堆,大頂堆的數據均小於小頂堆的數據,當要詢問的時候
如果是偶數個數據,兩個堆的堆頂元素即為中間元素
如果奇數個數據,兩個堆中數據較多的那個堆的堆頂元素即為中間元素
step1 步驟分析
大頂堆為big,堆頂元素bigpeak,大小為bigsize,小頂堆稱small,堆頂元素為smallpeak,大小為smallsize
進來1個元素,big為空 :放入big
big不為空:
放入元素<bigpeak,放入到big
放入元素>bigpeak, 放入到small
放入1個元素完成后
如果bigsize-smallsize>1,把big元素的堆頂元素拿掉 堆化big,把拿掉的元素放入small 然后堆化
如果bigsize-smallsize<1,把small元素的堆頂元素拿掉 堆化small,把拿掉的元素放入big 然后堆化
findmiddle.php
$arr = [9, 8, 11, 4, 2, 6, 5, 1, -1, 3, 20, 10];
//$arr=[9,8,11,4,2,6,5,100];
findMiddle($arr);
//動態數據實時獲取中位數
function findMiddle($arr)
{
//大頂堆
$bigHeap = new Heap(0, 1);
//小頂堆
$smallHeap = new Heap(0, 0);
foreach ($arr as $k => $v) {
if ($bigHeap->isEmpty()) {
$bigHeap->insert($v);
} else {
$bigPeak = $bigHeap->peak();
if ($v < $bigPeak) {
$bigHeap->insert($v);
} else {
$smallHeap->insert($v);
}
if ($bigHeap->count - $smallHeap->count > 1) {
$bigPeak = $bigHeap->deleteFirst();
$smallHeap->insert($bigPeak);
} elseif ($smallHeap->count - $bigHeap->count > 1) {
$smallPeak = $smallHeap->deleteFirst();
$bigHeap->insert($smallPeak);
}
}
//實時獲取中位數
echo implode(',', midPeak($bigHeap, $smallHeap)) . PHP_EOL;
}
}
function midPeak($heap1, $heap2)
{
if ($heap1->count == $heap2->count) {
$midArr = [$heap1->peak(), $heap2->peak()];
} elseif ($heap2->count > $heap1->count) {
$midArr = [$heap2->peak()];
} else {
$midArr = [$heap1->peak()];
}
return $midArr;
}
過程分析
幾個重要的點
- 兩個堆元素數相等時中間元素為兩個堆頂
否者為較多元素堆的堆頂 - 兩者元素個數差值大於1時,要調整堆的元素個數
依次插入的元素 為 9, 8, 11, 4, 2, 6, 5, 1, -1, 3, 20, 10,大頂堆 稱為big,小頂堆稱為small,各自大小bigsize,smallsize,堆頂為bigpeak,smallpeak,
9進來 big為空,插入big, bigsize-smallsize=1 不大於1
此時bigsize>smallsize 中間元素為bigpeak即為[9]
8進來 8<bigpeak, 插入big,bigsize-smallsize=2 大於1
此時bigpeak 需要從Big刪除,big堆化,放入到small ,small堆化 ,此時bigsize=smallsize 所以中間元素為[bigpeak,smallpeak] 即為[8,9]
11進來 11>bigpeak(8),11插入small,此時smallsize=2,bigsize=1,差值不大於1,因為smallsize>bigsize,中間元素為[smallpeak] 即為[9]
4進來 4<bigpeak(8),4插入到big,big堆化,此時bigsize=2,smallsize=2,中間元素為[bigpeak,smallpeak] 即為[8,9]
此時堆圖
2進來 2<8 ,2插入big然后堆化,bigsize=3,smallsize=2 所以此時中位數為[8]
6進來 6<8,6插入big后堆化 為下圖
此時,bigsize=4,smallsize=2,bigsize-smallsize>1,刪除big的堆頂元素 堆化,然后把把刪除的元素插入到small,堆化后
此時big,small見下圖,中間元素位[bigpeak,smallpeak]即 [6,8]
5進來 5<bigpeak(8),5插入big堆化
此時Bigsize=4,smallsize=3,差值不大於1,中間元素位bigpeak 即為[6]
之后的步驟同理
插入數據因為需要涉及堆化,所以時間復雜度變成了O(logn),但是求中位數我們只需要返回大頂堆的堆頂元素就可以了,所以時間復雜度就是O(1)