原地归并排序

原地归并排序所利用的核心思想便是“反转内存”的变体,即“交换两段相邻内存块”,对于反转内存的相关文章,曾在文章“关于反转字符串(Reverse Words)的思考及三种解法”中对一道面试题做了分析。这一思想用到的地方很多,在《编程珠玑》中被称为“手摇算法”。通过手摇算法的交换内存的思想来进行原地归并又有不少变种,我们举例分析一种比较常见的情况,不同的方法还有基于二分查找的方法来确定交换的内存块,在《计算机编程艺术》中也有不同的思路提供,感兴趣见本文参考资料。

下面举例说明一种原地归并排序的思想。

    在了解原地归并的思想之前,先回忆一下一般的归并算法,先是将有序子序列分别放入临时数组,然后设置两个指针依次从两个子序列的开始寻找最小元素放入归并数组中;那么原地归并的思想亦是如此,就是归并时要保证指针之前的数字始终是两个子序列中最小的那些元素。文字叙述多了无用,见示例图解,一看就明白。

假设我们现在有两个有序子序列如图a,进行原地合并的图解示例如图b开始

原地归并排序_第1张图片

    如图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如下:

原地归并排序_第2张图片


重复上述的过程,如图f,相当于图b的过程,直到最后,这就是原地归并的一种实现思想

上面是http://blog.163.com/zhaohai_1988/blog/static/2095100852012721113044469/分享的,非常感谢

利用上一篇的交换程序可得

#include <iostream>
using namespace std;
template <typename T>
void swap(T a[],int first,int second){
	T temp;
	temp = a[first];
	a[first] = a[second];
	a[second] = temp;
}
template <typename T>
void reverse(T a[],int begin,int end){
	while(begin < end){
		swap(a,begin++,end--);
	}
}
template <typename T>
void exchange(T a[],int begin,int mid,int end){
	reverse(a,begin,mid);
	reverse(a,mid+1,end);
	reverse(a,begin,end);
}
template <typename T>
void merge(T a[],int begin,int mid,int end){
	int i = begin;
	int j = mid + 1;
	while( i < j && j <= end){
		while(i < j && a[i] <= a[j]){
		i++;
	}
	int old_j = j;
	while(j <= end && a[j] < a[i]){
		j++;
	}
	exchange(a,i,old_j-1,j-1);
	i += (j - old_j);
	}
	
}
template <typename T>
void merge_sort(T a[],int begin,int end){
	if(begin < end){
		int mid = begin + (end - begin)/2;
		merge_sort(a,begin,mid);
		merge_sort(a,mid+1,end);
		merge(a,begin,mid,end);
	}
}
int main() {
	int a[] = {2,3,1,55,6,4,7,3};
	int len = sizeof(a)/sizeof(int);
	int mid = 3;
    merge_sort(a, 0, len-1);
    int i;
    for (i = 0; i < len; ++i)
        cout << a[i] << " ";
    cout << endl;
}

上面应该算是自顶向下的归并算法,自底向上的归并算法:

#include <iostream>
using namespace std;
template <typename T>
void swap(T a[],int first,int second){
	T temp;
	temp = a[first];
	a[first] = a[second];
	a[second] = temp;
}
template <typename T>
void reverse(T a[],int begin,int end){
	while(begin < end){
		swap(a,begin++,end--);
	}
}
template <typename T>
void exchange(T a[],int begin,int mid,int end){
	reverse(a,begin,mid);
	reverse(a,mid+1,end);
	reverse(a,begin,end);
}
template <typename T>
void merge(T a[],int begin,int mid,int end){
	int i = begin;
	int j = mid + 1;
	while( i < j && j <= end){
		while(i < j && a[i] <= a[j]){
		i++;
	}
	int old_j = j;
	while(j <= end && a[j] < a[i]){
		j++;
	}
	exchange(a,i,old_j-1,j-1);
	i += (j - old_j);
	}
	
}
template <typename T>
void merge_sort(T a[],int begin,int end){
	if(begin < end){
		int mid = begin + (end - begin)/2;
		merge_sort(a,begin,mid);
		merge_sort(a,mid+1,end);
		merge(a,begin,mid,end);
	}
}
int main() {
	int a[] = {2,3,1,55,6,4,7,3,0};
	int len = sizeof(a)/sizeof(int);
	int step = 1;
	int i = 0;
	for(step = 1;step < len ; step*=2){
		for(i = 0;i+2*step-1 < len; i = i+2*step){
			merge_sort(a, i, i+2*step-1);
		}
		if(i < len){
			merge_sort(a,i,len-1);
		}
	}
    for (i = 0; i < len; ++i)
        cout << a[i] << " ";
    cout << endl;
}


参考: http://zju3053027087.blog.163.com/blog/static/13064377720124732052698/

http://www.cnblogs.com/daniagger/archive/2012/07/25/2608373.html

http://blog.csdn.net/zheda3072211120/article/details/6427532

http://blog.163.com/zhaohai_1988/blog/static/2095100852012721113044469/

http://www.seenthewind.cn/?p=1105

http://blog.csdn.net/cjf_iceking/article/details/7920153

你可能感兴趣的:(原地归并排序)