原地歸併排序

一般在提到Merge Sort時,大家都很自然地想到Divide-and-Conqure, O(n lgn)的時間複雜度以及額外的O(n)空間。O(n)的extra space似乎成了Merge Sort最明顯的缺點,但實際上這一點是完全可以克服的,也就是說,我們完全可以實現O(n lgn) time 以及 O(1) space 的Merge Sort。對於這種不用額外空間(即常數大小的額外空間)的算法,有一個通用的名字叫做In-place Algorithms,因此我們稱該歸併算法爲in-place merge sort,也就是原地歸併排序。

下面總結一下原地歸併排序特點:

空間:不需要輔助數組即可歸併,空間複雜度爲O(1)

時間:時間複雜度爲O(nlogn)


關鍵在於merge這個函數。兩段遞增的子數組arr[begin…mid-1]和arr[mid…end],i=begin,j=mid,k=end

image

 

i往後移動,找到第一個arr[i]>arr[j]的索引

image

j往後移動,再找第一個arr[j]>arr[i]的索引

image

然後我們將i到mid的部分和mid到j-1的部分對調,較小的部分就調到前面去了,然後從後面的部分與j到k的部分又是兩個遞增的子數組,繼續迭代即可。


a) 對調旋轉

旋轉又稱循環移動,假設有這樣一個序列:e0, e1, …, ei-1, ei, ei+1, …, en-1, en。現在我們需要把它向左循環移動i個位置變成:ei, ei+1, …, en-1, en, e0, e1, …, ei-1。爲了儘可能的節約內存和保證較快的速度,我們可以在時間複雜度O(n),空間複雜度O(1)的情況下達到目的。一種解決方案如下:

把原始序列看成兩個子序列:e0, e1, …, ei-1ei, ei+1, …, en-1, en

把這兩個子序列分別逆序得:ei-1, …, e1, e0en, en-1, …, ei+1, ei

也就是得到了這樣一個序列:ei-1, …, e1, e0, en, en-1, …, ei+1, ei

再把上面的序列整體逆序得ei, ei+1, …, en-1, en, e0, e1, …, ei-1

以上旋轉過程的時間複雜度爲O(n/2) + O(n/2) + O(n) = O(2n) = O(n),逆序時僅需要一個元素的輔助空間,空間複雜度O(1)

下面舉例說明一種原地歸併排序的思想。

    在瞭解原地歸併的思想之前,先回憶一下一般的歸併算法,先是將有序子序列分別放入臨時數組,然後設置兩個指針依次從兩個子序列的開始尋找最小元素放入歸併數組中;那麼原地歸併的思想亦是如此,就是歸併時要保證指針之前的數字始終是兩個子序列中最小的那些元素。文字敘述多了無用,見示例圖解,一看就明白。

假設我們現在有兩個有序子序列如圖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的過程,直到最後,這就是原地歸併的一種實現思想,具體代碼如下。

#include <iostream>
#include <algorithm>

using namespace std;

//將長度爲n的數組逆序
void reverse(int *A,int n)
{
	int i=0;
	int j=n-1;
	while (i<j)
	{
		swap(A[i],A[j]);
		i++;
		j--;
	}
}

//將數組向左循環移位i個位置
void exchange(int *A,int n,int i)
{
	reverse(A,i);
	reverse(A+i,n-i);
	reverse(A,n);
}

//數組兩個有序部分的歸併
void Merge(int *A,int begin,int mid,int end)
{
	int i=begin;
	int j=mid;
	int k=end;
	
	while (i<j&&j<=k)
	{
		int step=0;
		while (i<j&&A[i]<=A[j])
			i++;
		while (j<=k&&A[j]<A[i])
		{
			j++;
			step++;
		}
		exchange(A+i,j-i,j-i-step);
		i=i+step;
	}
}


void MergeSort(int *A,int l,int r)
{
	if(l<r)
	{
		int mid=(l+r)/2;
		MergeSort(A,l,mid);
		MergeSort(A,mid+1,r);
		Merge(A,l,mid+1,r);
	}
}

int main()
{

	int arr[]={0,1,5,6,9,2,3,4,7,4,1,8};
	int len=sizeof(arr)/sizeof(arr[0]);
	for (int i=0;i<len;i++)
	{
		cout<<arr[i]<<" ";
	}
	cout<<endl;

	
	MergeSort(arr,0,len-1);

	for (i=0;i<len;i++)
	{
		cout<<arr[i]<<" ";
	}
	cout<<endl;

	return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章