In-place Merge 原地归并 【转贴】

标签:
算法原地归并排序归并排序 |
分类: 程序开发 |
C++ STL中的近百个算法,绝大多数都是非常简单,用10行程序足以说明。但是,inplace_merge这个算法,却是相当地复杂,伤脑啊。
如果有线性额外空间,合并仅需要O(n)时间;如果是堆排序,原地执行,时间复杂度是O(n log n)。
=============================
In-place Merge Sort (原地归并排序)
转贴自:
在进入具体的细节之前,先来看一个稍后会用到的引论
给定长度分别为n和m的两个各自有序数组X,Y,以及一个长度为n的辅助数组A,存在O(m+n)时间复杂度的算法AuxSort;该算法可以原地归并排序X和Y,同时A中元素保持不变(顺序可能打乱)。
看到留言说用了辅助数组A,空间复杂度就是O(n)了。所以先提前在这解释下:AuxSort算法中确实用到了长度为n的辅助数组A。但是在最终的算法 中,AuxSort会作为subroutine,而我们会将待排序数组的一部分作为这个辅助数组A,而不是另外开辟一块空间。所以最终的空间复杂度仍然是 O(1)。所以请大家耐心往下看。。。
AuxSort算法具体步骤如下:
-
交换X
和 A。 -
维护两个整数下标
px, py,初始化为0,分别代表A[0]和Y[0]; 另外维护一个dest指针,初始化指向X[0],并且该指针在移动到X[n-1]后,自动过渡到Y[0]。 -
比较A[px],
Y[py]:
若A[px] <= Y[py], swap(A[px++], *dest++);
若A[px] > Y[py],swap(Y[py++], *dest++); -
若A中仍有剩余元素,则将剩余部分依次与*dest交换。当然每一步要执行dest++操作。【Y中有剩余元素不必再操作了,此时已经保持有序】
通
过上述步骤很容易发现,该算法的基本思想与传统的2-way
为了有助于理解,我们来看一个例子。假设n=3,且x1
http://s9/mw690/001x63y2gy6FyoVQQTm28&690Merge
图1
下面开始本文的重点,in-place
Knuth, Donald (1998). "Section 5.2.4: Sorting by
Merging". Sorting and Searching. The Art of Computer
Programming 3 (2nd ed.). Addison-Wesley.
pp.
该算法出自
Kronrod, M.A. An optimal ordering algorithm without a field of
operation (in Russian), Dok. Aknd. Nauk SSSR 186 (1969),
1256-1258.
如果感觉抓不住一篇论文的精髓,那么看看后来的引用文献对这篇被引论文的总结:block
rearrangement、internal buffering、employ O(sqrt(n)) blocks each of
size O(sqrt(n)). this approach allows the user to
employ one block as an internal buffer to aid in rearranging or
otherwise manipulating the other blocks in constant extra space.
Since only the contents of the buffer and the relative order of the
blocks need ever be out of sequence, linear time is sufficient to
sort both the buffer and the blocks (each sort involves O(sqrt(n))
keys) and thereby complete the merge.
这帮人真是神人。。。废话不说了,直接进入到正题吧。
假设我们有两个已经各自有序的数组X和Y,其总长度为N,如图2所示。下面将要介绍的算法将利用O(1)的额外空间,在O(N)的时间内将其原地排序。总体来说算法分为3部分:分块,两两归并,扫尾。
http://s10/mw690/001x63y2gy6Fyp06gtP29&690Merge
图2
分块:我们将原始数组组合在一起,并以长度n=sqrt(N)将其分成m+2份:
http://s8/mw690/001x63y2gy6Fyp4UZBt67&690Merge
图3
另
外,如图3所示,假设X[-1]
两两归并:
根据每一块中的第一个元素大小调整Z1,
如 此排列好之后,我们便可以从Z1和Z2开始进行两两归并排序。由于A的长度s>=n,这里我们可以采用前文介绍的AuxSort方法。唯一的缺点是 A中的元素顺序会被打乱,但由于A中元素本来也就无序,所以也就无所谓了。也就是说,我们要执行m-1轮AuxSort算法,其中第i轮处理Zi和 Zi+1。通过下面的归纳法证明,我们可以得知,这m-1轮AuxSort过程执行完之后,Z1~Zm中所有的元素就已经排序完毕了。
在此之前首先假设排序结束之后的第i块为Ri,而经过AuxSort处理后的Zi为Z’i。另外,为了描述方便,我们把Z1~Zm中属于X的第一块叫做X1,属于Y的第一块叫做Y1,依次类推。
我们接下来将要证明——在第i轮处理后,Ri
-
i=1。不失一般性,我们假设Z1为X1,那么Z1,
Z2, Z3 可能为{X1, X2, X3}, {X1, X2, Y1}, {X1, Y1, X2}, {X1, Y1, Y2}. 往证R1 = Z’1且Z’2[0] <= Z3[0] : 1.1 {X1,
X2, X3}。 则X1[n-1] <= X2[0] = Z2[0] <= Zi[0] <= Zi[k] (i>=2),也就是说,X1包含 最小的n个元素,R1 = X1=Z’1。另外,Z’2[0] = Z2[0] = X2[0] <= Zi[0] (i>=3)。 1.2 {X1,
X2, Y3}。同上,X1[n-1] <= Z2[0] <= Zi[k] (i>=2),因此R1=X1=Z’1。另外,Z’2[0]=Z2[0]<=Zi[0] (i>=3)。 1.3 {X1,
Y1, X2}。显然最小的n个元素一定在X1或Y1中,因此R1=Z’1。另外,Z’2[0] <= max(X1[n-1], Y1[0]) <= X2[0] = Z3[0]。 1.4 {X1,
Y1, Y2}。同理,最小的n个元素一定在X1或Y1中,R1=Z’1。另外,Z’2[0] <= Y1[n-1] <= Y2[0] = Z3[0] 因 此merge之后,
Z’2[0] <= Z3[0]的性质仍然成立。在随后的分析中,如果Z’2[n-1] = X1[n-1], 那么可以将 Z’2看成是X1; 同理如果Z’2[n-1] = Y1[n-1],我们可以将其看成是Y1。这是一个很重要的推论,直接支持我们在随后继续沿用上述四 种情况的分析。 -
假设i=k的时候结论成立,也就是说k轮AuxSort之后,R1~Rk已经就位。且根据上一步中的推论,Z’k+1可以直接看作是Xi或Yj。也就是说,上一步中四种分情况讨论的方法依旧适用。证毕;
-
综上所述,根据归纳法我们可以得知结论成立。
由于共经过m-1轮两两归并,每一轮归并时间复杂度为O(2n),因此第二步总体时间复杂度为O(mn)
扫尾:“两两归并”步结束之后,R[0...m*n-1]已经排序完毕。由于A中共有s个元素,容易看出,原始数组中最大的s个元素一定存在于A和R[m*n-s,
m*n-
1]中。因此我们可以利用选择排序,在O(s^2)
利
用AuxSort,
这样一来,通过分块,两两归并,以及扫尾三个步骤以后,我们完成了对于X和Y的一轮原地归并排序,时间复杂度为O(N),空间复杂度为O(1)。以此为基础,完整的归并排序可以在O(NlogN)时间内完成,空间复杂度仍然为O(1)。
注意由于AuxSort基于交换操作,A中元素的顺序会被打乱,因此该排序算法是不稳定的。
后记:
记
不得最开始听说这个概念是什么时候了,最近有印象的一次是去年去STC面试,面试官问到merge
后
来回去之后翻STL中的inplace_merge研究了一下,发现这个并不是纯正的in-place
另外,stable
========================
Bing-Chao Huang, Michael A. Langston: Practical In-Place Merging. Commun. ACM 31(3): 348-352 (1988)
全文:
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.1155&rep=rep1&type=pdf