算法导论 Exercises 9.37

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”

原文地址:https://www.cnblogs.com/snser/p/2746973.html