有序区间的归并排序,百度笔试题题解,为墙内同学谋福利

      蛋疼的百度笔试题倒数第二题,要达到时间复杂度O(n),空间复杂度O(1),引用一位博友的话“某个追求时间和空间极限的bt程序员某个时间脑袋灵光一闪,搞出来这些个所谓的nb算法,然后再自豪地把它们呈给那些技术面试官,让他们用这些来测测我们这些普通程序员的脑袋是否灵光。然而,网络的发达或许让他们的这一想法完全落空,本来就是些高中生都能看懂的算法(似乎我没有夸张),加上网络一传播,地球人都知道了。所以到最后,也只能考考那些从未看过类似我这篇Blog的人:)。所以,我自己倒是更愿意把这个归为一道智力测试题。国外已经有很多专家在质疑智力测试的准确性,在知识能够迅速获取的今天,很多智力题目只要你做过一次,第二次就不再是智力测试,而是记忆力测试了。所以很奇怪为什么现在面试官对智力测试有一种不可思议地喜欢,我们姑且称之为"智力测试喜好症候群"。“

  虽然这道题感觉用作笔试题毫无意义,但搜来搜去,网上还是有人给出了解法,不过需要FQ,本人特意整理下来以供大家参考学习。

inplace-merge(应用于有序区间):

设子数组a[0:k]和 a[k+1:n-1]已排序好(0 <=k <=n-1).试设计一个合并这两个子数组为排序的数组a[0:n-1]的算法.

STL的inplace_merge的描述: 如果两个连在一起的序列[first, middle)和[middle, last)都已经排序,那么inplace_merge可将它们组合成一个单一的序列,并保持有序性。

这个问题有三个解法:

解法1: 最简单,需要0(n)的辅助空间、0(n)的时间复杂度。[SGI STL版本当有可用的缓冲区采用的算法]

解法2:  稍微复杂,O(1)的辅助空间、O(nlogn)的时间复杂度。[SGI STL版本当没有可用的缓冲区采用的算法]

解法3: 最复杂,需要0(1)的辅助空间和0(n)的时间复杂度。[Doklady Akad. Nauk SSSR 186   A969), 1256- 1258]


下面来说明三种不同的解法:

其中STL的inplace_merge原型为:

template <class BidirectionalIterator><br>void inplace_merge ( BidirectionalIterator first,BidirectionalIterator middle, BidirectionalIterator last );
template <class BidirectionalIterator, class Compare> void inplace_merge ( BidirectionalIterator first,BidirectionalIterator middle,BidirectionalIterator last, Compare comp );

解法1:将待归并的二个序列中较短的序列copy到缓冲区中,然后再对二个序列进行简单的归并,将结果放入原始序列中即可。

解法2:是递归的思想, 分别记二个序列为A, B,然后通过将A的最后n个和B的前m个交换得到A', B', 使得A'中任何一元素均小于B'中元素,且A'和B'均满足前后分别有序。然后对A'和B‘递归处理即可。

例如: 序列           1   3   5   7   | 2    4   6   8   10

交换7 和 2 4 得到   1   3   5  2    4  |  7   6   8   10  

使得前半部分均小于后半部分,且前半部分和后半部分均部分(1 3 5)(2 4)|(7)(6 8 10)有序, 接着递归的解1 3 5 2 4 和 7 6 8 10 二个子序列即可。

其中注意交换7 2 4 为 2  4 7 其实即为向量旋转问题

时间复杂度为T(n) = 2 T(n/2) + O(n) 得T(n) = n*logn.

解法3:在计算机程序设计艺术 第三卷中5.24的习题18 给出了答案,现在解释如下,

假设二个序列分别为[K, K),[KM+1, KN):

K<= K<= K<=...<=KM , KM+1<= KM+1<= KM+2<=…<=KN

step1: 记n = sqrt(N), 将整个序列分为m+2份,分别记为Z1 Z2 Z3.. Zm Zm+1 Zm+2, 其中Z1到Zm+1每份均包含n个元素, 如果M % n == 0 则Zm+2的大小也为n,否则为N % n份, 把Zm+1Zm+2即为区域A,则A的大小为n<=s<2n,

例如N=13,数组为 [1 3 5 7 9 11 13 15 2 4 6 8 10]时:

则n = sqrt(13) = 3,m = 3, 即把序列分成m+2 = 5份,分别为

Z1: [1 3 5]

Z2: [7 9 11]

Z3: [13 15 2]

Z4: [4 6 8]

Z5: [10]

step2: 将包含KM的块与Zm+1交换。

则上面例子变成:

Z1: [1 3 5]

Z2: [7 9 11]

Z3: [4 6 8]

Z4: [13 15 2]  (m+1)

Z5: [10]  

step3: 可以看到前m个区域是分别有序的,然后将这m个区域按照首元素排序。排序可以用普通的选择排序,每次选择最小的首元素的区域与Z1交换,然后选择次小的元素与Z2交换,直到所有区域全部按首元素排序(m个),而每次交换需要交换n个元素,故这步操作时间复杂度为O(m(m+n)) = O(N)。

Z1: [1 3 5]

Z2: [4 6 8]

Z3: [7 9 11]

Z4: [13 15 2]  (m+1)

Z5: [10]  

经过step3后,前m个区域中相邻的三个区域最少有个二个区域来自原始序列中的同一个有序部分。例如Z1和Z3来自原序列的前一个有序部分[1 3 5 7 9 11 13 15]。这样相邻的三个区域中,最小的n个元素必然在开始的二个序列中。(这个地方好好思考!)

step4:对Z1和Z2进行归并排序,借助Zm+1作为辅助空间: 首先将Z1和Zm+1交换,然后对Z2和Zm+1进行归并将结果放入Z1开始区域。归并完成后,Z1和Z2整体有序,整个序列最小的元素必在Z1中,而Zm+1区域的值不变。

例如:          1  3  5    4  6  8  13  15  2

Z1和Zm交换   13 15 2    4  6  8   1    3   5

1 < 4           1  15 2     4  6  8   13  3   5

3 < 4           1  3  2      4  6  8   13  15  5

4 < 5           1  3  4      2  6  8   13  15  5

5 < 6           1  3  4      5  6  8   13  15  2

个…知道完成,可见Z1和Z2有序,而Zm+1区域的值不变,这步时间复杂度为O(2n)

step5: 重复step4知道前m个区域均有序。

step4和step5总的复杂度为O(m*2n)= O(mn) = O(N),此时Z1~ZM均为有序的

step6:  对KN+1-2s .. KN(其中s是区域A的大小)用插入排序,时间复杂度为0(s^2) = O(N) (n<=s<2n)

step6完成后保证 K1..KN-2s  和 KN-2s+1...KN 两个序列有序 ,且A区域中为序列的最大s个值, 接下来就要归并这两个区域,

step7: 现利用末尾的s个数KN-s+1到KN 亦即区域A作为缓冲区,以s作为块大小从后向前类似step 4归并KN-s~K1,结果是K1~KN-s有序(即A区域之前的都有序),而结尾用作为辅助空间A区域中是乱序,但是是整个序列中最大的s个数。此步时间复杂度为O(N/s*2s)=O(N) 
step8: 对A区域即末尾的s个数进行排序。此步时间复杂度O(s*s)=O(N)

故总的时间复杂度为0(N) 空间复杂度为O(1)

 转载至http://wansishuang.appspot.com/?p=139001(需FQ)

转载于:https://www.cnblogs.com/god-like/archive/2012/05/10/2494143.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值