算法导论 Exercises 9.3-7

Problem Description:

Describe an O(n)-time algorithm that, given a set of S of n distinct numbers and a positive integerd k ≤ n,

determines the k numbers in S that are closest to the median of S.

问题描述:

有一个长为n的数组且数组元素互不相同,要求以O(n)的时间复杂度找到离数组中值最近(与中值的差的绝对值最小)的k个数(k ≤ n)。

问题升级:

题目要求是找离中值最近的 k 个数,这里做一下推广,找离数组中第 i 个数最近的k个数。原题作为 i = n / 2 + n % 2; 时的特殊情况。

 

解决方案:

1、首先,找数组的第 i 小的数是O(n)的(参见 ithSmallestLinear )。

2、假设数组中第 i - k 小的数是 m ,第 i + k 小的数是 n ,则离数组第 i 个数最近的 k 个数一定在[m, n]这个大小范围内。

3、把原数组中上述范围内的数放到数组最前面,然后求这些数离第 i 个数的距离,并找到第 k 小的距离 distThreshold。

4、用这个阈值再扫描一遍原数组即可得到离第 i 个数最近的 k 个数。

需要注意的是:

由于原数组的元素是互不相同的,则满足离第 i 个数距离为distThreshold的数的个数只可能为 k 或者 k + 1。

因为存在这样一种情况:2 5 8 中 2 和 8 离 5 的距离相同都为 3,但不可能在原数组中找到第三个数离 5 的距离为 3。

因此在第四步扫描原数组找最近的 k 个数的时候要处理一下这种情况。

 

实现代码:

 

View Code
 1 View Code 

 2  void selectKClosestNumbers(int a[], int beg, int end, int i, int k)

 3  {

 4      if (k > end - beg)

 5      {

 6          return;

 7      }

 8  

 9      int ivalue = a[ithSmallestLinear(a, beg, end, i)];

10      int lowerBoundSeqNum = (i - k < 1) ? 1 : i - k;

11      int upperBoundSeqNum = (i + k > end - beg + 1) ? end - beg + 1 : i + k;

12      int lowerBound = a[ithSmallestLinear(a, beg, end, lowerBoundSeqNum)];

13      int upperBound = a[ithSmallestLinear(a, beg, end, upperBoundSeqNum)];      

14      

15      int count = 0;

16      //search elements in the range of [lowerbound, upperbound], at most 2k

17      for (int j = 0; (count != 2 * k) && (j != end); ++j)

18      {

19          if ((a[j] >= lowerBound) && (a[j] <= upperBound) && (a[j] != ivalue))

20          {

21              swap(a, j, count++);

22          }

23      }

24      int *dist = new int[count];

25      //calculate the distance to the ivalue

26      for (int j = 0; j != count; ++j)

27      {

28          dist[j] = abs(a[j] - ivalue);

29      }

30      int threshold = dist[ithSmallestLinear(dist, 0, count - 1, k)];

31      //the number of candidate whose distance to the ivalue <= threshold

32      //candidateNum == k or k+1

33      int candidateNum = 0;

34      for (int j = 0; j != count; ++j)

35      {

36          if (dist[j] <= threshold)

37          {

38              ++candidateNum;

39          }

40      }

41  

42      //select k elements

43      for (int j = 0, t = 0; t != k; ++j)

44      {

45          int tmpDist = abs(a[j] - ivalue);

46          if (tmpDist < threshold)

47          {

48              swap(a, j, t++);

49          }

50          //select the second candidate once there are two candidiates 

51          //whose distance to the ivalue equal threshold

52          if ((tmpDist == threshold) && (candidateNum-- != k + 1))

53          {

54              swap(a, j, t++);

55          }

56      }

57  

58      delete [] dist;

59  }

 

 

测试:

取数组元素为 1 2 3 4 5 6 7 8 9 10

①找与第6个数(6)最近的5个数,这里dist的阈值是3,而4和9同时满足这个阈值,按照实现代码里的逻辑,我们取排在后面的9。

②找与第1个数(1)最近的3个数,这种情况下没有比1小的数,所以这3个数应该是2 3 4。

测试代码:

View Code
 1 int main(void)

 2 {

 3     int testArrayA[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

 4     int testArrayB[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

 5 

 6     selectKClosestNumbers(testArrayA, 0, 9, 6, 5);

 7     selectKClosestNumbers(testArrayB, 0, 9, 1, 3);

 8 

 9     outputArray(std::cout, testArrayA, 0, 4);

10     outputArray(std::cout, testArrayB, 0, 2);

11 

12     return 0;

13 }

 

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

 

你可能感兴趣的:(算法导论)