求topK问题

来源:互联网 发布:2017云计算大会 ppt 编辑:程序博客网 时间:2024/05/22 01:26

        最近关注一下数据算法面试的时候常考的题目,求topK问题。

本文从两种方法实现,并给出实际数据实验结果以及相关的分析。

以下是堆排序

// Heap sort;void MaxHeapify(int * src, int pos,int count){int l = (pos<<1)+1;int r = l+1;int maxpos = pos;if (l<count && src[l]>src[pos]){maxpos = l;}if (r<count && src[r]>src[maxpos]){maxpos = r;}if (maxpos != pos){int temp = src[maxpos];src[maxpos] = src[pos];src[pos] = temp;MaxHeapify(src,maxpos,count);}}void CreateMaxHeap(int * src,int count){for (int t = count>>1;t>=0;--t){MaxHeapify(src,t,count);}return;}void HeapSort(int * src,int count){CreateMaxHeap(src,count);while (count--){int t = src[0];src[0] = src[count];src[count] = t;MaxHeapify(src,0,count);}}int _tmain(int argc, _TCHAR* argv[]){int intarray[1024] = {0};for(int i = 0; i< 1024; ++i){intarray[i] = i;}HeapSort(intarray,1024);return 0;}

      只要把上面的HeapSort函数稍改一下,我们就可以得到一个求从小到大排序排在第k位置的数值。

int HeapSortGetMinK(int *src,int begin,int end,int k){CreateMaxHeap(src,k);int i = k;while (i<=end){if (src[i]<src[0]){int t = src[0];src[0] = src[i];src[i] = t;MaxHeapify(src,0,k);}++i;}return src[0];}
      其中的参数名字都很好理解。从时间复杂度上算,建一个k大小的堆,o(k);小于n-k次堆的调整,o((n-k)lgk)近似为nlgk/2。整体时间复杂度数量级为nlgn;
      另一种方法是类似快速排序的分法,每次使用partition将数值分为两边,根据k值与当前分值位置的对比,决定下一个partition区间;也就是说比快排少了一边递归;以下是算法的实现:

int partition(int *A,int begin,int end){int i = begin;int j = end;int mid = A[begin];while (i<j){while (A[j]>=mid && i<j){--j;}if (i<j){A[i] = A[j];}while (A[i]<=mid && i<j){++i;}if (i<j){A[j] = A[i];}}A[j] = mid;return j;}// 求一个长为m的数组中第k(k<m)大的数值// 输入: 数组指针A,数组长度len,k值// 返回: 第k+1小的数int PartitionSortGetMinK(int *A,int begin,int end,int k){int mid = partition(A,begin,end);if ( k == mid ){return A[mid];}else if (k<mid){return PartitionSortGetMinK(A,begin,mid-1,k);}return PartitionSortGetMinK(A,mid+1,end,k);}
注意这个partition比算法导论上面那个标准算法要来的快,来的巧妙。从时间复杂度上算,partition算法只需要一次遍历,就能把范围内的数值分两边,复杂度为lgn;而在递归的时候,每次partition的范围从平均上说都减半,算法导论上证明了以上方法的时间复杂度在平均情况下是o(n)。

        那么,从实际数据检验的结果来看会怎么样呢?我们以下就给实验过程:

