PTA_数据结构学习与实验指导_题解_1-3.1两个有序序列的中位数

进阶实验1-3.1 两个有序序列的中位数

 已知有两个等长的非降序序列S1, S2, 设计函数求S1与S2并集的中位数。有序序列A​0​​,A​1​​,⋯,A​N−1​​的中位数指A​(N−1)/2​​的值,即第⌊(N+1)/2⌋个数(A​0​​为第1个数)。算法

输入样例1:数组

5
1 3 5 7 9
2 3 4 5 6

输出样例1:函数

4

输入样例2:spa

6   
-100  -10  1  1  1  1  
-50  0  2  3  4  5

输出样例2:设计

1

题目:进阶实验1-3.1 两个有序序列的中位数 (25分)3d

算法分析

 看完题目第一反应是两个集合求并集,再排个序输出中间的数就行了。可是看到数据量10,0000个数,时间限制是200ms。快排时间复杂度是O(nlogn),必定是会超时的。
 因此必定有一个更好的算法。
 接下来留意到题目中的序列是非降序序列,想到取各自的中位数而后比较。经过比较缩小问题规模的办法。若是办法有效,算法的时间复杂度应该是O(logn),知足评分要求。
 那么下面开始验证这个想法。code

算法验证

思想
 咱们首先取序列S1的中位数设为mida,再取序列S2的中位数设为midb
 因为序列S一、S2都是升序排列的。故S1mida左边的数都小于mida,右边的数都大于mida。序列S2同理。
 此时比较midamidb
 因为mida是S1的中位数,midb是S2的中位数。故集合U=S1∪S2中,大于MAX{mida,midb}的全部数都不多是中位数。同理可得,集合中小于MIN{mida,midb}的全部数也都不多是中位数。
 经过比较mida和midb的大小,咱们把集合U划分红了两个区间.
 即A=[MIN{mida,midb}的右区间, MAX{mida,midb}的左区间]∁UA
 此时问题就被简化成了求集合A的中位数。
 然后经过不断的二分查找,A最后必定会变成一个只有2个数的集合。那么根据中位数的定义,此时中位数必然是min{A},即两个数中更小的那个。blog


 咱们来模拟一下这个过程。排序

image.png

 这是序列S1,此时mida=5。递归

image.png

 这是序列S2,此时midb=4。

 因为mida > midb,故此时U=S1∪S2被分红了两个集合,A={1,3,5}∪{4,5,6}(蓝色)及∁UA(白色)。

image.png

中位数必然在集合A中。由于中位数是排序后位于数列中间,因此它应该在两个升序子序列的中位数的中间。

 此时问题就变成了在集合A中取得中位数。白色的∁UA能够直接抛弃。

 递归上述操做,咱们能够逐步迭代集合A。

直到这一步,咱们会遇到一个问题,也是笔者遇到的一个大坑。

image.png

 此时两个序列中的数字个数都为偶数数,中位数为俩数中小的那个也就是前面那个。若继续按这种方式迭代,接下来的集合会变成这个。

image.png

因为{3,5}中,中位数为3,小于4,那么接下来应该取它右边的序列。此时会发现此序列取右边的序列仍是{3,5}!它会形成无限递归或者死循环!

 因此分析到这一步咱们发现,应该是要分辨集合中数字的个数为奇数仍是偶数来分别取子序列。最终咱们发现,除了0之外,天然数中最小的偶数是2。在序列长度为2且升序的状况下,中位数直接就是前面那个。
 把它扩展到4,那么咱们发现只要抛掉首位两个数,状况就退化成了上述状况。即又一次迭代了集合A。换做到题目中,即直接抛掉偶数序列中的边界数便可。即mida或midb(两个子序列都是偶数则兼有之)。

因此{4,5,6,1,3,5}以后的迭代出的{4,5,3,5}是不正确的!

 正确迭代方式应该抛掉{4,5,6,1,3,5}中的midamidb

 正确的集合A以下。

image.png

 最终筛选出中位数为4。

代码

下面给出笔者的代码。因为最近在复习C语言,写的是尾递归的版本。

#include <stdio.h>

#define MAX_N 100000

/* 二分查找函数声明 aleft a数组左下标,aright a数组右下标*/ 
int bin_search(int a[], int aleft, int aright, int b[], int bleft, int bright); 

int main()
{
    int n = 0, a[MAX_N] = {0}, b[MAX_N] = {0};
    scanf("%d", &n);
    for(int i = 0;i<n;i++){
        scanf("%d", &a[i]);
    }
    for(int i=0;i<n;i++){
        scanf("%d", &b[i]);
    }
    int mid = bin_search(a, 0, n-1, b, 0, n-1);
    printf("%d\n", mid);
    return 0;
}

int bin_search(int a[], int aleft, int aright, int b[], int bleft, int bright){
    int al=0, ar=0, bl=0, br=0; /* 下一步递归的a,b数组下标 */ 
    /* indexa a数组中位数下标 mida a数组中位数的值*/ 
    int indexa = (aleft+aright)/2, indexb = (bleft+bright)/2, mida = a[indexa], midb = b[indexb];
    /* 若是俩数组中位数相等 则必是解 */
    if(mida == midb){ 
        return mida;
    }
    /*若是待查找区间内只有一个数,则小的那个为解*/
    if(aleft >= aright && bleft >= bright){  
        return mida<midb?mida:midb;
    }
    if(mida > midb){
        bl = indexb; /* 小的取右区间 */
        br = bright;
        ar = indexa; /* 大的取左区间 */ 
        al = aleft;    
        if( (aright-aleft+1) % 2 == 0){  /*偶数个数缩小范围时抛掉当前中位数*/
            bl = indexb+1;
        }
    }else if(mida < midb){
        al=indexa;
        ar = aright;
        bl=bleft;
        br = indexb;
        if((bright-bleft+1) % 2 == 0){
            al=indexa+1;
        }
    }
    return bin_search(a, al, ar, b, bl, br);
}

 运行状况以下。

image.png

 看了下最快耗时是25ms左右。并无数量级上的差距。若是把递归改为循环,缓冲输入改为快速输入应该能有差很少的时间耗时,说明此算法应该是目前为止最快的了。

小结

 本次题目难度不大,主要锻炼了下写代码态度QAQ。毕竟很久没写代码了。对于边界条件的掌握仍是有些生疏,但愿可以更加严谨。 朋友们有什么问题的也欢迎跟我交流~

相关文章
相关标签/搜索