使用堆尋找前K個最大值兼談程式最佳化(中)

來源:互聯網
上載者:User

         上篇談到, 之前的程式使用堆尋找前K個最大值的效率並不理想,本篇嘗試對程式進行最佳化,以提高程式效率。

         一、 演算法設計方面

        要提高程式效率, 首先從演算法設計方面,即時間複雜度方面考慮。 由於尋找前K個最大值總要遍曆整個列表,因此,其效率必定不小於線性,而前面已經談到,使用堆尋找其效率平均情況下可以達到線性, 因此, 整體的演算法複雜度恰好為線性,無法在量級上有重大提升。 不過,通過仔細改進和最佳化,是可以將原來的效率提升若干倍的。

        二、  使用效能分析工具

       進行效能最佳化的第二步驟是使用效能分析工具分析“作用區”, 也就是耗費時間非常多的地方。我是在DEV C++ IDE 上開發的, 使用內建的Profile 分析結果如下: 

       

        顯然, maxHeapify 所耗費的時間是最多的, 也就是為了保持最大堆性質而進行的操作。 要改進這一操作, 首先想到的是將遞迴改為非遞迴。

  

        三、  遞迴轉化為非遞迴

        maxHeapify 的非遞迴程式如下:

       

void fastMaxHeapify(Elem list[], long i, long heapsize){    Elem temp;    temp.num = list[i].num;       long curr_largest = i;    long last_largest = i;        while (curr_largest <= heapsize) {       long lch = LEFT(curr_largest);       long rch = RIGHT(curr_largest);       if (lch <= heapsize && list[lch].num > list[curr_largest].num) {            curr_largest = lch;       }       if (rch <= heapsize && list[rch].num > list[curr_largest].num) {            curr_largest = rch;       }       if (curr_largest == last_largest) {           break;       }       list[last_largest].num = list[curr_largest].num;       last_largest = curr_largest;    }         list[curr_largest].num = temp.num;}

      使用 fastMaxHeapify 替代原來的 maxHeapify 後 , 其 Profile 分析結果如下:

       

        可以很明顯地看出, 其已耗用時間降低到原來的大約一半。這是因為減少了很多交換操作及系統調用時間。 使用堆尋找N個數中的前K個最大值(不包括初始化N個數)時間降為2.5s 左右, 首戰告捷。 

  

        四、 將下標索引操作替換為指標操作

                改進很不明顯, 也許是編譯器已經最佳化的緣故。

 

        五、 去掉 RandNum 函數

        RandNum短 函數在這裡只是為了可讀性, 可以直接去掉, 用裡面的內容代替其調用以減少系統調用開銷。 當然,這隻是小幅度減少了建立隨機元素的時間,並沒有影響尋找效率。

         

       六、  停下來! 建立正確性的迴歸測試

        做程式最佳化很容易頭腦發昏,一味地盯著效能數字變化,忘記一件很重要的事情: 那就是每次最佳化的改動中,必須總是保持程式的正確性。 這不, 出問題了! 使用fastMaxHeapify 後對於有些情況不能正常工作。 現在建立正確性的迴歸測試還不算晚,要是等待程式已經壘得有點“規模”了,再來測試, 准得更頭疼。 此外,需要對程式碼群組織進行調整下,以使整個結構更加清晰。經過仔細的測試和調試, fastMaxHeapify 確實有個重要的錯誤. 讀者不妨找找看。

        建立迴歸測試後,最重要的是, 在之後的最佳化工作中, 可以保證改動總是在滿足正確性的範圍內。 值得注意的是,用於測試和驗證的函數一定要正確,否則會起誤導的作用。 其程式主要如下:

