(戀上資料結構筆記):歸併排序(Merge Sort)

Asinmy發表於2020-12-04

目錄

歸併排序

序列分割-divide

序列合併-merge

原地合併-merge

左邊先結束

右邊先結束

程式碼實現複雜度分析

歸併排序

  • 1945年由約翰·馮·諾伊曼(John von Neumann)首次提出。
  • 執行流程
    • ① 不斷地將當前序列平均分割成 2 個子序列
      • 直到不能再分割(序列中只剩 1 個元素)
    • ② 不斷地將 2 個子序列合併成一個有序序列
      • 直到最終只剩下 1 個有序序列

序列分割-divide

protected void sort() {
	leftArray = (T[]) new Comparable[array.length >> 1];
	sort(0, array.length);
}
/
 * 對 [begin, end) 範圍的資料進行歸併排序
 */	
private void sort(int begin, int end){
	if(end - begin < 2) return; // 至少要2個元素
	
	int mid = (begin + end) >> 1;
	sort(begin, mid); // 歸併排序左半子序列
	sort(mid, end);	// 歸併排序右半子序列
	merge(begin, mid, end); // 合併整個序列
}

序列合併-merge

原地合併-merge

  • 將兩個序列合併時,不一定要合併到新空間,可以合理的利用原空間實現原地合併。
  • 例如:
    • 將 array的左半部分[begin, mid),備份到 leftArray 中;
    • 然後將 leftArray 視為左子序列,arrary的右半部分[mid, end] 視為右子序列;
    • 將左子序列和右子序列合併到 array 中。

  • 合併過程

左邊先結束

右邊先結束

程式碼實現

public class MergeSort <T extends Comparable<T>> extends Sort<T> {
private T[] leftArray;

@Override
protected void sort() {
	// 準備一段臨時的陣列空間, 在merge操作中使用
	leftArray = (T[])new Comparable[array.length >> 1];
	sort(0, array.length);
}

/**
 * 對 [begin, end) 範圍的資料進行歸併排序
 */	
private void sort(int begin, int end){
	if(end - begin < 2) return; // 至少要2個元素
	
	int mid = (begin + end) >> 1;
	sort(begin, mid); // 歸併排序左半子序列
	sort(mid, end);	// 歸併排序右半子序列
	merge(begin, mid, end); // 合併整個序列
}

/**
 * 將 [begin, mid) 和 [mid, end) 範圍的序列合併成一個有序序列
 */
private void merge(int begin, int mid, int end){
	int li = 0, le = mid - begin; // 左邊陣列(基於leftArray)
	int ri = mid, re = end;	// 右邊陣列(array)
	int ai = begin; // array的索引
	
	// 備份左邊陣列到leftArray
	for(int i = li; i < le; i++){
		leftArray[i] = array[begin + i];
	}
	
	// 如果左邊還沒有結束
	while(li < le){ // li == le 左邊結束, 則直接結束歸併
		if(ri < re && cmp(array[ri], leftArray[li]) < 0){ // cmp改為<=0會失去穩定性
			array[ai++] = array[ri++]; // 右邊<左邊, 拷貝右邊陣列到array
		}else{
			array[ai++] = leftArray[li++]; // 左邊<=右邊, 拷貝左邊陣列到array
		}
	}
}
}
  • 測試

複雜度分析

【常見的遞推式和複雜度】

相關文章