使用堆查找前K个最大值兼谈程序优化(上)


     一、 缘起

        看到CSDN社区有篇《3秒搞定一亿数据的前K个最大值》, 想想堆排序不是可以用来做这事吗,于是就动手做做,看看堆排序能够达到怎样的效率。堆的原理就不多说了,网上有好多, 如果想参阅比较权威性的材料,可参见《算法导论》第六章堆排序。 本文的程序即是建立其上。

     

     二、 程序

    KthMax.c

    

/* 
 * 使用堆排序求解前K个最大值问题
 *
 */ 
 
 #include "HeapUtil.c"
 
/* 计算 list 中前 k 个最大值 */
Elem* findkthMax(Elem *list, int k, long num);        
void testkthMax(long NUM, int K);
void measure(long NUM , int K);
void testValid();
void testPerf();

int main()
{
      srand(time(NULL));  
      //test(" Test Valid ", testValid);
      // test(" Measure Performace ", testPerf);
      
      measure(100000000, 100);
      
      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 += 1) {
        for (k = 0; k <= num; k+=1) {
           testkthMax(num, k);
        } 
     }
}

/* 测试找出前K个最大值 */
void testkthMax(long NUM, int K)
{
      Elem *kthmax;
      Elem* list = myalloc(NUM+1);
      creatList(list, NUM);
      printf("\nThe original list:\n");
      printList(list, NUM);
      kthmax = findkthMax(list, K, NUM);
      printListInternal(kthmax, 0, K, K);  
      free(kthmax);
      free(list);
      printf("\n"); 
}

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);
}


/* 计算 list 中前 k 个最大值 */
Elem* findkthMax(Elem *list, int k, long n)
{
     long i;
     long heapsize = n;
     Elem *kthmax = myalloc(k);
     Elem *p = kthmax;
     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;
}

    HeapUtil.c

  

/* HeapUtil.c
 * 实现建堆过程的函数 
 *
 * 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)

/* 
 * maxHeapify: 使以结点i为根的子树为最大堆. 
 * 前置条件:结点i的左右子树都满足最大堆性质 
 */
void maxHeapify(Elem list[], long i, long heapsize);  
void buildInitMaxHeap(Elem list[], long num);    /* BuildMaxHeap: 构造初始最大堆 */
void swap(Elem *e1, Elem *e2);

void maxHeapify(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);
      }
}

/* 
 * buildMaxHeap: 构造初始最大堆 
 */

void buildInitMaxHeap(Elem list[], long num)
{
     long i;
     for (i = (num+1)/2; i >= 1; i--)
        maxHeapify(list, i, num);
}

void swap(Elem *e1, Elem *e2)
{
      Elem e;
      e = *e1;
      *e1 = *e2;
      *e2 = e;
}

   common.c

 

/* 
 * common.c 存放公共结构与例程 
 */
 
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <limits.h>
 
 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 printList(Elem *list, long num);
Elem *myalloc(long size);
long randNum();

/* 
 * 随机生成列表元素,并存入从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 = randNum();
}

/* 
 *  打印从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("%3d%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 printList(Elem *list, long num)
{
     printListInternal(list, 1, num, num+1);
}

Elem *myalloc(long size)
{
     Elem* list = (Elem *)malloc(size*sizeof(Elem));
     if (!list) {
          fprintf(stderr, "fail to allocate memory.");
          exit(1);
      }
      return list;
}

long randNum()
{ 
     return ((1 + rand()) % INT_MAX) * ((1 + rand()) % INT_MAX);
}

 

 

   HeapSort.c

   

/*
 * HeapSort.c 实现堆排序 
 */ 

#include "HeapUtil.c"

#define NUM 10 
 
void heapSort(Elem list[], long num);
void testHeapSort(); 
 
int main()
{
    printf("*********** test heap sort ********");
    testHeapSort();
    getchar();
    return 0;
}
 

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);
     }
} 
 
 /* 测试堆排序 */
void testHeapSort()
{  
      Elem list[NUM+1];
      creatList(list, NUM);
      printf("\nThe original list:\n");
      printList(list, NUM);
      printf("\n");
      heapSort(list, NUM); 
      printf("\nThe sorted list:\n");
      printList(list, NUM);
      printf("\n"); 
}

 

     三、 时间测量

      

  

        四、 效率分析

        使用堆的方法在n个数值中查找前K个最大值的时间效率为 O(n + k*log2n) ,其中建立初始最大堆(即buildInitMaxHeap)的时间为O(n) ,而每次查找均需要约 O(log2n) , 合起来即是前述结果。 因此, 总时间是随总元素数目线性增长的, 而当K远小于 n 时, 查找前K个最大值所耗费的时间差别不大。


        五、 程序优化的起点

        从结果中可以看到,比别人的整整慢一倍! 还有的能达到0.1s级别,当然,这可能与机器硬件配置有关,不过,无论如何,也阻挡不住我进行程序优化的决心了。之前学过一些优化的方法手段,正好在这程序上练练手,巩固下。 请期待下文。



     

 

posted @ 2012-05-11 19:38  琴水玉  阅读(560)  评论(0编辑  收藏  举报