转自: http://wansishuang.appspot.com/?p=139001
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
BidirectionalIterator middle, BidirectionalIterator last );
template
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.
下面是Java仿SGI STL源码实现的版本:
1 class InPlaceStableSort extends AlgoTri {
3
4 int lower ( int from, int to, int val) {
5 int len = to - from, half;
6 while (len > 0) {
7 half = len/ 2;
8 int mid= from + half;
9 if (compare (mid, val) < 0) {
10 from = mid+ 1;
11 len = len - half - 1;
12 } else len = half;
13 }
14 return from;
15 }
16
17 int upper ( int from, int to, int val) {
18 int len = to - from, half;
19 while (len > 0) {
20 half = len/ 2;
21 int mid= from + half;
22 if (compare (val, mid) < 0)
23 len = half;
24 else {
25 from = mid+ 1;
26 len = len - half - 1;
27 }
28 }
29 return from;
30 }
31
32 void insert_sort ( int from, int to) {
33 delay (ext); // emulates recursion's & stack cost
34 if (to > from+ 1) {
35 for ( int i = from+ 1; i < to; i++) {
36 for ( int j = i; j > from; j--) {
37 if (compare(j, j- 1)< 0)
38 exchange(j, j- 1);
39 else break;
40 }
41 }
42 }
43 }
44
45 int gcd ( int m, int n) {
46 while (n!= 0) { int t = m % n; m=n; n=t; }
47 return m;
48 }
49
50 void reverse ( int from, int to) {
51 while ( from < to) {
52 exchange( from++, to--);
53 }
54 }
55
56 void rotate ( int from, int mid, int to) {
57 /* a less sophisticated but costlier version:
58 reverse(from, mid-1);
59 reverse(mid, to-1);
60 reverse(from, to-1);
61 */
62 if ( from==mid || mid==to) return;
63 int n = gcd(to - from, mid - from);
64 while (n-- != 0) {
65 int val = item( from+n);
66 int shift = mid - from;
67 int p1 = from+n, p2= from+n+shift;
68 while (p2 != from + n) {
69 assign(p1, item(p2));
70 p1=p2;
71 if ( to - p2 > shift) p2 += shift;
72 else p2= from + (shift - (to - p2));
73 }
74 assign(p1, val);
75 }
76 }
77
78 void merge( int from, int pivot, int to, int len1, int len2) {
79 delay(ext);
80 if (len1 == 0 || len2== 0) return;
81 if (len1+len2 == 2) {
82 if (compare(pivot, from) < 0)
83 exchange(pivot, from);
84 return;
85 }
86 int first_cut, second_cut;
87 int len11, len22;
88 if (len1 > len2) {
89 len11=len1/ 2;
90 first_cut = from + len11;
91 second_cut = lower(pivot, to, first_cut);
92 len22 = second_cut - pivot;
93 } else {
94 len22 = len2/ 2;
95 second_cut = pivot + len22;
96 first_cut = upper( from, pivot, second_cut);
97 len11=first_cut - from;
98 }
99 rotate(first_cut, pivot, second_cut);
100 int new_mid=first_cut+len22;
101 merge( from, first_cut, new_mid, len11, len22);
102 merge(new_mid, second_cut, to, len1 - len11, len2 - len22);
103 }
104
105 void sort( int from, int to) {
106 delay (ext); // emulates recursion's & stack cost
107 if (to - from < 12) {
108 insert_sort ( from, to);
109 return;
110 }
111 int middle = ( from + to)/ 2;
112 sort ( from, middle);
113 sort (middle, to);
114 merge( from, middle, to, middle- from, to - middle);
115 }
116
117 public void sort () {
118 sort( 0, size);
119 }
120 };
假设二个序列分别为[K1 , KM ),[KM+1, KN):解法3:在计算机程序设计艺术 第三卷中5.24的习题18 给出了答案,现在解释如下,
K1 <= K2 <= K3 <=...<=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)