/*  * HeapUtil.c * 實現建堆過程的函數以及堆排序,求解前K個最大值  * * NOTE: 數組 list 的 0 號單位未用,元素應放置於[1:num]而不是[0:num-1] *       num 是應用中的資料數目, 因此必須給數組分配至少 num+1 個元素空間, */#include "common.c"#define LEFT(i)     (2*(i))#define RIGHT(i)    (2*(i)+1)#define FAST  1/*  * maxHeapify: 使以結點i為根的子樹為最大堆.  * 前置條件:結點i的左右子樹都滿足最大堆性質  */void maxHeapify(Elem list[], long i, long heapsize);void maxHeapifyRec(Elem list[], long i, long heapsize);  void fastMaxHeapify(Elem list[], long i, long heapsize);/* buildInitMaxHeap: 構造初始最大堆 */void buildInitMaxHeap(Elem list[], long num);   void swap(Elem *e1, Elem *e2); /* 計算 list 的 n 個數中前 k 個最大值 */Elem* findkthMax(Elem *list, int k, long n); /* 驗證以 index 為結點的子樹確實滿足最大堆性質 */void  validMaxHeap(Elem *list, long index, long heapsize);/* 驗證 建立初始最大堆的正確性 */ void validBuildInitMaxHeap(Elem list[], long num);/* 驗證 kthmax 確實是 list 中的前 k 個最大值 */void validkthMax(Elem* list, int num, Elem* kthmax, int k);  /* 堆排序實現 */ void heapSort(Elem list[], long num); void maxHeapify(Elem list[], long i, long heapsize){     #if FAST == 0         maxHeapifyRec(list, i, heapsize);     #else     fastMaxHeapify(list, i, heapsize);     #endif }void maxHeapifyRec(Elem list[], long i, long heapsize){    long largest = i;     // 結點i與其左右孩子節點中關鍵詞最大的那個結點的下標     long lch = LEFT(i);    long rch = RIGHT(i);    if (lch <= heapsize && list[lch].num > list[largest].num) {    largest = lch;      }      if (rch <= heapsize && list[rch].num > list[largest].num) {   largest = rch;      }    if (largest != i) {   swap(&list[largest], &list[i]);       maxHeapify(list, largest, heapsize);      }   }void fastMaxHeapify(Elem list[], long i, long heapsize){    Elem temp;    temp.num = list[i].num;       long curr_largest = i;    long last_largest = i;        while (curr_largest <= heapsize) {       long lch = LEFT(curr_largest);       long rch = RIGHT(curr_largest);              if (lch <= heapsize && (*(list+lch)).num > temp.num) {            curr_largest = lch;       }       if (rch <= heapsize && (*(list+rch)).num > (*(list+curr_largest)).num) {           curr_largest = rch;       }       if (curr_largest == last_largest) {           break;       }       (*(list+last_largest)).num = (*(list+curr_largest)).num;       last_largest = curr_largest;    }         (*(list+curr_largest)).num = temp.num;}/*  * buildInitMaxHeap: 構造初始最大堆  */void buildInitMaxHeap(Elem list[], long num){ long i; for (i = (num+1)/2; i >= 1; i--)    maxHeapify(list, i, num);}/* 驗證 建立初始最大堆的正確性 */ void validBuildInitMaxHeap(Elem list[], long num){     long i ;     if (num % 2 == 0) {         i = num / 2;         assert(list[i].num >= list[2*i].num);         i--;     }     else {         i = num / 2;     }     for (; i >= 1; i--) {         assert(list[i].num >= list[i*2].num);         assert(list[i].num >= list[i*2+1].num);     }}/* 驗證以 index 為結點的子樹確實滿足最大堆性質 */void  validMaxHeap(Elem *list, long index, long heapsize){      long lch, rch;      if (index > heapsize) {            return ;      }      lch = 2 * index;       if (lch <= heapsize) {          assert(list[index].num >= list[lch].num);          validMaxHeap(list, lch, heapsize);      }      rch = 2 * index + 1;      if (rch <= heapsize) {          assert(list[index].num >= list[rch].num);          validMaxHeap(list, rch, heapsize);      }}void swap(Elem *e1, Elem *e2){  Elem e;  e = *e1;  *e1 = *e2;  *e2 = e;}void heapSort(Elem list[], long num){     long i, heapsize = num;     buildInitMaxHeap(list, num);          for (i = num; i >= 1; i--) {        swap(&list[1], &list[i]);        heapsize--;        maxHeapify(list, 1, heapsize);     }}  /* 計算 list 的 n 個數中前 k 個最大值 */Elem* findkthMax(Elem *list, int k, long n){     long i;     long heapsize = n;     Elem *kthmax = myalloc(k+1);     Elem *p = kthmax+1; buildInitMaxHeap(list, n); for (i = n; i > n-k; i--) {           (p++)->num = (*(list+1)).num;        swap(list+1, list+i);    heapsize--;    maxHeapify(list, 1, heapsize); } return kthmax;}/*  * 驗證 kthmax[1:k] 確實是 list[1:num] 中的前 k 個最大值   */void validkthMax(Elem* list, int num, Elem* kthmax, int k){     int i;     Elem *p, *q;     heapSort(kthmax, k);  /* 對列表 kthmax 進行排序,使之從小到大排序 */          for (i = 1; i <= k; i++) {         p = kthmax + k-i+1;   // 從大到小依次取 kthmax 中的最大值          for ( q = list+i; q <= list+num; q++) {            assert((*q).num <= (*p).num);              if ((*q).num == (*p).num) {               swap(list+i, q);            }         }      }        }

      

