问题
如何从一个无序数组中求出第 K 大的数(为了简化讨论,假设数组中的数各不相同)。
例如,对数组 { 5,12,7,2,9,3 }来说,第三大的是 5,第五大的是 9
随机选择算法
概念
最直接的想法是对数组排序,然后取出第 K 个。这样做法时间复杂度 O(nlogn),虽然看起来很好,但是还有更优的算法。
随机选择算法,对任何输入都可以达到 O(n)的期望时间复杂度
随机选择算法的原理类似于 随机-快速排序
假设主元为 A[p],当对 A[left,right] 执行一次 randPartition 函数之后,A[p] 左侧的元素个数就是确定的(p - left 个),而且他们都小于主元,那么 A[p] 就是第 p-left+1
大的数
不妨令 M = p - left + 1
-
如果 K==M,说明第 K 大的就是主元
-
如果 K < M,说明第 K 大的在主元的左边,也就是
[left,p-1]
的第 K 大,应该向左侧元素递归 -
如果 K > M,说明第 K 大的元素在右边,也就是
[p+1,right]
的第 K-M 大,应该向右侧元素递归
算法以 left==right
为递归边界,返回 A[left]
代码实现
// 随机选择算法,从 [left, right] 中返回第 K 大的数
public static int randSelect(int[] A, int left, int right, int K){
if(left == right) {
return A[left]; //边界
}
int p = randPartition(A, left, right); // 划分后主元的位置 P
int M = p - left + 1; // A[p] 是[left, right]中的第M大
if(K == M) {
//找到第K大的数
return A[p];
} else if(K < M){
// 第 K 大元素在主元左侧
return randSelect(A, left, p-1, K); // 向主元左侧找第 K 大
} else {
// 第 K 大的元素在主元右侧
return randSelect(A, p+1, right, K-M); // 向主元右侧找第 K 大
}
}
复杂度
可以证明,虽然最坏时间复杂度为 O( n 2 n^2 n2),但是其对任意输入的期望时间复杂度确是 O(n)
这意味着不存在一组特定的数据能使这个算法出现最坏情况,是个相当使用和出色的算法(详细证明可以参考算法导论)
应用
问题
给一个由各不相同的整数组成的集合,现在要将它分成两个子集,使:两子集合的并为原集合、两子集合的交为空集,同时在两个子集合的元素个数 n 1 n_1 n1 和