歸併排序(重溫經典算法系列)







原理簡述

單個元素肯定有序;
歸併排序採用分治思想,分而治之:
將待排序數組劃分爲n等分,每份長度爲1個元素,則 n份全部有序;
再一生二,二生四,逐步兩兩元素有序的區間,歸一合併成1個有序區間;
最終會歸併出整一個數組元素有序的結果.
歸併排序有兩種實現方式:
(1)自頂向下遞歸調用實現;
(2)自底向上迭代執行實現.


遞歸調用(自頂向下,Top-down)

//左右雙閉區間[low,high];
//歸併過程;
template<typename T>
void __Merge(T* arr, T* tempArr, const int low, const int mid, const int high) {

	//臨時存儲空間需要在每一次歸併之前更新數據,使得已排序的元素得以存儲;
	for (int index = low; index <= high; ++index) {
		tempArr[index] = arr[index];
	}

	int i = low;//左區間[low,mid]起點;
	int j = mid + 1;//右區間[mid+1,high]起點;

	//歸併過程,使用索引k記錄已排好序的最後元素位置;
	for (int k = low; k <= high; ++k) {
		if (i > mid) {//左區間已遍歷完;
			arr[k] = tempArr[j++];
		}
		else if (j > high) {//右區間已遍歷完;
			arr[k] = tempArr[i++];
		}
		else if (tempArr[i] <= tempArr[j]) {//相等時取左側元素以保持排序的穩定性;
			arr[k] = tempArr[i++];
		}
		else {//左大右小時,才取右區間元素;
			arr[k] = tempArr[j++];
		}
		////輸出並理解中間過程;
		//RandomArrayFuntionHelper::PrintArray(arr, low, high+1);
		//RandomArrayFuntionHelper::PrintArray(tempArr, low, high+1);
		//std::cout << " ------\n";


	}
}
//分治過程;
template<typename T>
void __MergeSort(T* arr, T* tempArr, int low, int high) {//左閉右開區間,[low,high);

	//if (low < high) {
	//通過修改條件並通過else代碼,使用直接插入排序優化;
	if (low + 15 < high) {
		//int mid = low + (high - low) / 2;
		int mid = low + ((high - low) >> 1);

		//分(二分、折半);
		__MergeSort(arr, tempArr, low, mid);
		__MergeSort(arr, tempArr, mid + 1, high);
		//治(歸併、合一);
		if (arr[mid] > arr[mid + 1]) {
			__Merge(arr, tempArr, low, mid, high);
			//下一行代碼爲適得其反的優化,https://blog.csdn.net/tinyDolphin/article/details/78457343;
			//__Merge(tempArr, arr, low, mid, high);
		}
	}
	else {
		DirectInsertionSort(arr, low, high);
		//BinaryInsertSort(arr, low, high);
		//經測試,百萬數量級時:剩餘 10~50 元素左右時使用,效果較佳(治在if優化之前15最佳,加了if條件之後50最好);
		//千萬數量級時:剩餘 30 元素左右時使用,效果較佳;
		//二分插入的優化比直接插入的優化稍微遜色,也可以適當使剩餘元素變爲最佳狀態時的 3/2倍左右,二分的效果更佳;
	}
}
//遞歸調用(Top-down);
template<typename T>
//void DataSort<T>::MergeSortTD(T* arr, const std::size_t n) {
void MergeSortTD(T* arr, const std::size_t n) {
	//臨時數組空間;
	T *tempArr = new T[n];
	//僅處理元素索引區間(相當於左右全閉區間);
	__MergeSort(arr, tempArr, 0, n - 1);
	delete[] tempArr;
}

溫馨提示:
代碼中間採用了小規模時,直接通過直接插入排序算法實現優化;
該部分代碼鏈接在我的另外一篇文章裏,鏈接如下:

插入排序(重溫經典算法系列)

//重載直接插入排序;
template <typename T>
void DirectInsertionSort(T* arr, const int low, const int high) {//high極可能會傳入負數值,故而不可使用無符號數;

	assert(arr);

	//i索引遍歷待排序元素;
	for (int i = low + 1; i <= high; ++i) {

		T temp = arr[i];
		int	j;

		for (j = i; j > low && arr[j - 1] > temp; --j) {
			arr[j] = arr[j - 1];//把前邊的元素往後挪,用這一操作替換掉每一次的數值交換swap,能減少開銷;
		}
		//找到合適插入位置j,第二次循環提前終止;
		if (j != i) {
			arr[j] = temp;
		}
	}
}

關於歸併排序算法的具體優化思想,可以參考文章:

歸併排序及其優化



迭代實現(自底向上,Bottom-up)


需要使用頭文件:

#include <algorithm>
//歸併過程;
template<typename T>
void __Merge(T* arr, T* tempArr, const int low, const int mid, const int high) {

	//臨時存儲空間需要在每一次歸併之前更新數據,使得已排序的元素得以存儲;
	for (int index = low; index <= high; ++index) {
		tempArr[index] = arr[index];
	}

	int i = low;//左區間[low,mid]起點;
	int j = mid + 1;//右區間[mid+1,high]起點;

	//歸併過程,使用索引k記錄已排好序的最後元素位置;
	for (int k = low; k <= high; ++k) {
		if (i > mid) {//左區間已遍歷完;
			arr[k] = tempArr[j++];
		}
		else if (j > high) {//右區間已遍歷完;
			arr[k] = tempArr[i++];
		}
		else if (tempArr[i] <= tempArr[j]) {//相等時取左側元素以保持排序的穩定性;
			arr[k] = tempArr[i++];
		}
		else {//左大右小時,才取右區間元素;
			arr[k] = tempArr[j++];
		}
		////輸出並理解中間過程;
		//RandomArrayFuntionHelper::PrintArray(arr, low, high+1);
		//RandomArrayFuntionHelper::PrintArray(tempArr, low, high+1);
		//std::cout << " ------\n";


	}
}

//歸併排序(自底向上迭代版)(Bottom-up);
template<typename T>
//void DataSort<T>::MergeSortBU(T* arr, const std::size_t n) {
void MergeSortBU(T* arr, const std::size_t n) {

	T* tempArr = new T[n];
	//最開始的小規模數組,使用直接插入排序優化;
	const std::size_t littleSize = 16;
	for (std::size_t index = 0; index < n; index += littleSize) {
		DirectInsertionSort(arr, index, std::min(index + littleSize - 1, n - 1));
	}
	//第 i ( i = 0,1,2 …… )輪要歸併已分別各自有序的兩個數組範圍是;
	//arr[ index + i * sz, index + (i+1) * sz - 1];
	//arr[ index + (i+1) * sz, index + (i+2) * sz - 1];
	for (std::size_t sz = littleSize; sz < n; sz += sz) {//分組長度size(元素個數);
		for (std::size_t index = 0; index + sz < n; index += sz + sz) {
			if (arr[index + sz - 1] > arr[index + sz]) {
				__Merge(arr, tempArr, index, index + sz - 1, std::min(index + sz + sz - 1, n - 1));
			}
		}
	}

	delete[] tempArr;
}


參考材料


歸併排序優化思想解說,可以參考:

歸併排序及其優化

歸併排序算法的動畫過程演示,可以參考:
排序算法過程演示

交流方式
QQ —— 2636105163(南國爛柯者)


溫馨提示:
轉載請註明出處!!


文章最後更新時間: 2020年3月29日23:24:16
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章