/*  * common.c 存放公用結構與常式  */ #include <stdio.h>#include <stdlib.h>#include <time.h>#include <limits.h>#include <assert.h>#define MOD 101#define DEBUG 0  typedef struct {  long  num;   /* 設為關鍵詞 */ } Elem;void creatListInternal(Elem *list, long offset, long num, long len);void printListInternal(Elem *list, long offset, long num, long len);void creatList(Elem *list, long num);void creatList2(Elem* list, long num);void printList(Elem *list, long num);void validSort(Elem *list, long size);Elem *myalloc(long size);/*  * 隨機產生列表元素,並存入從offset開始的 num 個元素, len 是為列表長度  * 若 len < offset + num 則只存入 len-offset個元素  */void creatListInternal(Elem *list, long offset, long num, long len){   long i, endIndex = offset+num;   srand(time(NULL));   if (offset < 0 || offset >= len) {            return ;       }   if (endIndex > len) {          endIndex = len ;       }   for (i = offset; i < endIndex; i++)      (*(list+i)).num = ((1 + rand()) % MOD) * ((1 + rand()) % MOD);}/*  *  列印從offset開始的num個元素, len 是為列表長度 *  若 len < offset + num 則只列印 len-offset個元素  */ void printListInternal(Elem *list, long offset, long num, long len){   long i, endIndex = offset+num;   if (offset < 0 || offset >= len) {            return ;       }   if (endIndex > len) {          endIndex = len ;       }   for (i = offset; i < endIndex; i++)       printf("%6d%c", (*(list+i)).num, ((i-offset+1)%10 == 0) ? '\n': ' ');       printf("\n");}void creatList(Elem *list, long num){     creatListInternal(list, 1, num, num+1);}void creatList2(Elem* list, long num){     int i ;     for (i = 1; i <= num; i++) {         list[i].num = i;      }}void printList(Elem *list, long num){     printListInternal(list, 1, num, num+1);}void validSort(Elem *list, long size){     long i;     for (i=1; i < size; i++) {         assert(list[i].num <= list[i+1].num);     }}Elem *myalloc(long size){     Elem* list = (Elem *)malloc(size*sizeof(Elem));     if (!list) {          fprintf(stderr, "fail to allocate memory.");          exit(1);      }      return list;}

