算法导论 Exercises 9.3-6

Problem Description:

The kth quantiles of an n-element set are the k - 1 order statistics that divide the sorted set into
k equal-sized sets (to within 1). Give an O(n lg k)-time algorithm to list the kth quantiles of a set.

问题描述:

一个集合的第k分位数是指这样k - 1个数:

若将一个大小为n的集合从小到大排好序,这k - 1个数能将这个有序的数列分为k组,并且每组元素的个数相差不超过1。

现给出一个长为n的数组(无序),要求以O(n lg k)的时间复杂度找到其 k 分位数。

比如:

8 4 5 3 2 6 1 7 9  排序后是 1 2 3 4 5 6 7 8 9

其 3 分位数是 3 6,将排序后的数列分成 1 2 3; 4 5 6; 7 8 9 三段,每段元素个数均为3。

2 9 3 4 3 3 9 1 3 8 1 排序后是 1 1 2 3 3 3 3 4 8 9 9

其 4 分位数是 2 3 8,将排序后的数列分成 1 1 2; 3 3 3; 3 4 8; 9 9 四段,每段元素个数分别为 3 3 3 2。

 

解决方案:

首先,我们知道在一个长度为n的无序数组里找到第 i 个数的时间复杂度是O(n),具体见这篇文章里的算法 ithSmallestLinear

所以最直观的方法是调用k次ithSmallestLinear,但这样总时间复杂度是O(kn)而不是题目要求的O(n lg k)。

为了实现从 k 到 lgk 的突破,必然采用分治的方法(我们先假定 n/k 刚好整除):

0、如果 k == 1 ,return

1、i = k / 2     ……O(1)

    将数组 a 划分成两部分 A 和 B,使得 A 中所有元素不大于 B,且 A 中元素的个数为 tmpSize = (n / k) * i。    ……O(n)

2、在 A 中找第 i 分位数    ……规模减小为 k / 2

3、输出a[tmpSize - 1]    ……O(1)

4、在 B 中找第 k - i 分位数    ……规模减小为 k / 2

由上面的分析,这个算法的时间复杂度满足题设要求O(n lg k)。

 

如果 n/k 不是刚好整除(假设 x = n%k,x != 0),那么我们定义k分位数的分组为:

前 x 组每组的元素个数为n/k⌋ + 1,后 k - x 组每组元素个数为⌊n/k⌋。

比如15个元素分为4组,则每组为 4 4 4 3 个元素。

 

对应的,将tmpSize更改为 (n / k) * i + (n % k < i ? n % k : i)

( PS:若 k==n ,这实际上是一个O(n lg n)的排序算法。)

 

实现代码:

View Code
 1 //9.3-6

 2 //list the kth quantiles of a set

 3 void kthQuantiles(int a[], int beg, int end, int k)

 4 {

 5     if (k == 1)

 6     {

 7         return;

 8     }

 9 

10     int len = end - beg + 1;

11     int i = k / 2;

12     int tmpSize = (len / k) * i + (len % k < i ? len % k : i);

13     int pivotLoc = ithSmallestLinear(a, beg, end, beg + tmpSize);

14     pivotLoc = partitionSpecifyPivot(a, beg, end, pivotLoc);

15 

16     kthQuantiles(a, beg, beg + tmpSize - 1, i);

17     std::cout << a[beg + tmpSize - 1] << " ";

18     kthQuantiles(a, beg + tmpSize, end, k - i);

19 }

 

测试代码:

View Code
 1 #define ARRAY_SIZE 15

 2 #define COUNT 10

 3 

 4 int a[ARRAY_SIZE];

 5 

 6 int main(void)

 7 {

 8     int arraySize = ARRAY_SIZE;

 9     int k = 7;

10     for (int j = 0; j != COUNT; ++j)

11     {

12         //std::cin >> arraySize >> k;

13         randArray(a, arraySize, 1, ARRAY_SIZE * 2);

14         copyArray(a, 0, b, 0, arraySize);

15         quickSort(b, 0, arraySize - 1);

16 

17         //list kth quantiles in O(nlgk)-time

18         kthQuantiles(a, 0, arraySize - 1, k);

19         std::cout << std::endl;

20 

21         //output standard kth quantiles

22         int remainder = arraySize % k;

23         int groupSize = arraySize / k;

24         int currentLoc = -1;

25         for (int i = 1; i != k; ++i)

26         {

27             currentLoc += (groupSize + ((remainder--) > 0 ? 1 : 0));

28             std::cout << b[currentLoc] << " ";

29         }

30         std::cout << std::endl << std::endl;

31     }

32 

33     system("pause");

34     return 0;

35 }

 

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

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