中位数选取
描述
任给有序向量S1和S2,如何找出它们归并后所得有序向量S=S1∪S2的中位数?
蛮力解法
代码实现(C++)
template <typename T>
T trivialMedian(vector<T>& S1, int lo1, n1, vector<T>& S2, int lo2, int n2) {
int hi1 = lo1 + n1, hi2 = lo2 + n2;
vector<T> S;
while((lo1 < hi1) && (lo2 < hi2)) {
while((lo1<hi1) && S1[lo1] <= S2[lo2])
S.push_back(S1[lo1++]);
while((lo2<hi2) && S2[lo2] <= S1[lo1])
S.push_back(S2[lo2++]);
}
while(lo1 < hi1) S.push_back(S1[lo1++]);
while(lo2 < hi2) S.push_back(S2[lo2++]);
return S[(n1+n2)/2];
}
复杂度分析
时间复杂度、空间复杂度均为O(n1 + n2)。
优化
以上蛮力解法没有充分利用“两个子向量已经有序”的条件,仅适合n1、n2不太大的情况下。实际上可以充分利用“已经有序”的条件,以及“减而治之”的思想,来更快地完成这一任务。
代码实现
template <typename T>
T median(vector<T>& S1, int lo1, int n1, vector<int>& S2, int lo2, int n2) {
if(n2 < n1)
return median(S2, lo2, n2, S1, lo1, n1); //严格保证n1<=n2
if(n2 < 6)
return trivialMedian(S1, lo1, n1, S2, lo2, n2); //递归基
if(2*n1 < n2)//若两个向量长度相差悬殊,则长者(S2)的两翼可直接截除
return median(S1, lo1, n1, S2, lo2 + (n2 - n1 - 1) / 2, n1 + 2 - (n2 - n1) % 2);
int mi1 = lo1 + n1/2;
int mi2a = lo2 + n2/2;
int mi2b = lo2 + n2 - 1- n1/2;
if(S2[mi2b] < S1[mi1]) //取S1左半、S2右半
return median(S1, lo1, n1/2+1, S2, mi2a, n2-(n1-1)/2);
else if(S1[mi1] < S1[mi2a]) //取S1右半、S2左半
return median(S1, mi1, (n1+1)/2, S2, lo2, n2-n1/2);
else //S1保留,S2左右同时缩短
return median(S1, lo1, n1, S2, mi2a, n2-(n1-1)/2*2);
}
复杂度分析
时间复杂度为O(log(n1+n2)),更精确地,其复杂度为O(log(min(n1,n2)))。
第k位数选取
第k位数选取是中位数选取的更加一般的问题。
代码实现
template <typename T> void quickSelect(vector<T>& A, Rank k) {
for(int lo = 0, hi = A.size() - 1; lo < hi; ) {
int i = lo, j = hi;
T pivot = A[lo];
while(i < j) {
while((i<j) && (pivot <= A[j]))
--j;
A[i] = A[j];
while((i<j) && (A[i] <= A[i]))
++i;
A[j] = A[i];
}// assert: i == j
A[i] = pivot;
if(k <= i) hi = i - 1;
if(i <= k) lo = i + 1;
}//A[k] is now a pivot
}
复杂度分析
最坏情况下时间复杂度为O(n2)。