看到说找工作笔试面试经常会出这道题,题目很简单明了,就是在一组数字中,找出最大的K个数,首先想到的就是利用快排算法改一改。
这里简单回顾下快排算法(从小到大):
1 2 int Partition(int a[],int low,int high) 3 { 4 int pivotkey; 5 pivotkey=a[low]; 6 while(low<high) 7 { 8 while(low<high&&a[high]>=pivotkey) 9 { 10 --high; 11 } 12 a[low]=a[high]; 13 while(low<high&&a[low]<=pivotkey) 14 { 15 ++low; 16 } 17 a[high]=a[low]; 18 } 19 a[low]=pivotkey; 20 return low; 21 } 22 23 void QSort(int a[], int low, int high) 24 { 25 int pivotloc; 26 if(low < high) 27 { 28 pivotloc = Partition(a, low, high); 29 QSort(a, low, pivotloc-1); 30 QSort(a, pivotloc+1, high); 31 } 32 }
因为我们只需要找出最大的K个数,不要求排序,所以在QSort()里不用再对pivotloc的两边都分别在递归执行QSort(),
而是把pivotloc左半边的长度L(即大于等于a[pivotloc]的数的个数)与K进行比较:
若L>K,即左半边个数大于K,需要在左半边找出K个
若L<K,即左半边个数小于K,需要在右半边找出K-L个
另外对于Partition()比较大小那里的“大于号“”小于号”要调换一下,因为上面那个是从小到大排,而现在要从大到小排。
1 #include <stdio.h> 2 #define K 6 3 4 int Partition(int a[],int low,int high) 5 { 6 int pivotkey; 7 pivotkey=a[low]; 8 while(low<high) 9 { 10 while(low<high&&a[high]<=pivotkey) 11 { 12 --high; 13 } 14 a[low]=a[high]; 15 while(low<high&&a[low]>=pivotkey) 16 { 17 ++low; 18 } 19 a[high]=a[low]; 20 } 21 a[low]=pivotkey; 22 return low; 23 } 24 25 void findKNums(int a[], int low, int high, int kValue) 26 { 27 if(kValue <= 0) 28 return; 29 if(low < high){ 30 int pivotloc = Partition(a,low,high); 31 int aLen = pivotloc-low+1; 32 if( aLen == kValue) 33 return; 34 else if(aLen < kValue) 35 findKNums(a,pivotloc+1,high,kValue-aLen); 36 else 37 findKNums(a,low,pivotloc-1,kValue); 38 } 39 } 40 41 int main() 42 { 43 int nums[9] = {20,13,2,28,22,24,19,1,3}; 44 findKNums(nums, 0, 8, K); 45 46 for(int i=0; i<K; i++){ 47 printf("%d ", nums[i]); 48 } 49 printf("\n"); 50 return 0; 51 }
写起来的话,要修改的地方其实不多,小小地验证了下,应该没错了。后来想想好像有些细节没处理,比如又相同大小的值的取舍,原序列长度比K小等,
懒得改了,然后又在网上搜索一番,想看看其他解法,发现这道题还在《编程之美》里2.5,哇咔咔,解法能不多咩~~
解法一:用快排或者堆排把整个序列都排序,然后去前K个值,时间复杂度O(N * log2N)
解法二:用快排只做前K个数排序,避免做后N-K个数的排序,跟我上面误打误撞做的差不多,平均时间复杂度O(N * log2K)
解法三:用二分搜索找出第K大的数,时间复杂度O(N * log2N),然后在O(N)时间复杂度里找出所有大于第K大数的数
#include <stdio.h> #define N 9 #define K 6 #define DELTA 0.5 //delta的取值要比所有数中的任意两个不相等的元素差值之最小值小 //如果所有元素都是整数,delta可以取值0.5 int countNums(int b[], float Mvalue) { int Mcount = 0; for(int j=0; j<N; j++) if((float)b[j] >= Mvalue) Mcount++; return Mcount; } void findKNums(int a[]) { float Vmin=(float)a[0],Vmax=(float)a[0],Vmid; for(int i=1; i<N; i++){ if(a[i] < Vmin) Vmin = (float)a[i]; if(a[i] > Vmax) Vmax = (float)a[i]; } while(Vmax - Vmin > DELTA){ Vmid = (Vmax + Vmin)/2; if(countNums(a, Vmid) >= K) Vmin = Vmid; else Vmax = Vmid; } for(int k=0; k<N; k++) if((float)a[k] >= Vmin) printf("%d ",a[k]); printf("\n"); } int main() { int nums[N] = {20,13,2,28,22,24,19,1,3}; findKNums(nums); return 0; }
解法四:用一个数组来存储最大的K个数,每新加入一个数X,如果X比最大的K个数中的最小的数Y小,那么最大的K个数还是保持不变。如果X比Y大,那么最大的K个数应该去掉Y,而包含X,所耗费的时间为O(N * K)。进一步,可以用容量为K的最小堆来存储最大的K个数,更新过程花费的时间复杂度为O(log2K)
#include <stdio.h> #define N 9 #define K 6 void adjustMinHeap(int h[], int pos) { int t,p,q; p = pos; while(p < K){ q = 2*p+1; if(q >= K) break; if((q < K-1) && (h[q+1] < h[q])) q = q+1; if( h[q] < h[p]){ t = h[p]; h[p] = h[q]; h[q] = t; p = q; } else break; } } void sortMinHeap(int a[]) { int i; for(i = K/2-1; i>= 0; i--) adjustMinHeap(a, i); for(i = K; i<N; i++){ if(a[i] > a[0]){ a[0] = a[i]; adjustMinHeap(a,0); } } } int main() { int nums[N] = {20,13,2,28,22,24,19,1,3}; sortMinHeap(nums); for(int i=0; i<K; i++) printf("%d ", nums[i]); printf("\n"); return 0; }
解法五:如果所有N个数都是正整数,且它们的取值范围不太大,可以考虑申请空间,记录每个整数出现的次数,然后再从大到小取最大的K个。比如,所有整数都在(0, MAXN)区间中的话,利用一个数组count[MAXN]来记录每个整数出现的个数(count[i]表示整数i在所有整数中出现的个数)。我们只需要扫描一遍就可以得到count数组。然后,寻找第K大的元素。
当实际情况下,并不一定能保证所有元素都是正整数,且取值范围不太大。上面的方法仍然可以推广适用。如果N个数中最大的数为Vmax,最小的数为Vmin,我们可以把这个区间[Vmin, Vmax]分成M块,每个小区间的跨度为d =(Vmax – Vmin)/M,即 [Vmin, Vmin+d], [Vmin + d, Vmin + 2d],……然后,扫描一遍所有元素,统计各个小区间中的元素个数,跟上面方法类似地,我们可以知道第K大的元素在哪一个小区间。然后,再对那个小区间,继续进行分块处理。这个方法介于解法三和类计数排序方法之间,不能保证线性。跟解法三类似地,时间复杂度为O((N+M)* log2M(|Vmax - Vmin|/delta))。遍历文件的次数为2 * log2M(|Vmax - Vmin|/delta)。当然,我们需要找一个尽量大的M,但M取值要受内存限制。
#include <stdio.h> #define N 9 #define K 6 #define MAXN 28//为了测试方便随便填的 void findKNums(int a[]) { int i,j,sumCount,v; int count[MAXN+1] = {0}; for(i = 0; i<N; i++) count[a[i]]++; for(i = 0; i<=MAXN; i++) printf("%d %d \n",i,count[i]); for(sumCount = 0, v = MAXN; v >= 0; v--){ sumCount += count[v]; if(sumCount >= K) break; } sumCount = 0; for(i = MAXN; i>=v; i--){ if(count[i] != 0) for(j = count[i]; j>0; j--){ printf("%d ",i); sumCount++; if(sumCount == K) break; } } } int main() { int nums[N] = {20,13,13,28,22,24,13,1,3}; findKNums(nums); printf("\n"); return 0; }
上面五个解法摘自《编程之美》,丑丑的代码是不才根据书中算法写的。这真是本好书,看来要买一本回来支持下正版。。。