问题描述
给定线性序集中n 个元素和一个正数k ,1 ≤ k ≤ n ,要求找出这n个元素中第k大的元素
算法描述:
对于该问题的解决可采用一种类似于快速排序的划分的方法。
最开始我们将所有的元素五个一组,分为⌈n/5⌉个组,然后对于每组无论采用哪种排序算法进行排序,找出其中位数,最后找出共⌈n/5⌉个中位数。
递归调用该算法找出这⌈n/5⌉个中位数的中位数。(如果⌈n/5⌉为偶数的话,就找出其较大的中位数)
然后以该元素作为划分基准,将所有的中位数进行划分。
如图所示
此时已经将所有的分组按照其中位数进行划分。
将所有元素以基准为中心划为四个区域
此时所有处于左下区域的元素都小于基准。右上区域的元素都大于基准。
即至少判断得到⌊n/10⌋*3个数字小于基准,⌊5/10⌋*3个数字大于基准。
此时⌊n/10⌋*3>n/4
经过该方法,每次递归至少可以省区1/4的元素。
复杂度分析如下:
在此,我们的算法的递归调用只有在n>75时执行,当小于75时我们通过简单的排序然后输出第k大的数。
有⌊n/10⌋*3>n/4
若需要对左下区域的元素进行递归,则复杂度为为T(n/4)
否则需要T(4*n/3)
另外我们得到无论是什么样的数据,当n<75是,其复杂度不会超过一个常数。
由以上可得:
解此递归式可得T(n)=O(n);
#include<iostream>
#include<cstdio>
#include<random>
using namespace std;
int n, k, len;
//选择排序
void SelectSort(int a[], int p, int r);
//将x作为基准数将数组分割,返回x的位置
int Partition(int a[], int p, int r, int x);
//交换两个元素
void Swap(int& a, int& b);
//找每组的中位数,返回中位数的位置i
int SearchMid(int a[], int p, int r);
//线性划分
int Select(int a[], int p, int r, int k);
int main()
{
//输入数组长度n
cin >> n;
//输入数组
int* a = new int[n];
for (int i = 0; i < n; ++i)
{
cin >> a[i];
}
//输入第k小
cin >> k;
//找第k小并返回
int res = Select(a, 0, n - 1, k);
cout << res << endl;
delete[]a;
return 0;
}
void SelectSort(int a[], int p, int r)
{
for (int i = p; i < r; ++i)
{
int index = i;
for (int j = i + 1; j <= r; ++j)
{
if (a[j] < a[index])
{
index = j;
}
}
Swap(a[i], a[index]);
}
}
int Partition(int a[], int p, int r, int x)
{
//i指向首元素的前一个位置,j指向尾元素的后一个位置
int i = p - 1, j = r + 1;
while (1)
{
//i从基准数右边的元素开始找,直到找到第一个大于等于基准数的元素
while (a[++i] < x && i < r);
//j从尾元素开始找,直到找到第一个小于等于基准数的元素
while (a[--j] > x && j > p);
//若i>=j,说明基准数的位置已找到,为j
if (i >= j)
{
break;
}
//交换两个元素,使得基准数左边的数均不大于它,右边的数均不小于它
Swap(a[i], a[j]);
}
//返回基准数的位置
return j;
}
void Swap(int& a, int& b)
{
int temp;
temp = a;
a = b;
b = temp;
}
int SearchMid(int a[], int p, int r)
{
//建立与数组a同等大小的数组b
int* b = new int[r - p + 1];
//用数组b存放数组a(注意此时b的首地址为0,而a的首地址为p)
for (int i = p; i <= r; ++i)
{
b[i - p] = a[i];
}
//将数组b排序,b[(r-p+1)/2]为中位数
SelectSort(b, 0, r - p);
for (int i = p; i <= r; ++i)
{
if (a[i] == b[(r - p + 1) / 2])
{
return i;
}
}
delete[]b;
return 0;
}
int Select(int a[], int p, int r, int k)
{
if (r - p < 5)
{
SelectSort(a, p, r);
return a[p + k - 1];
}
//分成n/5组,每组5个,找到每组的中位数并将它放到数组首元素的位置
for (int i = 0; i <= (r - p - 4) / 5; ++i)
{
int mid = SearchMid(a, p + 5 * i, p + 5 * i + 4);
Swap(a[mid], a[p + i]);
}
//找到各组中位数的中位数
int x = Select(a, p, p + (r - p - 4) / 5, (r - p - 4) / 10 + 1);
//按照中位数划分
int i = Partition(a, p, r, x);
//求较小数数组的长度
len = i - p + 1;
//若较小数数组的长度小于等于k,说明第k小的元素在这个数组内,将其递归
if (k <= len)
{
return Select(a, p, i, k);
}
//否则,说明第k小的元素在较大数数组,将其递归
else
{
return Select(a, i + 1, r, k - len);
}
}