划分为n/5个块后利用“中位数的中位数”分治求解。具体步骤如下:
- 将所有元素分成⌈n/5⌉组,每组5个元素(最后一组可能有不足5个元素)。
- 找出每组中的中位数,共⌈n/5⌉个。
- 递归地使用select算法,在这⌈n/5⌉个中位数中找出中位数,记作m*。
- 基于m*对所有元素进行划分,假设有x-1个元素小于m*,n-x个元素大于m*。
- 如果k=x,则直接返回m*。
- 如果k<x,则递归地对小于m*的元素用select算法选择阶为k的元素。
- 如果k>x,则递归地对大于m*的元素用select算法选择结为k-x的元素。
以上算法描述摘录自黄宇老师的《算法设计与分析 第2版》一书。至于为什么划分为5个元素一组,这是为了尽可能降低时间复杂度,这是可以证明的(待我以后填坑)。C++算法如下:
#include<iostream>
#include<vector>
using namespace std;
int n, k;
vector<int>arr;//存储数据
int partition(vector<int>& a, int s, int e, int pivot) {//划分函数
int i = s, j = e;
while (i <= e && j >= s) {
while (a[i] < pivot)i++;
while (a[j] > pivot)j--;
if (i >= j)break;
else {
swap(a[i], a[j]);
}
}
return j;
}
void select_lsort(vector<int>&a,int s, int e)//选择排序
{
for (int i = s; i < e; i++) {
int min = i;
for (int j = i + 1; j < e; j++)
if (a[j] < a[min])
min = j;
std::swap(arr[i], arr[min]);
}
}
int liner_select(vector<int>&a,int s,int e,int k) {//算法主体
if (e - s < 5) {
select_lsort(a,s, e);
return a[s + k - 1];
}
int base;
for (int i = 0; i <= (e - s - 4) / 5; i++) {//通过不超过五次比较找出每组的中位数
base = s + i * 5;
if (a[base] > a[base + 1])//CEX 1,2
swap(a[base], a[base + 1]);
if (a[base + 2] > a[base + 3])//CEX 3,4
swap(a[base + 2], a[base + 3]);
if (a[base] > a[base + 2])//CEX 1,3
{
swap(a[base], a[base + 2]);
swap(a[base + 1], a[base + 3]);
}
if (a[base + 1] > a[base + 4])//CEX 2,5
{
swap(a[base + 1], a[base + 4]);
}
if (a[base + 1] > a[base + 2])//CEX 2, 3
{
swap(a[base + 1], a[base + 2]);
swap(a[base + 3], a[base + 4]);
}
if (a[base + 2] > a[base + 4])//CEX 3, 5
{
swap(a[base + 2], a[base + 4]);
}
swap(a[base + 2], a[s + i]);
}
int mid = liner_select(a, s, s + (e - s - 4) / 5, ((e - s - 4) / 5 + 1) / 2+1);
int mid_index = partition(a, s, e-1, mid);
int mid_rank = mid_index - s + 1;
if (k == mid_rank) {
return a[mid_index];
}
else if (k < mid_rank) {
return liner_select(a, s, mid_index, k);
}
else {
return liner_select(a, mid_index + 1, e, k - mid_rank);
}
}
int main()
{
int temp;
cin >> n >> k;
for (int i = 0; i < n; i++)
{
cin >> temp;
arr.push_back(temp);
}
cout << liner_select(arr, 0, n, k) << endl;
}
这里对于数据规模小于5的数据,我直接采用了选择排序。划分时取的是下整,至于剩余不超过五个的元素那一组,则进入第三步递归进行处理。
利用不超过6次比较找到五个数的中位数,网上的答案大多是错误的,这里给出正确的思路(详情可见代码),可以参考下面图片:
时间复杂度为:
T
(
n
)
=
O
(
1
)
,
n
<
5
T(n)=O(1), n<5
T(n)=O(1),n<5
T
(
n
)
=
T
(
n
/
5
)
(
递
归
选
择
中
位
数
的
中
位
数
)
+
T
(
3
n
/
4
)
(
递
归
地
对
三
部
分
元
素
进
行
选
择
)
+
O
(
n
)
,
n
>
=
5
T(n)=T(n/5)(递归选择中位数的中位数)+T(3n/4)(递归地对三部分元素进行选择)+O(n),n>=5
T(n)=T(n/5)(递归选择中位数的中位数)+T(3n/4)(递归地对三部分元素进行选择)+O(n),n>=5。