排序算法之歸併排序

一、基本思想

歸併排序,和快排一樣同樣採用了分治的思想,將兩個(或以上)有序表合併成一個新的有序表。

歸併排序步驟如下:
  1. 把N個記錄看成 N個長度爲 1 的有序子表;
  2. 進行兩兩歸併使記錄關鍵字有序,得到 N/2 個長度爲 2 的有序子表; 
  3. 重複第2步直到所有記錄歸併成一個長度爲N的有序表爲止。

二、算法實現

下面是歸併排序算法的遞歸實現

#include <iostream>
#include <malloc.h>
using namespace std;

// 合併兩個有序部分
void mergeArray(int a[], int first, int mid, int last, int temp[]) {
	if (first >= last) {
		return;
	}
	int i = first, j = mid + 1;
	int k = first;
	while (i <= mid && j <= last) {
		if (a[i] > a[j]) {
			temp[k++] = a[j++];
		} else {
			temp[k++] = a[i++];
		}
	}
	while (i <= mid) {
		temp[k++] = a[i++];
	}
	while (j <= last) {
		temp[k++] = a[j++];
	}

	// 回寫到原來數組中
	for (k = first; k <= last; k++) {
		a[k] = temp[k];
	}
}

// 遞歸調用歸併
void mSort(int* a, int first, int last, int *temp) {
	if (first < last) {
		int mid = (first + last) >> 1;
		mSort(a, first, mid, temp);
		mSort(a, mid + 1, last, temp);
		mergeArray(a, first, mid, last, temp);
	}
}

// 歸併排序算法
void mergeSort(int *a, int len) {
	int* temp = (int*) malloc(len * sizeof(int));
	mSort(a, 0, len - 1, temp);
	free(temp);
}

int main() {
	int arr[] = { 213, 43, 43, 123, 45, 52, 67, 234, 452, 5, 67 };
	int len = 11;

	cout << "Before sorting:" << endl;
	for (int i = 0; i < len; i++) {
		cout << arr[i] << "\t";
	}

	mergeSort(arr, len);

	cout << endl << "After merge sorting:" << endl;
	for (int i = 0; i < len; i++) {
		cout << arr[i] << "\t";
	}
	cout << endl;

	return 0;
}

三、算法分析

歸併排序最好、平均、最壞時間複雜度都是:O(n*log2(n))

歸併排序需要額外的存儲空間,其空間複雜度爲:O(n).

歸併排序和快排、堆排序一樣是一種高效的排序算法,在數據規模較大而且存儲空間要求足夠的情況下是非常好的選擇。

四、算法改進

歸併排序改進和優化的方向如下:
  • 當問題分割很小到某個規模的時候停止遞歸,採用簡單插入排序;
  • 消除遞歸調用
  • 消除反覆回寫
  • ...

下面是改進的一個消除遞歸算法:

#include <iostream>
#include <malloc.h>
using namespace std;

// 合併數組中連續的兩個有序部分
void mergeArray(int a[], int first, int mid, int last, int temp[]) {
	int i = first, j = mid + 1;
	int k = first;
	while (i <= mid && j <= last) {
		if (a[i] > a[j]) {
			temp[k++] = a[j++];
		} else {
			temp[k++] = a[i++];
		}
	}
	while (i <= mid) {
		temp[k++] = a[i++];
	}
	while (j <= last) {
		temp[k++] = a[j++];
	}
}

// 根據設定的步長來順序歸併
void mergeStep(int a[], int step, int len, int temp[]) {
	int first, mid, last;
	first = 0;
	last = first + step + step - 1;
	mid = first + step - 1;
	while (last < len) {
		mergeArray(a, first, mid, last, temp);
		first = last + 1;
		last = first + step + step - 1;
		mid = first + step - 1;
	}
	// 末端注意數組邊界
	if (mid > len) {
		for (int i = first; i < len; i++) {
			temp[i] = a[i];
		}
	} else {
		mergeArray(a, first, mid, len - 1, temp);
	}
}

void mergeSort(int a[], int len) {

	cout << "Before sorting:" << endl;
	for (int i = 0; i < len; i++) {
		cout << a[i] << "\t";
	}
	cout << endl;
	//
	int flag = 0; // 寫入方向標識
	int* temp = (int*) malloc(len * sizeof(int));
	// 消除遞歸
	for (int step = 1; step < len; step = step << 1) {
		// 避免返回回寫
		if (flag++ % 2) {
			// flag初始爲奇數,向a寫入排序結果,執行後flag爲偶數
			mergeStep(temp, step, len, a);
		} else {
			// flag初始爲偶數,向temp寫入即可,執行後flag爲奇數
			mergeStep(a, step, len, temp);
		}
	}
	// 若flag爲奇數,則表明排序結果存在於temp中,需要回寫
	if (flag % 2) {
		for (int i = 0; i < len; i++) {
			a[i] = temp[i];
		}
	}
	//
	free(temp);
	//
	cout << "After merge sorting:" << endl;
	for (int i = 0; i < len; i++) {
		cout << a[i] << "\t";
	}
	cout << endl;

}

int main() {
	int arr[17] = { 213, 67, 89, 10, 23, 9, 23, 45, 12, 456, 234, 67, 12, 0 };
	int len = 17;
	mergeSort(arr, len);
	return 0;
}



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