在一个含有n个元素的集合中,有时我们需要找到第该集合中第
k
个小的元素,这也被称作第
1. 期望时间的选择算法
算法思想
这里我们要用到快速排序的思想,快速排序是将
1
个关键字pivot_key排到整个集合中的正确位置,并将整个集合分成两个部分,左边都不比它大,而右边的都不比它小,如果我们现在将这个关键字pivot_key所在位置pivot_pos和我们的
算法实现
下面我们直接通过代码来说明问题:
int randomized_Partition(int *arr, int from, int to) {
int pivot_key = arr[from];
int low = from;
int high = to;
while (low < high) {
while (low < high && pivot_key <= arr[high]) {
--high;
}
arr[low] = arr[high];
while (low < high && pivot_key >= arr[low]) {
++low;
}
arr[high] = arr[low];
}
arr[high] = pivot_key;
return high;
}
上面的代码我们很容易就是快速排序将集合分成两部分,并放回pivot_key, 正确的位置。
int randomized_select(int *arr, int from , int to, int i) {
if (from == to) {
return arr[from];
}
// 算出arr[from] 所在的 pivot 位置
int q = randomized_Partition(arr, from, to);
// arr[from] 所在序列中第几个元素
int k = q -from + 1;
int result;
if (i == k) {
result = arr[q];
} else if (i < k) {
result = randomized_select(arr, from, q - 1, i);
} else {
result = randomized_select(arr, q + 1, to, i - k);
}
return result;
}
这剩下的这一部分代码就是我们上述提到的核心思想。
算法分析
z这里我们主要分析它为什么期望时间是
O(n)
, 我们在这里假设
Xk
表示有k个元素的事件,且经过一次randomized_Partition我们要找的第
i
个顺序统计量都在
所以可得其期表达式:
而上述公式最终算出的期望值恰好为 T(n)=O(n)
2. 最坏情况为线性时间的选择算法
算法步骤
先假设我们这个算法函数为Select()
1) 将输入数组arr的
n
个元素划分为
2) 找这
⌈n5⌉
组中每一组的中位数:首先对每组元素进行插入排序,然后确定每组有序元素的中位数。
3) 对第2步中找出的
⌈n5⌉
个中位数,递归调用Select()找出其中位数
x
4) 利用修改过的partition函数,找出这个
5) 如果
t通过 上述算法描述,我们可以利用下面一张图进行形象化。
算法实现
插入排序
// 插入排序
void insertSorting(int *arr, int from, int to) {
int i, j, t;
int sc;
for (i = from + 1; i <= to; ++i) {
sc = arr[i];
j = i - 1;
while(j >= from) {
// 找到一个不满足arr[j] <= sc
if (arr[j] > sc) {
--j;
} else {
break;
}
}
// 将arr[j + 1, i - 1]向前移动一个位置
// 即arr[j + 2, i]
for (t = i; t > j; --t) {
arr[t] = arr[t - 1];
}
arr[j + 1] = sc;
}
}
改版的partition
// 讲指定位置pivot的元素作为主元
int partition(int *arr, int from, int to, int pivot) {
int sc = arr[pivot];
swap(arr + from, arr + pivot);
int low = from;
int high = to;
while (low < high) {
while (low < high && sc <= arr[high]) {
--high;
}
arr[low] = arr[high];
while (low < high && sc >= arr[low]) {
++low;
}
arr[high] = arr[low];
}
arr[low] = sc;
return high;
}
上述算法核心就是BFPRT算法:
// 利用BFPRT 求解第k小元素
int BFPRT(int *arr, int from, int to, int ith) {
// 当元素小于5个的时候直接插入并且返回
if(to - from + 1 <= 5) {
insertSorting(arr, from, to);
return arr[from + ith - 1];
}
int t = from - 1;
// 作为每5个数组的开始和结束的index
int st, ed;
// 至少有一组元素能进入这个循环, 也就是 to - from + 1 > 5
for (st = from; (ed = st + 4) <= to; st += 5) {
insertSorting(arr, st, ed);
++t;
swap(arr + t, arr + st + 2);
}
// 关心的是中位数的位置,而不是中位数的值
int pivot = (from + t) >> 1;
BFPRT(arr, from, t, pivot - from + 1);
int m = partition(arr, from, to, pivot);
// pivot 为第几小
int cur = m - from + 1;
int res;
if (cur == ith) {
res = arr[m];
} else if (cur < ith) {
res = BFPRT(arr, m + 1, to, ith - cur);
} else {
res = BFPRT(arr, from, m - 1, ith);
}
return res;
}
算法分析
从上图分析,如果我们去掉数组arr最右边不足5个那一组,以及中位数
x
所在的那一组,我们可以判定
也就是说,经过一次迭代或每一轮我们的就能确定至少有 3n10 比 x 小,则我们在最坏的情况,只需要和剩下的
如果我们假设每次迭代都没找到我们的第 k 小元素,则我们会有如下关于时间复杂度的数学关系式:
接下来我们着重分析 n≥140 的情况。我们的算法主要有 3 个耗时的步骤:
步骤 | 耗时 |
---|---|
对 |
f(n)=O(n) 就不详细数学公式证明(读者可以尝试使用 f(n)=an+b 来证明)。 讨论
致谢本文是基于《算法导论》写的,最主要的是有本人大量的心得体会,感谢《算法导论》的那些作者Thomas H.Cormen、Charles E.Leiserson等 人。如果有错误的请留言,不甚感激。谢谢。 参考《算法导论》Thomas H.Cormen、Charles E.Leiserson等 第三版 第9章 “中位数和顺序统计量” 源代码:转载,请注明
12-25
![]()
07-14
![]()
03-29
![]() “相关推荐”对你有帮助么?
提交
评论
![]() ![]() ![]()
查看更多评论
![]()
添加红包
![]() ![]() |