c语言实验题数列有序,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:

6

-100 -10 1 1 1 1

-50 0 2 3 4 5

输出样例2:

1

算法分析

看完题目第一反应是两个集合求并集,再排个序输出中间的数就好了。但是看到数据量10,0000个数,时间限制是200ms。快排时间复杂度是O(nlogn),一定是会超时的。

所以一定有一个更好的算法。

接下来留意到题目中的序列是非降序序列,想到取各自的中位数然后比较。通过比较缩小问题规模的办法。如果办法有效,算法的时间复杂度应该是O(logn),满足评分要求。

那么下面开始验证这个想法。

猜想与验证

基于数学进行猜想

我们首先取序列S1的中位数设为mida,再取序列S2的中位数设为midb。

由于序列S1、S2都是升序排列的。故S1mida左边的数都小于mida,右边的数都大于mida。序列S2同理。

此时比较mida和midb。

由于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},即两个数中更小的那个。

基于测试用例验证

我们来模拟一下这个过程。

bVbDcoF

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

bVbDcoG

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

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

bVbDcoY

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

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

递归上述操作,我们可以逐步迭代集合A。

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

bVbDcpH

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

bVbDcqx

由于{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}中的mida和midb。

正确的集合A如下。

bVbDcq8

最终筛选出中位数为4。

代码

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

#include

#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

scanf("%d", &a[i]);

}

for(int i=0;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

}

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);

}

运行情况如下。

bVbDcrm

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

小结

本次题目难度不大,主要锻炼了下写代码态度QAQ。毕竟好久没写代码了。对于边界条件的掌握还是有些生疏,希望能够更加严谨。

朋友们有什么问题的也欢迎跟我交流~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值