问题:对于一个包含n个元素的集合来说,k分为数就是指能把有序集合分成k个等大小集合的“k-1个顺序统计量”,给出一个能找出某一集合的k分位数的O(nlogk)的算法。
首先k要整除n,这样才可以分为k个等大小的集合。若将一个大小为n的集合按照顺序排好,我们所要求的这k-1个数就是要把这个集合平均分为k个集合。例如集合A= { 8, 4,0, -89, -12, 0, 36, 789, 21},将集合排序后A={-89,-12,0,0,4,8,21,36,789};若k=3,则将集合分为{-89,-12,0},{0,4,8},{21,36,789}那么2个顺序统计量为0,8
设d=n/k,于是这k-1个数是集合A中第d,2d....(k-1)d小的数。我们知道利用Select算法(选择的O(n)算法)找出第i小的数算法复杂度为O(n),那么逐一的找出这k-1个数时间为O(nk),而题目给出的时间是O(nlogk).于是需要结合二分的思想。 首先将第(k/2)*d小的数找出来放在A中正确的位置(数组A[0...n-1],则它正确的位置下标为(k/2)*d-1)),然后数组A[o..n-1]分为两部分A[0...(k/2)*d-2]和A[(k/2)*d...n-1],递归的放置其他的k-2个数。这样最终时间复杂度为O(nlogk).
#include<iostream>
#include<algorithm>
using namespace std;
void Select_K(int *a, int p, int r, int k);
int main(){
/*测试*/
int i;
int const n = 9;
const int k = 3;
int a[n] = { 8, 4,0, -89, -12, 0, 36, 789, 21};
Select_K(a, 0,9,k);
for (i =1; i < k; i++)
cout << a[n/k*i-1] << endl;
return 0;
}
int Partition(int a[],int low,int high, int x){ //这里数组a是[low,high)的,注意右边界最多到a[high-1],
/*利用x将数组划分为2部分*/
int i = low;
high--;
while (a[i]!= x) i++;
swap(a[low], a[i]); //将基准移到首位置
while (low < high){
while (low < high&&a[high] >= x) high--;
a[low] = a[high];
while (low < high&&a[low] <= x) low++;
a[high] = a[low];
}
a[low] = x;
return low;
}
int Select(int *a, int low, int high, int k){
int i, j,x,q,n;
n = high - low; //n为数组a[low...high]元素个数,注意右边最多取到a[high-1]
if (n < 5){ //元素小于5时候单独处理
sort(a + low, a + high);
return a[low +k-1];
}
for (i = 0; i <n/ 5; i++){
sort(a + low+i * 5, a + low+i * 5 + 5); //对每组数据排序
swap(a[low+i], a[low+i * 5 + 2]); //中位数移到前面
}
x = Select(a, low, low + n / 5, n / 10 + 1); //寻找中位数的中位数、n/10+1非常重要,避免n<10时n/10==0此时会出现问题
j = Partition(a, low, high, x); //根据x将数组a划分为2部分,j为x所在数组下标
q = j - low + 1; // q为小于或者等于x元素的个数
if (q == k)
return x;
else if (q>k)
return Select(a, low, j + 1, k);
else
return Select(a, j + 1, high, k - q);
}
void Select_K(int *a, int p, int r, int k){
if (k == 0)
return;
int d= (r - p)/k;
Select(a, p, r, (k / 2)*d);
Select_K(a, p, (k / 2)*d-1, k / 2);
Select_K(a, (k / 2)*d, r, k / 2);
}