My Links

Blog Stats

算法第二章上机实践报告

实践题目:7-3 两个有序序列的中位数

题目描述:已知有两个等长的非降序序列S1, S2, 设计函数求S1与S2并集的中位数。有序序列A​0​​,A​1​​,⋯,A​N−1​​的中位数指A​(N−1)/2​​的值,即第⌊(N+1)/2⌋个数(A​0​​为第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)。

 

心得体会:

这道题应用二分法确实比创建新数组进行两个数组合并排序更佳,以后遇上类似的与有序数列的中间数相关的问题优先考虑二分法。我看到这道题先想到的是先合并排序,这是以前学的求中位数的方法。但是学习算法就是为了改进原始的、笨拙的方法,结合新学的算法,更新原先的模式。再则,学习课堂上的算法优化并推广应用的同时,要是养成平时多思考的习惯就受益无穷了。如果说结伴编程有什么教训的话,一定是不能两个人一上来就商量。先各自思考、编程,遇到问题再讨论,这样或许更有效果。

posted on 2019-09-23 00:39  Binet  阅读(...)  评论(...编辑  收藏