原地归并排序所利用的核心思想便是“反转内存”的变体,即“交换两段相邻内存块”,对于反转内存的相关文章,曾在文章“关于反转字符串(Reverse Words)的思考及三种解法”中对一道面试题做了分析。这一思想用到的地方很多,在《编程珠玑》中被称为“手摇算法”。通过手摇算法的交换内存的思想来进行原地归并又有不少变种,我们举例分析一种比较常见的情况,不同的方法还有基于二分查找的方法来确定交换的内存块,在《计算机编程艺术》中也有不同的思路提供,感兴趣见本文参考资料。
下面举例说明一种原地归并排序的思想。
在了解原地归并的思想之前,先回忆一下一般的归并算法,先是将有序子序列分别放入临时数组,然后设置两个指针依次从两个子序列的开始寻找最小元素放入归并数组中;那么原地归并的思想亦是如此,就是归并时要保证指针之前的数字始终是两个子序列中最小的那些元素。文字叙述多了无用,见示例图解,一看就明白。
假设我们现在有两个有序子序列如图a,进行原地合并的图解示例如图b开始
如图b,首先第一个子序列的值与第二个子序列的第一个值20比较,如果序列一的值小于20,则指针i向后移,直到找到比20大的值,即指针i移动到30;经过b,我们知道指针i之前的值一定是两个子序列中最小的块。
如图c,先用一个临时指针记录j的位置,然后用第二个子序列的值与序列一i所指的值30比较,如果序列二的值小于30,则j后移,直到找到比30大的值,即j移动到55的下标;
如图d,经过图c的过程,我们知道数组块 [index, j) 中的值一定是全部都小于指针i所指的值30,即数组块 [index, j) 中的值全部小于数组块 [i, index) 中的值,为了满足原地归并的原则:始终保证指针i之前的元素为两个序列中最小的那些元素,即i之前为已经归并好的元素。我们交换这两块数组的内存块,交换后i移动相应的步数,这个“步数”实际就是该步归并好的数值个数,即数组块[index, j)的个数。从而得到图e如下:
重复上述的过程,如图f,相当于图b的过程,直到最后,这就是原地归并的一种实现思想,具体代码如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
1
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
/* 原地归并
* 可二路归并亦可与插入排序相结合,如代码
*/
//reverse array
void reverse(int arr[], int size)
{
int left = 0;
int right = size -1;
while(left < right)
{
int temp = arr[left];
arr[left++] = arr[right];
arr[right--] = temp;
}
}
// swap [arr,arr+headSize) and [arr+headSize,arr+headSize+endSize)
void SwapMemory(int arr[], int headSize, int endSize)
{
reverse(arr, headSize);
reverse(arr + headSize, endSize);
reverse(arr, headSize + endSize);
}
void Inplace_Merge(int arr[], int beg, int mid, int end)
{
int i = beg; // 指示有序串1
int j = mid + 1; // 指示有序串2
while(i < j && j <= end) //原地归并结束的条件。
{
while(i < j && arr[i] <= arr[j])
{
++i;
}
int index = j;
while(j <= end && arr[j] <= arr[i])
{
++j;
}
SwapMemory(&arr[i], index-i, j-index);//swap [i,index) and [index,j)
i += (j-index);
}
}
void Inplace_MergeSort(int arr[], int beg, int end)
{
if(beg < end)
{
int mid = (beg + end) / 2;
Inplace_MergeSort(arr, beg, mid);
Inplace_MergeSort(arr, mid+1, end);
Inplace_Merge(arr, beg, mid, end);
}
}
/* 简单测试用例 */
void main()
{
int arr[] = {3,5,1,7,0,6,9,11,8};
int temp_arr[] = {3,5,1,7,0,6,9,11,8};
/* 测试不同的归并算法 */
//MergeSort(arr,0,8,temp_arr);
//Insert_MergeSort(arr,0,8,temp_arr);
//Inplace_MergeSort(arr,0,8);
for(int i = 0; i < 9; ++i)
{
cout<<arr[i]<<" ";
}
cout<<endl;
}
|
转自