1. 程式人生 > >歸併排序:二路歸併

歸併排序:二路歸併

歸併排序(Merge Sort)是將兩個(或兩個以上)有序表合併成一個新的有序表,即把待排序序列分為若干個有序的子序列,再把有序的子序列合併為整體有序序列。

歸併排序的具體做法:

  1. 把原序列不斷地遞等分,直至每等份只有一個元素,此時每等份都是有序的。
  2. 相鄰等份合,不斷合併,直至合併完全。

二路歸併

歸併排序是建立在歸併操作上的一種有效的排序演算法。該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用。歸併排序最常用的是二路歸併,即把兩個小的有序的序列和併成一個大的有序序列:合二為一。

一個二路歸併的流程圖是這樣的:

多路歸併無非是多個有序的小序列合併成一個大的有序序列,道理和二路歸併一樣。

先來看下如何把兩個有序的序列合併成一個大的有序序列,程式碼如下:

/*
 *把有序序列a和b,合併成c 
 *該演算法成立前提: a和b已經有序  
 */ 
void merge(int a[], int na, int b[], int nb, int c[])
{
	if(a && b && c && na >0 && nb >0)
	{
		int i,j,k;
		i = j = k = 0;
		//不斷地比較a和b的頭部元素,較小的存入c 
		while(i < na && j < nb)
		{
			if(a[i] <= b[j]) // <= 保持演算法的穩定性
				c[k++] = a[i++];
			else
				c[k++] = b[j++];
			/*另一種更有效的做法是這樣的 
			while(i < na && a[i] <= b[j])
				c[k++] = a[i++];
			while(j < nb && b[j] < a[i])
				c[k++] = b[j++];
			*/
		}
		//把a或b中剩餘的元素直接存入c 
		/*  也可以這樣:
	     *  memcpy(c+k, a+i, (na-i)sizeof(int));
	     * 下同
	     */
		while(i < na)
			c[k++] = a[i++];
		while(j < nb)
			c[k++] = b[j++];
	}
}

可以看出,二路歸併的時間複雜度是O(n),n是原序列的資料規模。以上程式碼是歸併排序的基礎,弄懂了它,就很好寫歸併排序了,看下歸併排序的流程圖:


可以看出,上半部分不斷地遞迴深入:不斷地均分原序列,直到每一部分只含有一個元素。下半部分,開始遞迴返回,通過反覆呼叫二路歸併演算法,把相鄰的有序子序列合併成一個規模更大的序列。

理解了這些,相信就很容易寫出歸併排序的程式碼了:

//把[first, mid]和[mid+1, last]範圍內的資料合併  
void mergeArray(int a[], int b[], int first, int mid, int last)
{
	int i, j, k;
	i = first, j = mid + 1, k = 0;
	while (i <= mid && j <= last)
	{
		while(i <= mid && a[i] <= a[j])
			b[k++] = a[i++];
		while(j <= last && a[j] < a[i])
			b[k++] = a[j++];	
	}
	/*  也可以這樣:
	 *  memcpy(b+k, a+i, (mid-i+1)sizeof(int));
	 * 下同
	 */
	while (i <= mid)
		b[k++] = a[i++];
	while (j <= last)
		b[k++] = a[j++];
	//[first,last]範圍內的資料已有序,則寫回原陣列
	for (i = 0; i < k; i++)
		a[first + i] = b[i];
}
void mergesort(int a[], int b[], int first, int last)
{
	if (first < last)
	{
		int mid = first + ((last - first) >> 1);
		mergesort(a, b, first, mid);
		mergesort(a, b, mid + 1, last);
		mergeArray(a, b, first, mid, last);
	}
}
void MergeSort(int a[], int n)
{
	if (a && n > 1)
	{
		int *b = new int[n];  //構建輔助陣列
		mergesort(a, b, 0, n - 1);
		delete[]b;
	}
}

在排序過程中,我們使用了一個相同大小的臨時輔助陣列。

演算法分析:

1.演算法的複雜度

對陣列長度為n的序列進行歸併排序,則大約要進行logn次歸併,每一次合併都是線性時間O(n)。故粗略的計算出歸併排序的時間複雜度是O(nlogn)(最好、最差都是這樣)。空間複雜度是O(n)。詳細的時間複雜度分析是這樣的:

對長度為n的序列歸併排序,需要遞迴的對長度為n/2的子序列進行歸併排序,最後把兩段子序列二路歸併。遞推關係是這樣的:T(n)=2T(n/2)+O(n),顯然T(1)=O(1),解得T(n)=o(nlogn)。

2.穩定性

歸併排序是穩定的,並且是時間複雜度為o(nlogn)的幾種排序(快速排序堆排序)中唯一穩定的排序演算法。

3.儲存結構

順序儲存和鏈式儲存都行。

另外,歸併排序多用於外排序中。

若是有所幫助,頂一個哦!

所有內容的目錄