int getmilliseconds(SYSTEMTIME begin,SYSTEMTIME end){// 请勿在23:59到00:01之间做此计算int hour = end.wHour -begin.wHour;int min = end.wMinute - begin.wMinute;int seconds = end.wSecond - begin.wSecond;int millisecond = end.wMilliseconds - begin.wMilliseconds;return ((hour*60+min)*60+seconds)*1000+millisecond;}//产生数组的方法,可以使用多种不同的算法产生数组void GetArray(int *intarray,int count,int seed){// 以种子产生的随机数srand(seed);for(int i = 0; i< count; ++i){intarray[i] = rand();}}void test(int *intarray,int len,int k,int seed,int &heapsortresult,int &partitionresult){int kvalue = 0;int inter = 0;GetArray(intarray,len,seed);SYSTEMTIME begin1,begin2,end1,end2;GetSystemTime(&begin1);kvalue = HeapSortGetMinK(intarray,0,len-1,k);GetSystemTime(&end1);inter = getmilliseconds(begin1,end1);heapsortresult += inter;//printf("\t %d \t",inter);GetArray(intarray,len,seed);GetSystemTime(&begin2);kvalue = PartitionSortGetMinK(intarray,0,len-1,k-1);GetSystemTime(&end2);inter = getmilliseconds(begin2,end2);partitionresult +=inter;//printf("%d\n",inter);}int _tmain(int argc, _TCHAR* argv[]){int maxlen = 1<<24;int *intarray = (int*)malloc(maxlen*sizeof(int));// 整体数组最长值,每次增长四倍for (int len = 1<<2;len<=maxlen;len<<=2){printf("++++++length %d \n",len);// k值每次增长2倍for (int k = 1;k<len;k<<=1){printf("\tk %15d",k);int partitionresult=0;int heapsortresult = 0;// 四个种子数方法,计算时间for (int i = 1;i<5;i++){int seed = i;//printf("\t\tseed %d ",i);test(intarray,len,k,seed,heapsortresult,partitionresult);}// 对四次堆方法的时间和取平均值heapsortresult>>=2;// 对四次划分方法的时间和取平均值partitionresult>>=2;printf("\t%d\t%d\n",heapsortresult,partitionresult);}printf("\n");}free(intarray);return 0;}

实验结果:

++++++length 4 k               100k               200++++++length 16 k               100k               200k               400k               800++++++length 64 k               100k               200k               400k               800k              1600k              3200++++++length 256 k               100k               200k               400k               800k              1600k              3200k              6400k             12800++++++length 1024 k               100k               200k               400k               800k              1600k              3200k              6400k             12800k             25600k             51200++++++length 4096 k               100k               200k               400k               800k              1600k              3200k              6400k             12800k             25600k             51200k            102400k            204800++++++length 16384 k               100k               200k               400k               800k              1600k              3200k              6400k             12800k             25600k             51201k            102400k            204800k            409600k            819200++++++length 65536 k               100k               200k               400k               800k              1600k              3200k              6400k             12800k             25600k             51200k            102400k            204811k            409610k            819221k           1638421k           3276820++++++length 262144 k               100k               200k               400k               800k              1600k              3200k              6400k             12800k             25600k             51202k            102401k            204812k            409612k            819232k           1638442k           3276882k           65536103k          131072113++++++length 1048576 k               110k               211k               400k               800k              1610k              3210k              6400k             12801k             25600k             51211k            102410k            204827k            409627k            819247k           1638477k           32768138k           65536227k          131072348k          2621444910k          5242885512++++++length 4194304 k               144k               244k               444k               844k              1643k              3244k              6443k             12843k             25644k             51244k            102444k            204854k            409664k            8192933k           163841233k           327682135k           655363536k          1310725936k          2621449935k          52428816038k         104857627846k         209715237851++++++length 16777216 k               11515k               21515k               41515k               81515k              161515k              321515k              641515k             1281515k             2561515k             5121615k            10241616k            20481615k            40961816k            81922015k           163842615k           3276836141k           6553655142k          13107292142k          262144155144k          524288270145k         1048576528177k         20971521102176k         41943041877171k         83886082274187



实验中设计,length值从4开始,以4为步长,一直增长到1<<24;对应 的K值从1开始,以2为步长,一直增长至length的一半值;所使用的数据分别用1,2,3,4 为种子数,生产伪随机序列,四次计算时间后得出当前方法计算的平均时间作为打印结果。

结果中,其中length是数组的总长度,k后面的第一个数字为k值,第三列为使用【最大堆方法】求出第k小数所需要的时间(单位是ms),第四列为【划分法】求第k小数所需要的时间,单位为ms。


可以看出,大部分情况下,划分法的速度是占住优势的。但有一个疑问,在以固定的N值,以最大推方法求的过程中,时间随k值稳步上升;而以划分法时间随k增大在某一个时间点增加很快,其他时间点基本稳定。


我怀疑是产生随机数的算法问题,造成数据有一定的规律,并且影响到划分排序的进行。







原创粉丝点击