使用堆查找前K个最大值兼谈程序优化(中)
上篇谈到, 之前的程序使用堆查找前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 } }