【算法题】TopK相关问题简述
最近几天,看了与topk相关的几个问题,在这里做一些记录。
问题一:已知一个数组,求取数组的最小值和最大值。
方法一:将求取最小值和最大值看成单独的问题,每个问题单独解决,具体实现如下:
1 void MinMaxValue(int* data,int n,int& min_v,int max_v) 2 { 3 //求取最小值 4 min_v = data[0]; 5 for(int i = 1;i < n;i++) 6 { 7 if(data[i] < min_v) 8 min_v = data[i]; 9 } 10 //求取最大值 11 max_v = data[0]; 12 for(int i = 1;i < n;i++) 13 { 14 if(data[i] > max_v) 15 max_v = data[i]; 16 } 17 }
方法二:维持两个变量min和max,每次取两个数,比较1次,较小的和min比较,决定是否更新min;较大的和max比较,决定是否更新max。具体实现如下:
1 void MinMaxValue(int* data,int n,int& min_v,int max_v) 2 { 3 min_v = max_v = data[0]; 4 int min_tmp,max_tmp,i; 5 for(i = 1;i < n - 1;i += 2) 6 { 7 if(data[i] < data[i + 1]) 8 { 9 min_tmp = data[i]; 10 max_tmp = data[i + 1]; 11 } 12 else 13 { 14 min_tmp = data[i + 1]; 15 max_tmp = data[i]; 16 } 17 if(min_tmp < min_v) 18 min_v = min_tmp; 19 if(max_tmp > max_v) 20 max_v = max_tmp; 21 } 22 //处理最后落单的数据 23 if(i == n - 1) 24 { 25 if(data[i] < min_v) 26 min_v = data[i]; 27 if(data[i] > max_v) 28 max_v = data[i]; 29 } 30 }
正上述代码所示,每处理二个数据,需要比较3次,其总的比较次数为1.5n。
方法三:将数组两两分组,并比较,将小数置于数组前半部分,大数置于数组所半部分;然后在数组的前半部分进行比较找到最小的数,在数组的后半部分找到最大的数;如果n为奇数,将数组中间数最小值和最大值比较,得到最终的最小值和最大值。具体实现如下:
1 void MinMaxValue(int* data,int n,int& min_v,int& max_v) 2 { 3 int i = 0,j = n - 1; 4 while(i < j) 5 { 6 if(data[i] > data[j]) 7 { 8 //swap data[i],data[j] 9 int tmp = data[i]; 10 data[i] = data[j]; 11 data[j] = data[i]; 12 } 13 i++; 14 j--; 15 } 16 min_v = data[0]; 17 for(i = 1;i < (n + 1)/2;i++) 18 { 19 if(data[i] < min_v) 20 { 21 min_v = data[i]; 22 } 23 } 24 max_v = data[(n + 1)/2]; 25 for(i = (n + 1) / 2 + 1;i < n;i++) 26 { 27 if(data[i] > max_v) 28 { 29 max_v = data[i]; 30 } 31 } 32 if(n % 2) 33 { 34 int mid = n / 2; 35 if(data[mid] < min_v) 36 { 37 min_v = data[mid]; 38 } 39 if(data[mid] > max_v) 40 { 41 max_v = data[mid]; 42 } 43 } 44 }
方法四:分而治之,将数组分成两半,递归计算前一半的min和max;递归计算后一半的min和max;然后比较前一半min和后一半的min取二者最小值,比较前一半的max和后一半的max取二者最大值。具体实现如下:
1 void MinMaxValue(int* data,int begin,int end,int& min_v,int& max_v) 2 { 3 if(end - begin == 1) 4 { 5 min_v = max_v = data[begin]; 6 return; 7 } 8 int min_v_front,min_v_back,max_v_front,max_v_back; 9 MinMaxValue(data,begin,begin + (end - begin) / 2,min_v_front,max_v_front); 10 MinMaxValue(data,begin + (end - begin) / 2,end,min_v_back,max_v_back); 11 min_v = (min_v_front < min_v_back)? min_v_front : min_v_back; 12 max_v = (max_v_front < max_v_back)? max_v_front : max_v_back; 13 }
问题二:已知一数组,求取数组的最大值和次大值。
这个问题与第一个问题类似,其区别只是以次大值取代最小值,解决该问题的思路也可依据前面的四种解决。其所需的比较次数也是O(1.5n)。下面简单的实现:
1 void MaxMax2Value(int* data,int n,int& max_v,int& max2_v) 2 { 3 if(data[0] < data[1]) 4 { 5 max_v = data[1]; 6 max2_v = data[0]; 7 } 8 else 9 { 10 max_v = data[0]; 11 max2_v = data[1]; 12 } 13 for(int i = 2;i < n;i++) 14 { 15 if(data[i] > max2_v) 16 { 17 if(data[i] < max_v) 18 { 19 max2_v = data[i]; 20 } 21 else 22 { 23 max2_v = max_v; 24 max_v = data[i]; 25 } 26 } 27 } 28 }
另一种方法,利用比较过程中的中间结果。
思路如下:对n个数两两比较,每次比较的较大值进入下一轮;对最多ceil(n/2)个较大值继续上述两两比较,得出较大值进下一轮比较;最后得出的较大值即为最大值。共进行了n-1次的比较操作。其关键点是:第二大的数一定是与最大值进行过比较的那些数中间的最大值。由于最大值一共进行了ceil(logn)次比较,因此我们需要从这ceil(logn)个数中找最大值,比较次数为ceil(logn)-1。总的比较次数为n+ceil(logn) -2。
该方法的比较次数比1.5n少,但是需要额外的存储空间。
问题三:找出无序数组的第K大的数。
针对该问题,若K=2时,则可以参用问题二中的解决方法。
K!=2时,可以利用排序先对数组进行排序,然后输出data[k-1]即为第K大的数,但这种方法的时间复杂度为O(n*log(n)).
另一种方法,利用STL中提供的nth_element()可以很方便的求出第K大的数,其平均的时间复杂度为O(n).
nth_element()的算法原理是利用快速排序的思想,从数组data中随机找出一个元素X,把数组分为两部分Sa和Sb。Sa中的元素大于等于X,Sb中元素小于X。这时有两种情况:1)Sa元素的个数小于k,则Sb中的第k-|Sa|个元素即为第k大数;2)Sa中元素的个数大于等于K,则返回Sa中的第k大数。
从该问题出发,利用nth_element()解决效率更好。但是在实际应用中,可能会根据具体应用情况而选用排序方法。
假设对于数组data,我们需要找出第K大的数,k={2,5,10,...},相当于对同一数组有m个求第K大数的请求。
若此时,利用nth_element()解决时,其时间复杂度为O(n*m),而利用排序的方法,则只需排序一次,以后每次请求第K大数都是线性的,其时间复杂度为O(n*log(n)).由此可见,具体应用中选用什么样的算法,需要根据应用需求确定。
参考资料:
http://hi.baidu.com/silverxinger/item/92d3d6c11b511b5dad00efa1