上篇談到, 之前的程式使用堆尋找前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 }}