问题描述
- 将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;}
