递归实践:求两个有序序列的中位数
完成日期:2017/9/26
实验内容
1. 实践题目
求两个有序序列的中位数
2. 问题描述
已知有两个等长的非降序序列S1, S2, 设计函数求S1与S2并集的中位数。
有序序列A0 ,A1 ,⋯,AN−1 的中位数指A(N−1)/2 的值,即第⌊(N+1)/2⌋个数(A0 为第1个数)。
要求时间复杂度为o(logN)
3. 算法描述
分别求出序列a和序列b的中位数a[mid1]和b[mid2],比较两个中位数的大小,会出现三种情况:
1). a[mid1] = b[mid2]:即a[mid1]为两个序列的中位数;
2). a[mid1] < b[mid2]:则中位数会出在a[mid1]和b[mid2]之间,在序列a
中删除a[mid1]之前的元素得到序列a1,在序列b中删除b[mid2]之后的元素得到序列b1;
3). a[mid1] > b[mid2]: 则中位数会出在b[mid2]和a[mid1]之间,在序列a
中删除a[mid1]之后的元素得到序列a1,在序列b中删除b[mid2]之前的元素得到序列b1;
在a1和b1中分别求出中位数,重复上述步骤1 2 3,如果得到两个序列都只剩一个元素,则两个元素中较小的那个就是所求。
注意:因为要求每次两个序列要舍弃的长度要相等,因此序列有奇数个元素和偶数个元素要分开讨论。
4. 算法时间及空间复杂度分析
因为每次求得两个序列的中位数之后,得到的两个子序列的长度都是上两个序列的一半,因为循环共执行log2N次,因此时间复杂度为o(logN)。
因为算法除了简单的变量以外,没有与N规模相关的辅助变量,因此空间复杂度为o(1)。
5. 程序代码
#include <iostream>
using namespace std;
void searchZ(int a[], int b[], int k){
int l1 =0, l2 = 0, r1 = k-1, r2 = k-1;
int mid1, mid2;
while (l1 <r1&&l2 <r2){
mid1 = (l1 + r1) / 2;
mid2 = (l2 + r2) / 2;
if ( a[mid1] == b[mid2]){
cout << a[mid1] << endl;
return;
}
else if (a[mid1] < b[mid2]){
if((l1 + r1) % 2 == 0){
l1 = mid1;
r2 = mid2;
}
else{
l1 = mid1 + 1;
r2 = mid2;
}
}
else{
if((l1 + r1) % 2 == 0){
r1 = mid1;
l2 = mid2;
}
else{
r1 = mid1;
l2 = mid2 + 1;
}
}
}
if (a[l1] < b[l2]){
cout << a[l1];
}
else{
cout << b[l2];
}
return;
}
int main() {
int k;
cin >> k;
int n[k+1];
int m[k+1];
for (int i = 0; i <k; i++){
cin >> n[i];
}
for (int j = 0; j < k; j++){
cin >> m[j];
}
searchZ(m, n, k);
return 0;
}
6. 程序运行截图
7. 心得体会
看到题目所要求的时间复杂度为o(logn)时,结合之前所学的归并排序,知道如果要算法时间的数量级为logn的话,每循环一次之后数据的规模都要减半,因此首先需要思考如何在每一次循环之后n要变成n/2,也就是运用上mid。
然后算法的框架出来之后,一开始运行的时候出现了无限循环的情况,后来思考了一下,发现自己忽略两个序列每次都要舍弃的长度要相同,因此需要分奇偶讨论。
后来老师提出了一个不需要分奇偶讨论的方法,就是mid1 = (left1 + right1) /2, mid2 = (left2 + right2+1)/2,这样对奇数个元素时没有影响,但是又能保证在偶数个元素时,两个序列舍弃的长度相同,但是这种做法我在实施的时候,遇到了一个很严重的问题:
思考了很久只得出了一个解决方法= =,就是在最后每个序列只剩下2个元素的时候,对这四个元素进行排序得到中位数,这样对时间复杂度没有做什么贡献,但是会有些累赘。除了这个以外就没想到其他的方法了。