堆實戰(動態數據流求top k大元素,動態數據流求中位數)


動態數據集合中求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)

完整代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM