Top K問題——堆排序

題解:Top k問題即在大量數據(n>>100000)中查找前k個最大的數據。

思路:排序是不可取的,因爲大量數據排序耗時太大,且空間複雜度也很大,一般利用數據結構的最小堆(最小堆即父節點的值小於等於孩子節點的數值)來處理;

具體做法:建立一個含有K個節點的最小堆,遍歷海量數據分別與根節點比較,若小於根節點則捨棄,否則用新數值替換根節點數值,並進行最小堆的調整,那麼最終得到的堆節點就是最大的k個數據。

時間複雜度=nlogK(堆調整時間複雜度爲logK);

推排序

轉:https://blog.csdn.net/YuZhiHui_No1/article/details/44258297

(1)思想

        把待排序的元素按照大小在二叉樹位置上排列,排序好的元素要滿足:父節點的元素要大於等於其子節點;這個過程叫做堆化過程,如果根節點存放的是最大的數,則叫做大根堆;如果是最小的數,自然就叫做小根堆了。根據這個特性(大根堆根最大,小根堆根最小),就可以把根節點拿出來,然後再堆化下,再把根節點拿出來,,,,循環到最後一個節點,就排序好了。

    具體排序過程如上圖:其實整個排序主要核心就是堆化過程,堆化過程一般是用父節點和他的孩子節點進行比較,取最大的孩子節點和其進行交換;但是要注意這應該是個逆序的,先排序好子樹的順序,然後再一步步往上,到排序根節點上。然後又相反(因爲根節點也可能是很小的)的,從根節點往子樹上排序。最後才能把所有元素排序好。

代碼實現

#include<stdio.h>
 
 #define LEN 12
 //打印數組
 void print_array(int *array, int length)
 {
     int index = 0;
     printf("array:\n");
     for(; index < length; index++){
         printf(" %d,", *(array+index));
     }
     printf("\n\n");
 }
 //堆化函數
 void _heapSort(int *array, int i, int length)
 {
     int child, tmp;
     //這個是改變了哪個節點,就從該節點開始對以該節點爲根節點的子樹進行排序
     for (; 2*i + 1 < length; i = child){//依次到它的子樹的子樹。。。。
         child = 2*i + 1;
         if ((child +1  < length) && (array[child+1] > array[child])) child++;//選個最大的孩子節點
         if (array[i] < array[child]){//最大子節點和父節點進行交互
             tmp = array[i];
             array[i] = array[child];
             array[child] = tmp;
         }else break;
     }   
 }
  
 void heapSort(int *array, int length)
 {
     int i, tmp;
 
     if (length <= 1) return;//如果元素小於1,則退出
     //這一步是先把元素都堆化好,後面的話 哪個節點修改過,就從哪個節點開始對以它爲根節點的子樹進行堆化
     for (i = length/2 - 1; i >= 0; i--)  _heapSort(array, i, length);//從第一個非葉子節點開始排序,一直到根節點
     
     // 先抽取到根節點,然後再對元素進行堆化,然後又抽取根節點,再對元素進行堆化。。。。依次循環
     for (i = 0; i < length; i++ ){
         tmp = array[0];
         array[0] = array[length-i-1];
         array[length -i-1] = tmp;
         _heapSort(array, 0, length-1-i);//堆化子樹
     }
 }
 
 int main(void)
 {
     int array[LEN] = {2, 1, 4, 0, 12, 520, 2, 9, 5, 3, 13, 14};
     print_array(array, LEN);
     heapSort(array, LEN);
     print_array(array, LEN);
     return 0;
 }

運行結果:

        

(2)時間複雜度

        堆排序的時間複雜度,主要在初始化堆過程和每次選取最大數後重新建堆的過程;

        初始化建堆過程時間:O(n)

        推算過程:

        首先要理解怎麼計算這個堆化過程所消耗的時間,可以直接畫圖去理解;

        假設高度爲k,則從倒數第二層右邊的節點開始,這一層的節點都要執行子節點比較然後交換(如果順序是對的就不用交換);倒數第三層呢,則會選擇其子節點進行比較和交換,如果沒交換就可以不用再執行下去了。如果交換了,那麼又要選擇一支子樹進行比較和交換;

        那麼總的時間計算爲:s = 2^( i - 1 )  *  ( k - i );其中 i 表示第幾層,2^( i - 1) 表示該層上有多少個元素,( k - i) 表示子樹上要比較的次數,如果在最差的條件下,就是比較次數後還要交換;因爲這個是常數,所以提出來後可以忽略;

        S = 2^(k-2) * 1 + 2^(k-3)*2.....+2*(k-2)+2^(0)*(k-1)  ===> 因爲葉子層不用交換,所以i從 k-1 開始到 1;

        這個等式求解,我想高中已經會了:等式左右乘上2,然後和原來的等式相減,就變成了:

        S = 2^(k - 1) + 2^(k - 2) + 2^(k - 3) ..... + 2 - (k-1)

        除最後一項外,就是一個等比數列了,直接用求和公式:S = {  a1[ 1-  (q^n) ] }  / (1-q);

        S = 2^k -k -1;又因爲k爲完全二叉樹的深度,所以 (2^k) <=  n < (2^k  -1 ),總之可以認爲:k = logn (實際計算得到應該是 log(n+1) < k <= logn );

        綜上所述得到:S = n - longn -1,所以時間複雜度爲:O(n)

        更改堆元素後重建堆時間:O(nlogn)

        推算過程:

       1、循環  n -1 次,每次都是從根節點往下循環查找,所以每一次時間是 logn,總時間:logn(n-1) = nlogn  - logn ;

       綜上所述:堆排序的時間複雜度爲:O(nlogn)

(2)空間複雜度

        因爲堆排序是就地排序,空間複雜度爲常數:O(1)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章