­

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

  • 2019 年 10 月 3 日
  • 筆記

動態數據集合中求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堆化,放入到smallsmall堆化 ,此時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)

完整程式碼