數據結構(26)--排序篇之歸併排序

參考書籍:數據結構(C語言版)嚴蔚敏吳偉民編著清華大學出版社

1 2-路歸併排序

    歸併就是將兩個或兩個以上的有序數據序列合併成一個有序數據序列的過程。
    採用歸併的思想進行排序—歸併排序
    假設初始序列含有 n個記錄,則可看成是 n個有序的子序列;每個子序列的長度爲 1,然後兩兩歸併,得到 n/2 個長度爲 2 或 1 有序的子序列,再兩兩歸併,... 如此重複,直至得到一個長度爲 n 的有序序列爲止。這種排序方法稱爲 2-路歸併

1.1基本思想

    2-路歸併排序的核心操作是將一維數組中前後相鄰的兩個有序序列歸併爲一個有序序列。

    一趟歸併排序的操作是,調用 [n/2h] 次算法 merge 將SR[1..n] 中前後相鄰且長度爲 h 的有序段進行兩兩歸併,得到前後相鄰、長度爲 2h 的有序段,並存放在TR[1..n]中,整個歸併排序需進行[log2n] 趟。 

1.2代碼實現


package sort.mergingSort;
public class MergeSort {
	/**
	 * @param args
	 */
	public static void merge(int[] L, int s, int m, int n){
		//將有序的L[s...m]和L[m+1...n]歸併爲有序的L[s...n],借用臨時數字(額外的輔存)
		int i=s, j=m+1, k=0;
		int[] tmp = new int[L.length];
		for(k = s; i<=m && j<=n; k++){
			if(L[i] <= L[j])
				tmp[k] = L[i++];
			else
				tmp[k] = L[j++];
		}
		//複製剩餘的元素
		while(i <= m)
			tmp[k++] = L[i++];
		while(j <= n)
			tmp[k++] = L[j++];
		
		//將臨時數組裏的已經排好序的元素返回給L
		for(k=s; k <= n; k++ )
			L[k] = tmp[k];
	}
	//將L[]歸併排序
	public static void mergeSort(int[] L, int start, int end){
		if(start < end){
			int m = (start+end)/2;//整個子序列會逐漸一分爲2,然後2分爲4,4分爲8....,主要依賴於L的長度,
			//直至每個子序列僅含有一個元素,就開始合併
			mergeSort(L, start, m);//遞歸的將L[start...m]歸併爲有序的L[start...m]
			mergeSort(L, m+1, end);//遞歸的將L[m+1...end]歸併爲有序的L[m+1...end]
			merge(L, start, m, end);//將有序的L[start...m]和L[m+1...end]歸併到L[start...end]
		}
		//當start == end時,說明此時子序列中只有一個元素,即在此後,每2個單元素將會合併成含有2個元素的子序列
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] test = {0, 46, 55, 13, 42, 94, 5, 17, 70}; //0號單元未使用
		mergeSort(test, 1, test.length-1);
		for(int i = 1; i <= test.length-1; i++)
			System.out.print(test[i]+" ");
	}
}
運行結果:


1.3性能分析

    在歸併排序算法中,遞歸深度爲O(log2n),記錄關鍵字的比較次數爲O(nlog2n)。算法總的時間複雜度爲O(nlog2n)
    歸併排序佔用附加存儲較多,需要另外一個與原待排序記錄數組同樣大小的輔助數組。這是這個算法的缺點。
    與快速排序和堆排序相比,歸併排序的最大特點是,它是一種穩定的排序方法。

2.各種內部排序方法的比較


2.1各種內部排序的比較