/*  * KthMaxTest.c 使用堆排序求解前K個最大值問題的測試  * */  #include "HeapUtil.c"#include "MaxHeapifyTest.c"#include "HeapSortTest.c"#define CHOOSE 2        void testkthMax(long NUM, int K);void measure(long NUM , int K);void testValid();void testPerf();int main(){      srand(time(NULL));      #if CHOOSE == 1         printf("\n*********** maxHeapify test begins. ********\n");         maintestMaxHeapify();          printf("\n*********** maxHeapify test completed. ********\n");         printf("\n*********** building initial maxheap test begins. ********\n");         maintestBIMP();         printf("\n*********** building initial maxheap test completed. ********\n");         printf("\n*********** heap sort test begins. ********\n");         maintestHeapSort();         printf("\n*********** heap sort test completed. ********\n"); test(" Test Valid ", testValid);      // 用於保證正確性的迴歸測試  printf("Successful Passed.");      #elif CHOOSE == 2          measure(100000000, 100);            // 用於程式最佳化的執行個體       #else        test(" Measure Performace ", testPerf);  // 用於測量效能   #endif    getchar();  return 0;}void test(char *msg, void (*test)()){     printf("\n----------- %s ----------\n", msg);     (*test)();     printf("\n\n");}void testValid(){     long num;     int k;     for (num = 0; num <= 8; num ++) {        for (k = 0; k <= num; k++) {           testkthMax(num, k);        }      }}/* 測試找出前K個最大值 */void testkthMax(long NUM, int K){      Elem *kthmax;      Elem* list = myalloc(NUM+1);      creatList(list, NUM);            #if DEBUG == 1          printf("\nThe original list:\n");          printList(list, NUM);      #endif        kthmax = findkthMax(list, K, NUM);      validkthMax(list, NUM, kthmax, K);            #if DEBUG == 1          printListInternal(kthmax, 1, K, K+1);        #endif            free(kthmax);      free(list);}void testPerf(){     long num ;      int k;     for (num = 1; num <= 100000000; num*=10) {        for (k = 1; k <= 1000 && k <= num ; k*=10) {           measure(num, k);        }      }}void measure(long NUM , int K){      clock_t start, end;      Elem *kthmax;      Elem* list = myalloc(NUM+1);      creatList(list, NUM);        start = clock();  kthmax = findkthMax(list, K, NUM);  end = clock();        free(kthmax);      free(list);       printf("\nNUM = %-10ld, K = %-10d, Eclipsed time: %6.3f\n", NUM, K, ((double)(end-start))/CLOCKS_PER_SEC);}

/** * MaxHeapifyTest.c  構建並保持最大堆性質函數的測試  * */ void maintestMaxHeapify();void testMaxHeapify(void (*creatList)(Elem* list, long size));void testmh(Elem *list, long heapsize);void testbimp(Elem *list, long heapsize);void testBuildInitMaxHeap(void (*creatList)(Elem* list, long size));void maintestBIMP();void maintestMaxHeapify(){    testMaxHeapify(creatList2);    testMaxHeapify(creatList); }void testMaxHeapify(void (*creatList)(Elem* list, long size)){         long heapsize;     for (heapsize = 0; heapsize <= 8; heapsize++) {        Elem list[heapsize+1];        creatList(list, heapsize);        testmh(list, heapsize);     }}void testmh(Elem *list, long heapsize){    long index;        #if DEBUG == 1          printf("\n----------- heapsize: %d ----------\n", heapsize);          printList(list, heapsize);    #endif        for (index = heapsize; index >= 1; index--) {         long lch, rch, curr;          maxHeapify(list, index, heapsize);       validMaxHeap(list, index, heapsize);                #if DEBUG == 1           printf("index = %d\t", index);           printList(list,heapsize);         #endif      }          }void maintestBIMP(){     testBuildInitMaxHeap(creatList2);     testBuildInitMaxHeap(creatList);}void testBuildInitMaxHeap(void (*creatList)(Elem* list, long size)){         long heapsize;     for (heapsize = 0; heapsize <= 8; heapsize++) {        Elem list[heapsize+1];        creatList(list, heapsize);        testbimp(list, heapsize);     }}void testbimp(Elem *list, long heapsize){    long index;          #if DEBUG == 1       printf("\n----------- heapsize: %d ----------\n", heapsize);         printList(list, heapsize);    #endif        buildInitMaxHeap(list, heapsize);    validBuildInitMaxHeap(list, heapsize);            #if DEBUG == 1       printList(list,heapsize);    #endif      }

/* * HeapSortTest.c 堆排序測試  */  void testHeapSort(void (*creatList)(Elem* list, long size)); void maintestHeapSort();   /* 測試堆排序 */void maintestHeapSort(){        testHeapSort(creatList2);      testHeapSort(creatList);}void testHeapSort(void (*creatList)(Elem* list, long size)){         long heapsize;     for (heapsize = 0; heapsize <= 8; heapsize++) {        Elem list[heapsize+1];        creatList(list, heapsize);                #if DEBUG == 1            printf("%-21s", "\nThe original list: ");        printList(list, heapsize);        #endif            heapSort(list, heapsize);     validSort(list, heapsize);        #if DEBUG == 1        printf("%-20s", "The sorted list: ");        printList(list, heapsize);        #endif     }}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.