一、线性时间选择
1. 问题描述
给定线性序集中n个元素和一个整数k,1≤k≤n,要求找出这n个元素中第k小的元素。
2. 线性时间选择-随机划分
算法步骤:
- 生成1个随机数 i ,将数组a[p:r]被划分成两个子数组a[p, i]和a[i+1, r];
- 使a[p, i]中每个元素都不大于a[i+1, r]中每个元素;
- 计算子数组a[p, i]中的元素个数j,如果k<=j,则a[p, r]中第k小个元素落在子数组a[p, i]中;如果k>j,则要找的第k小的元素落在子数组a[i+1, r]中。
- 递归的使用这种分割,直到子数组只有1个元素。
3. 线性时间选择-中位数
基本思想:
- 如果能在线性时间内找到一个划分基准,使得按这个基准划分出的两个子数组的长度都至少为原数组长度的ε倍(0<ε<1是某个正常数),那么在最坏情况下用O(n)时间就可以完成选择任务。
- 例如,若ε=9/10,算法递归调用所产生的子数组的长度至少缩短1/10。所以,在最坏情况下,算法所需的计算时间T(n)满足递归式T(n)<=T(9n/10)+O(n)。由此可得T(n)=O(n)。
算法步骤:
可以按以下步骤找到满足要求的划分基准。
- 将n个输入元素划分成「n/5」个组,每组5个元素,除可能有一个组不是5个元素外。用任意一种排序算法,将每组中的元素排好序,并取出每组的中位数,共「n/5」个。
- 递归调用Select找出这「n/5」个元素的中位数。如果「n/5」是偶数,就找它的两个中位数中较大的一个。然后以这个元素作为划分基准。
示意图:
- 下图是上述划分策略的示意图,
- 其中n个元素用小圆点来表示,空心小圆点为每组元素的中位数。中位数的中位数x在图中标出。图中所画箭头是由较大元素指向较小元素的。
二、分治算法实现
1. 编写程序代码
线性时间选择-随机划分:
// 完整程序待补全...
// 线性时间选择-随机划分
int RandomizedSelect(int a[], int p, int r, int k)
{
if (p == r)
return a[p];
// 数组a[p:r]被划分成两个子数组a[p, i]和a[i+1, r],
// 且a[p, i]中每个元素都不大于a[i+1, r]中每个元素。
int i = RandomizedPartition(a, p, r);
// 左子数组a[p, i]的长度
int j = i - p + 1;
// k<=j,则第k小元素在左子数组,否则在右子数组
if (k <= j)
return RandomizedSelect(a, p, i, k);
else
return RandomizedSelect(a, i + 1, r, k - j);
}
线性时间选择-中位数:
// 线性时间选择-中位数
template <class Type>
Type Select(Type a[], int p, int r, int k)
{
if (r - p < 75)
{
// 此处,某个简单排序算法对数组a[p, r]排序
return a[p + k - 1];
}
for (int i = 0; i <= (r - p - 4) / 5; i++)
{
// 此处,将a[p+5*i]的第3小元素与a[p+i]交换位置
Type x = Select(a, p, p + (r - p - 4) / 5, (r - p - 4) / 10); // 找中位数的中位数,(r-p-4)/5即n-5
int i = Partition(a, p, r, x);
int j = i - p + 1;
if (k <= j)
return Select(a, p, i, k);
else
return Select(a, i + 1, r, k - j);
}
}
2. 运行结果展示
To be continue……
…
三、友情链接~
- 其它一些常见算法请参阅此链接~
最后,非常欢迎大家来讨论指正哦!