【算法题】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

 

http://blog.jobbole.com/25710/

posted @ 2013-05-05 17:51  一点心青  阅读(620)  评论(0编辑  收藏  举报