问题描述
- 将n个元素分成n/5组
- 且各个组内分别排序 ,
- 取出各组的中位数,共n/5个
- 递归的调用1 , 找出n个元素中的类似中位数(中位数的中位数)
- 通过该中位数划分原数组
- 将原数组左右两侧的与该中位数等值的元素聚集(解决有重复元素)
- 注意 左边的放到左边 , 右边的放到右边
- 现在 中位数等值集合的位置,就是排好序之后的位置 , 也就是可以单独确定出中位数的第几小的值 , 将中位数集合下标和 目标K值比较
- 如果K值落在中位数集合中 , 则改中位数就是第k小的元素
- 如果K小于中位数的位置 , 则递归的在左边子序列找第k小的元素
- 如果k大于中位数的位置 , 则递归的在右边子序列找第k - (当前中位数及中位数左边元素的个数) 小的元素
算法分析
- 证明该算法可以在O(n)时间内找到第k小的元素
- 将n个元素分成 n/5 个小组 , 组内排序 , 找到中位数的中位数, 作为划分基准
- 组内排序每组 O(1) , 共有n/5个组
- 中组内中位数 T(n/5)
- 图中X一定大于同行左边的中位数 , 故一定大于其通组内小于中位数的两个数 ,
- 则X至少大于3(n/5/2) = 3n/10个元素 ,
- 则之多大于 7n/10 个元素
- 则X至少大于3(n/5/2) = 3n/10个元素 ,
- 故当前问题的子问题规模最大不超过 7n/10 T(n)=T(7n/10) + …
- 找到中位数的中位数X之后 , 对当前问题数组划分 , 划分操作复杂度为 o(n)
- 综上递归式为 T(n) = T(n/5) + T(7n/10) + Cn
T(n) = T(n/5) + T(7n/10) + CO(n) 数学归纳法 : 假设 : T(n) = O(n) 则
代码
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#include<algorithm>
#include<time.h>
using namespace std;
//数组元素个数
const int N = 500;
//元素最大值
const int MAX = 500;
bool cmp(int a, int b) {
return a < b;
}
void swap(int a[], int c, int d) {
int t = a[c];
a[c] = a[d];
a[d] = t;
}
//小数组数组 排序范围左 排序范围右 交换目标
void sortandswap(int a[], int p, int r, int tar) {
//选择排序
int max;
//只需要找打第三大的 (组内中位数)
for (int i = 0; i <= r - p - 2; i++) {
max = p;//默认最大值的为第一个
for (int j = p + 1; j <= r - i; j++) {//在未排序的获得最大的
if (a[j] > a[max]) {
max = j;
}
}
swap(a, max, r - i);
if (i == 2) {//找到组内中位数
break;
}
}
swap(a, tar, p + 2);
}
//将和主元的值相同的元素集中
int Amass(int a[], int p, int r, int pivort, int& p1, int& pr) {
int num1 = 0, num2 = 0;
for (int i = p; i <= r; i++) {
if (a[i] == a[pivort] && i != pivort) {
if (i < pivort - num1) {//左边重复的
num1++;
swap(a, i, pivort - num1);
}
else if (i > pivort + num2) {//右边重复的
num2++;
swap(a, i, pivort + num2);
}
}
}
p1 = pivort - num1;
pr = pivort + num2;
return num1 + num2;
}
//根据主元划分 返回主元下标
int partition(int a[], int p, int r, int pivort) {
//将基准放到最左边
swap(a, p, pivort);
int i = p, j = r + 1, x = a[p];
while (1) {
while (a[++i] < a[p] && i <= r);
while (a[--j] > a[p]);
if (i >= j) {
break;
}
swap(a, i, j);
}
a[p] = a[j];
a[j] = x;
return j;
}
//在数组a,a[p:r] 序列中找到第k小的元素 (!!!即排序后下标为a[p+k-1])的元素
int Select(int a[], int p, int r, int k) {
if (r - p < 75) {
//直接排序,然后返回
sort(a + p, a + r + 1, cmp);
return p + k - 1;
}
else {
//将a[p+i*5 : p+i*5+4] 中的中位数和a[p+i] 交换位置
for (int i = 0; i <= (r - p - 4) / 5; i++) {
sortandswap(a, p + i * 5, p + i * 5 + 4, p + i);
}
//找到中位数集合a[p:p+(r-p-4)/5]的中位数 pivort为下标
int pivort = Select(a, p, p + (r - p - 4) / 5, (r - p - 4) / 10);
//根据中位数的中位数划分数组
pivort = partition(a, p, r, pivort);
//TODO 集中和a[pivort]相等的元素到一起 , 到a[pivort : pivort+m] , m 为除了a[pivort]之外相等的元素个数
int pl, pr;
int m = Amass(a, p, r, pivort, pl, pr);
//判断是否需要继续递归寻找
int j = pl - p; //小于pivort的个数
if (k > pl - p && k <= pr - p + 1) {
return pl;
}
else if (k <= pl - p) {//在左边找
return Select(a, p, pl - 1, k);
}
else if (k > pr - p + 1) {//在右边找
return Select(a, pr + 1, r, k - (pr - p + 1));
}
}
}
int main()
{
while (1) {
//随机填充数组
srand(time(NULL));
int a[N];
for (int i = 0; i < N; i++) {
a[i] = rand() % MAX;
}
//随机取得第k大的元素
int k = rand() % N;
//获得结果
printf("------------------------------------------------------\n");
printf("k : %d\n",k);
int resIndex = Select(a, 0, N - 1, k);
int res = a[resIndex];
//校验并输出(对数组排序,并获取a[k-1]) 作为校验值
sort(a, a+ N, cmp);
int verify = a[k - 1];
if (verify != res) {//校验
printf("not equ! %d %d\n",res,verify);
}
else {
printf("第%d小的元素为 : %d \t校验 : %d", k,res, verify);
}
system("pause");
}
return 0;
}