一次求两序列中位数分治算法探索历程

如今博主在上算法设计与分析这门课,前两天刚讲到了分治法。
下面照搬一下教材《算法设计与分析》(第2版,王红梅、胡明 编著)分治法的设计思想概述。

分治者,分而治之也。


分治法(divide and conquer method)将一个难以直接解决的发问题划分成一些规模较小的子问题,分别求解各个子问题,再合并子问题的解得到原问题的解。一般来说,分治法的求解过程由以下三个阶段组成:

  1. 划分:把规模为n的原问题划分为k个(通常k=2)规模较小的子问题。
  2. 求解子问题:各子问题的解法与原问题的解法通常是相同的,可以用递归的方法求解各个子问题,有时递归处理也可以用循环来实现。
  3. 合并:把各个子问题的解合并起来,合并的代价因情况不同有很大差异,分支算法的效率很大程度上依赖于合并的实现。

也就是说,分治法是把一个大问题划分为若干个子问题,分别求解各个子问题,然后再把子问题的解进行合并得到原问题的解。而与之类似的减治法同样是把一个大问题划分为若干个子问题,但是这些子问题不需要分别求解,只需求解其中的一个子问题,因而也无需对子问题的解进行合并。所以,严格地说,减治法应该是一种退化了的分治法。
而在求两个序列的中位数的算法设计中,其实更严格来说是减治法的思想。
课本详细问题描述此处不予展示,算法基本思想此处也不列举。总之,课本上已知条件是两个等长的升序序列A和B。
而关键一点是当序列A和序列B均只有一个元素时,返回较小者。而博主在网上搜索到一些文章则是返回两个元素的算数平均数,这里有什么不同呢?
我想,这里可就真有点区别了!
首先,按照课本上的意思,一个序列的中位数的值有两种情况:当序列个数的奇数时(如2n+1),那么此时中位数的值就是序号为n+1的那个。若序列个数为偶数也就是2n时,此时序列的中位数是序号为n的那个元素。
按照课本上的意思的话,算法自然是没有问题的。然而,却不知网上的一些最后返回两个元素的算数平均数的文章中关于中位数的定义是什么呢。
单纯从数学的角度上来看的话,似乎确实是当序列为偶数时求取两个数的平均值。
然而在算法设计求解的过程中,只是单纯满足当减治到两个序列都只有一个元素时才应用这样的定义是否可行呢?
拓展来说,就算当序列为偶数时,用求中间两个数的平均值作为中位数作为两个序列的中位数的比较的值就一定可以了吗?
比如两个序列A{1,9}、B{2,4},用以上的算法求得的中位数是多少?
课本上的话,由于定义为较小值的缘故,结果为2是没有问题的,然而若是网上的那些算法那么求取的平均值将为(1+4)/2= 2.5,对没错,是2.5,那么这2.5是否是序列C{1,2,4,9}的中位数呢?很明显不是的。
那么这问题出在了哪里?
问题就出在了如果中位数不一定为两个序列中的已有值而是有可能为两个数的平均值。
分治的算法很简单,依次减半,A和B各取一半,最后的结果也是A和B各出一个(当都为一个元素时),然而此时序列C的中位数可能是由同一个序列的两个相邻值计算得来的,如B中的(2+4)/2 = 3,这才是这个序列的平均值,而很明显,以上的算法并没有考虑到这个。
这就是,既想用分治简单思想,又想求严格中位数的结果,颇有四不像的味道。


此时再去想的话,由于减治法总是能将中位数求解减少到两个序列都只剩一个或两个的情况,所以当A{a,b}、B{c,d}的时候,a,b,c,d之间的大小关系实际上只有两种。a<=b、c<=d的大小关系总是确定的。将值域分为(1)< c,(2)c<=&&<=d,(3)> d三种,那么ab两个可能都在1,都在2,都在3,分别在12,13,23。
当都为1或3或者为12、23的时候,符合之前分治算法的求解条件,当都为2或者分居13的时候,很明显中位数自然是(a+b)/2或者(c+d)/2了,而这其实也只是之前分治算法的拓展而已。
显然,两个个数为2的序列的比较情况可以推广至3及以上的了。
那么问题是:如何进行归并呢?
经简单分析可知,同样的对A和B序列从中间刨开分割为两个部分,对于个数为奇数(2n+1)的序列,两部分各包含序列的中位数【n+1】,对于偶数(2n),两部分各包含【n】及【n+1】。举例而言如A为[1,2,3],则A被分片为[1,2]和[2,3],两个分片的代表分别为2和2,若A为[1,2,3,4],则两个分组为[1,2]和[3,4],分片的代表分别为2和3。
然后A组和B组的两个小端两两猜拳,大的获胜,平的进A和B都行,大端也是如此,只不过较小的获胜。取两个获胜的分片接着递归直到求出结果。(注意:这种情况下,取到的应当是两个分属不同的分组,否则的话,直接就能输出结果了不是吗)这种情况下最好的情况是O(1),如A的分片代表分居B的分片代表两侧。(这里请读者自己体会)
再拓展开来,如果两个数组不等长呢?
博主之前还是这样一种方式,不过显然出问题了,问题在哪呢?
发现有的提前就成了1个元素了,而这时相应的判断标准要做出变化,这时如果序列个数为奇(2n+1),则取n-1,n,n+1三个值,都有用。如果序列为偶(2n),则取n,n+1两个数即可,此时用那一个元素与这几个元素比较确定之间的大小关系,从而求得中位数。
OK!
然后编写了随机生成数组测试程序,发现结果不对!!!
卧槽,什么鬼!
发现总是取值偏了一些,做了调试之后发现了最终的问题所在,同时也是减治法解决中位数求解的核心所在:减治法之所以能够逐渐缩小取值范围不将待求值遗漏出去的关键所在是每次都能左边抛去一定数量的保证比中位数小(反正是不大于)的元素,而右边抛去一定数量的保证比中位数大(反正是不小于)的元素,最关键的在于两边的数量是一致的,而在两序列大小不等的情况下两边去掉的元素个数极有可能不等,这样就会使中位数的取值范围发生偏移,从而取到错误的结果!所以,解决之道就是两个序列一个去大端一个去小端相同的数量就可以,而这数量取决于个数少的那个序列,直到为1或分居两旁为止等。
下面放上三次实验的代码,用aardio写的,博主非常喜欢的一门语言。
当然,没了解过也没关系,很容易看得懂。和JavaScript有点像的。


版本1:课本较小值版

import console;

console.setTitle("算法实验书中版本");

//返回子数组
//para: tab:数组;flag:标志
//flag 为1,返回后半部分数组,否则返回前半部分数组
 var subArray = function(tab,flag){
   
    var temp = {};
    var length = table.len(tab);
    if(flag == 1){
        for(i=math.floor(length/2)+1;length;1
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值