算法第二章上机实践报告
实践题目:7-3 两个有序序列的中位数
题目描述:已知有两个等长的非降序序列S1, S2, 设计函数求S1与S2并集的中位数。有序序列A0,A1,⋯,AN−1的中位数指A(N−1)/2的值,即第⌊(N+1)/2⌋个数(A0为第1个数)。
输入格式:
输入分三行。第一行给出序列的公共长度N(0<N≤100000),随后每行输入一个序列的信息,即N个非降序排列的整数。数字用空格间隔。
输出格式:
在一行中输出两个输入序列的并集序列的中位数。
输入样例1:
5
1 3 5 7 9
2 3 4 5 6
输出样例1:
4
输入样例2:
6
-100 -10 1 1 1 1
-50 0 2 3 4 5
输出样例2:
1
问题描述:
因为是两个数列的并集(在这里不是严格意义上的集合,元素可以重复),所以元素个数为偶数,题目要求找并集元素排序后最中间两个数的较小者。
算法描述:
找最中间的数的要求与二分搜索有类似之处。二分搜索的递归通过修改搜索范围查找,在这里可以采用在两数列内不断修改搜索区间逼近中位数。
代码如下所示:
#include <iostream> using namespace std; int median(int *a, int *b,int l_a, int r_a, int l_b, int r_b) { if (a[r_a] < b[l_b]) return a[r_a]; if (b[r_b] < a[l_a]) return b[r_b]; //以上两行条件语句既判断两数列是否首尾相接 //也充当递归终止条件:若不首尾相接,直到搜索区间剩余一个数时才会出现 a[r_a] < b[l_b] 或 b[r_b] < a[l_a] int mid_a = (l_a + r_a)/2; int mid_b = (l_b + r_b)/2; if (a[mid_a] == b[mid_b]) return a[mid_a]; else if (a[mid_a] < b[mid_b]) { if ((r_a - l_a) % 2) mid_a++; //接下来要查找右半段的当前区间有偶数个元素时,若左指针需修改要右移一位 median(a, b, mid_a, r_a, l_b, mid_b); } else { if ((r_b - l_b) % 2) mid_b++; median(a, b, l_a, mid_a, mid_b, r_b); } } int main() { int n; cin >> n; int a[100000], b[100000]; for (int i = 0; i < n; i++) cin >> a[i]; for (int i = 0; i < n; i++) cin >> b[i]; cout << median(a, b, 0, n - 1, 0, n - 1); return 0; }
算法时间与空间复杂度分析:
时间复杂度:二分每次把搜索区间折半,时间复杂度是O(log n)。
空间复杂度:每次调用median()函数只新建了两个int型变量,空间复杂度是O(1)。
心得体会:
这道题应用二分法确实比创建新数组进行两个数组合并排序更佳,以后遇上类似的与有序数列的中间数相关的问题优先考虑二分法。我看到这道题先想到的是先合并排序,这是以前学的求中位数的方法。但是学习算法就是为了改进原始的、笨拙的方法,结合新学的算法,更新原先的模式。再则,学习课堂上的算法优化并推广应用的同时,要是养成平时多思考的习惯就受益无穷了。如果说结伴编程有什么教训的话,一定是不能两个人一上来就商量。先各自思考、编程,遇到问题再讨论,这样或许更有效果。