算法导论 Exercises 9.3-8

Problem Description:

Let X[1...n] and Y[1...n] be two arrays, each containing n numbers already in sorted order.

Give an O(lgn)-time algorithm to find the median of all 2n elements in array X and Y.

问题描述:

X和Y是两个长度均为n的已排序数组,现要求以O(lgn)的时间复杂度找到两个数组中所有2n个数的中值。

(注:这里中值被定义为:对于奇数个数,排序后中间那个,或者对于偶数个数,排序后中间两个数下标较小的那个)

问题升级:

题目中假设了两个数组长度相等,并且找中值。

这里修改一下题目,假设两个数组长度不一定相等,分别为m和n,要求以O(lgm + lgn)的时间复杂度找到m+n个数中的第i个数。

先解决一般情况,再将原题作为一种特殊情况来处理。

 

解决方案:

如上图所示的两个已排序数组(假设为单调非减),其中aMid = (aBeg + aEnd) / 2是数组中值的下标。

{a1}是数组a中a[aBeg]至a[aMid]段的元素的集合,{a2}是数组a中a[aMid + 1]至a[aEnd]段的元素的集合。同理{b1}、{b2}如图所示。

 

假设a[aMid] <= b[bMid](a[aMid] > b[bMid]的情况与之类似),则有:

{a1}<={a2} {a1}<={b2}

即对于{a1}中的任意元素,在a、b中不小于它的数至少有(aEnd - aMid) + (bEnd - bMid) + 1个

所以对于{a1}中的任意元素,在a、b中小于它的数至多有

[(aEnd - aBeg + 1) + (bEnd - bBeg + 1)] - [(aEnd - aMid) + (bEnd - bMid) + 1]

=(aMid - aBeg) + (bMid - bBeg) + 1个......①

同理,{b2}>={b1} {b2}>={a1}

对于{b2}中的任意元素,在a、b中不大于它的数至少有(bMid - bBeg) + (aMid - aBeg) + 1

=(aMid - aBeg) + (bMid - bBeg) + 1个......②

由①、②可知:

如果i <= (aEnd - aMid) + (bEnd - bMid) + 1,那第i个数一定不在{b2}中。

此时只需在{a1}、{a2}、{b1}中继续找第i个数就可以了。

(当i == (aEnd - aMid) + (bEnd - bMid) + 1时,只有在a[aMid] == b[bMid]时,第i个数等于a[aMid]或者b[bMid],

此时虽然b[bMid]在{b2}中,但由于a[aMid]不在{b2}中,所以不影响我们“第i个数一定不在{b2}中”的判断)

如果i > (aEnd - aMid) + (bEnd - bMid) + 1,那第i个数一定不在{a1}中。

此时只需在{a2}、{b1}、{b2}中继续找第i - (aMid - aBeg + 1)个数就可以了。

同理在a[aMid] > b[bMid]时,可以得出类似的结论,只是a、b两个数组的角色互换。

 

由上面的分析,每次递归可以丢弃掉其中一个数组一半的元素直至递归结束。

递归结束的条件是其中一个数组已经为空,此时在另外一个数组里面直接找第i个数就可以了。

因此本算法的时间复杂度是O(lgm + lgn)的。

 

实现代码:

(代码里做了一点点优化,因为第a、b的第i个数一定在各自数组的前i个中)

View Code
 1 int ithSmallestNumberLog(int a[], int aBeg, int aEnd, int b[], int bBeg, int bEnd, int i)
 2 {    
 3     //the ith smallest number of all elements must be 
 4     //in the first i elements of either array.
 5     aEnd = aBeg + i - 1 < aEnd ? aBeg + i - 1 : aEnd;
 6     bEnd = bBeg + i - 1 < bEnd ? bBeg + i - 1 : bEnd;
 7     //index of mida and midb
 8     int aMid = (aBeg + aEnd) / 2;
 9     int bMid = (bBeg + bEnd) / 2;
10 
11     if (aBeg > aEnd)
12     {
13         return b[bBeg + i - 1];
14     }
15     if (bBeg > bEnd)
16     {
17         return a[aBeg + i - 1];
18     }
19     if (a[aMid] <= b[bMid])
20     {
21         if (i <= (aMid - aBeg) + (bMid - bBeg) + 1)
22         {
23             return ithSmallestNumberLog(a, aBeg, aEnd, b, bBeg, bMid - 1, i);
24         }
25         else
26         {
27             return ithSmallestNumberLog(a, aMid + 1, aEnd, b, bBeg, bEnd, i - (aMid - aBeg + 1));
28         }
29     }
30     else
31     {
32         if (i <= (aMid - aBeg) + (bMid - bBeg) + 1)
33         {
34             return ithSmallestNumberLog(a, aBeg, aMid - 1, b, bBeg, bEnd, i);
35         }
36         else
37         {
38             return ithSmallestNumberLog(a, aBeg, aEnd, b, bMid + 1, bEnd, i - (bMid - bBeg + 1));
39         }
40     }
41 }

 

测试:

取数组a长为500,数组b长为1000,数组c为a、b的合集。a、b元素为1到9999之间的随机数。

分别找a、b中所有元素中的第1至1500个元素。

测试代码如下:

View Code
 1 #define ARRAY_SIZE 500
 2  #define COUNT 1000
 3  
 4  int a[ARRAY_SIZE];
 5  int b[ARRAY_SIZE * 2];
 6  int c[ARRAY_SIZE * 3];
 7  
 8  int main(void)
 9  {
10      for (int z = 0; z != COUNT; ++z)
11      {
12          randarray(a, ARRAY_SIZE, 1, 9999);
13          randarray(b, ARRAY_SIZE * 2, 1, 9999);        
14          copyarray(a, 0, c, 0, ARRAY_SIZE);
15          copyarray(b, 0, c, ARRAY_SIZE, ARRAY_SIZE * 2);
16          quick_sort(a, 0, ARRAY_SIZE - 1);
17          quick_sort(b, 0, ARRAY_SIZE * 2 - 1);
18          quick_sort(c, 0, ARRAY_SIZE * 3 - 1);
19  
20          for (int i = 1; i <= ARRAY_SIZE * 3; ++i)
21          {
22              int resultTest = ithSmallestNumberLog(a, 0, ARRAY_SIZE - 1, 
23                                                    b, 0, ARRAY_SIZE * 2 - 1, i);
24              int resultStd = c[i - 1];
25  //             std::cout << "i = " << i << " resultTest = " << resultTest 
26  //                       << " resultStd = " << resultStd << std::endl;
27              if (resultTest != resultStd)
28              {
29                  std::cout << "Error" << std::endl;
30                  return - 1;
31              }
32          }
33          std::cout << "test " << z << " done." << std::endl;
34      }
35  
36      return 0;
37  }

 

 文中一些自定义函数的实现见文章“#include”

posted on 2012-10-28 23:18  Snser  阅读(805)  评论(0编辑  收藏  举报