從上表可以得出如下幾個結論:
    (1)從平均時間性能而言,快速排序最佳,其所需時間最省,但快速排序在最壞情況下的時間性能不如堆排序和歸併排序。而後兩者相比較的結果是,在n較大時,歸併排序所需時間較堆排序省,但它所需的輔助存儲量最多。
    (2)“簡單排序”包括除希爾排序之外的所有插入排序,冒泡排序和簡單選擇排序,其中以直接插入排序爲最簡單,當序列中的記錄“基本有序”或n值較小時,它是最佳的排序方法,因此常將它和其它的排序方法,諸如快速排序、歸併排序等結合在一起使用。 
    (3)基數排序的時間複雜度也可寫成O(d·n)。因此,它最適用於n值很大而關鍵字較小的序列。若關鍵字也很大,而序列中大多數記錄的“最高位關鍵字”均不同,則亦可先按“最高位關鍵字”不同將序列分成若干“小”的子序列,而後進行直接插入排序。
    (4)從方法的穩定性來比較,基數排序是穩定的,所有時間複雜度爲O(n2)的簡單排序法(除簡單選擇)也是穩定的,而快排、堆排序和希爾排序等時間性能較好的排序方法都是不穩定的。一般來說,排序過程中的“比較”是在“相鄰的兩個記錄關鍵字”間進行的排序方法是穩定的。

2.2各種內部排序的選擇

各種內排序方法的選擇:
  1.從時間複雜度選擇

對元素個數較多的排序,可以選快速排序、堆排序、歸併排序,元素個數較少時,可以選簡單的排序方法。
  2.從空間複雜度選擇
儘量選空間複雜度爲O(1)的排序方法,其次選空間複雜度爲O(log2n)的快速排序方法,最後才選空間複雜度爲O(n)二路歸併排序的排序方法。 
  3.一般選擇規則
(1)當待排序元素的個數n較大,排序碼分佈是隨機,而對穩定性不做要求時,則採用快速排序爲宜。
(2)當待排序元素的個數n大,內存空間允許,且要求排序穩定時,則採用二路歸併排序爲宜。
(3)當待排序元素的個數n大,排序碼分佈可能會出現正序或逆序的情形,且對穩定性不做要求時,則採用堆排序或二路歸併排序爲宜。
(4)當待排序元素的個數n小,元素基本有序或分佈較隨機,且要求穩定時,則採用直接插入排序爲宜。
(5)當待排序元素的個數n小,對穩定性不做要求時,則採用直接選擇排序爲宜,若排序碼不接近逆序,也可以採用直接插入排序。冒泡排序一般很少採用。 

2.3內部排序的最快速度

討論:
    本次討論的多數排序算法是在順序存儲結構上實現的,因此在排序過程中需進行大量記錄的移動。當記錄很大(即每個記錄所佔空間較多)時,時間耗費很大,此時可採用靜態鏈表作存儲結構。如表插入排序、鏈式基數排序,以修改指針代替移動記錄。
    但是,有的排序方法,如快速排序和堆排序,無法實現表排序。在這種情況下可以進行“地址排序”,即另設一個地址向量指示相應記錄;同時在排序過程中不移動記錄而移動地址向量中相應分量的內容。
內部排序可能達到的最快速度是什麼 
    本次討論的各種排序方法,其最壞情況下的時間複雜度或爲O(n^2 ),或爲O(nlogn),其中O(n^2 )是它的上界,那麼O(nlogn)是否是它的下界,也就是說,能否找到一種排序方法,它在最壞情況下的時間複雜度低於O(nlogn)呢? 


    若二叉樹的高度爲 h,則葉子結點的個數不超過 2h-1;反之,若有 u 個葉子結點,則二叉樹的高度至少爲 [log2u]向上取整+1。即,描述 n個記錄排序的判定樹上必存在一條長度爲 [log2(n!)]向上取整   的路徑。
    由此得到下述結論:任何一個藉助“比較”進行排序的算法,在最壞情況下所需進行的比較次數至少爲  [log2(n!)]向上取整   。 習健:根據斯特林公式,有 [log2(n!)]向上取整    =O(nlogn),所以從數量級上,藉助於“比較”進行排序的算法在最壞情況下能達到的最好的時間複雜度爲O(nlogn)

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