1. 程式人生 > >【演算法】排序演算法——歸併排序

【演算法】排序演算法——歸併排序

前言

       歸併排序是分治法在排序問題上的運用,因此為了更好地瞭解歸併排序,首先了解一下分治法。分治法的基本思想是:將原問題分解為幾個規模較小但是類似於原問題的子問題,遞迴地求解這些子問題,然後合併子問題的解來建立原問題的解。

      分治模式在每層遞迴時有三個步驟:

      分解原問題為若干子問題,這些子問題是原問題的規模較小的例項;

      解決子問題,遞迴地求解子問題;若子問題足夠小,直接求解;

      合併子問題的解,獲得原問題的解。

      歸併排序演算法遵循分治模式,操作如下:

      分解:分解待排序的具有n個元素的序列,成為具有n/2個元素的兩個子序列;

      解決:使用歸併排序遞迴地排序子序列;

      合併:合併已排序的兩個子序列產生已排序的答案。

歸併排序

       在歸併排序中,首先將初始序列不斷分解,當分解至子序列長度為1時,便開始遞迴排序。如下圖所示,圖中初始序列本分解為8個長度為1的子序列,而後開始歸併排序;

圖 1

       在進行排序時,需要一個函式merge()進行操作,在這個函式中有三個下標,分別指向兩個子序列中的元素以及歸併產生的有序序列中的位置;以上圖第二層中的9、11與5、8序列為例,merge()函式執行的操作可以用下圖表示。

圖 2

      在上圖中,第一次進行對比後將5放入歸併序列中,然後將j與k各往後移動一位。在第二次對比完後,j已經移動到序列尾部,此時只剩下第一個序列中的元素,因為元素已經是有序的,所以直接放入歸併序列即可。可以從這一步看出,merge()的時間複雜度為Θ(n)。

編寫程式碼

      首先設計一個函式merge_sort()對序列進行二分,當二分至子序列中只含有一個元素時,因為單個元素自身就是“有序”的,無需進行比較;然後將兩個具有單個元素的子序列進行歸併,這時候就需要呼叫merge()函式進行歸併排序,返回有序的序列。如此往復,直至所有的元素都被排序。思路理清楚了,可以開始寫程式碼;

bool merge_sort(vector<int> &a,int begin,int end)
{
	//--若只傳遞了一個引數,無需進行排序,直接返回  
	if (begin >= end)
	{
		return true;
	}


	int mid = (end + begin) / 2;
	merge_sort(a, begin, mid);
	merge_sort(a, mid + 1, end);

	//--經過上面的歸併後兩個子序列內部已經是有序的,若兩個子序列中第二個子序列的第一個元素已經大於第一個子序列的最後一個元素,則不需要排序;
	if (a[mid] <= a[mid + 1])
	{
		return true;
	}
	merge(a, begin, mid, mid + 1, end);
		
	
	return true;
}

       merge()函式的功能在上面已經介紹過了,輸入引數是主序列以及四個序號,四個序列號分別表示兩個子序列的區間。首先建立兩個臨時序列用於存放子序列,然後開始比較臨時序列中的元素,將元素依次存放入主序列當中,實現程式碼如下;

void merge(vector<int> &a, int l_begin, int l_end,int r_begin,int r_end)
{
	
	//if (l_begin == l_end&&r_begin == r_end)     //--如果只有一個元素,則只需比較一次比較了;
	//{
	//	if (a[l_begin]>a[r_begin])
	//	{
	//		int s = a[r_begin];
	//		a[r_begin] = a[l_begin];
	//		a[l_begin] = s;		
	//	}
	//	return ;
	//}
	//
	int leng1 = l_end - l_begin + 1;
	int leng2 = r_end - r_begin + 1;


	//--建立兩個臨時的vector進行存放序列;
	vector<int> temp_L;
	vector<int> temp_R;

	for (int i = 0; i < leng1; i++)
	{
		temp_L.push_back(a[l_begin+i]);
	}
	for (int i = 0; i < leng2; i++)
	{
		temp_R.push_back(a[r_begin + i]);
	}

	int i = 0;
	int j = 0;
	while (i < leng1 && j < leng2)
	{
		if (temp_L[i] <= temp_R[j])
		{
			a[l_begin + i + j] = temp_L[i];
			i++;
		}
		else
		{
			a[l_begin + i + j] = temp_R[j];
			j++;
		}
	}

	//--當某個其中一個序列的元素已經全部被放置進入歸併後的序列,此時直接將第另一個剩下部分序列放入歸併序列即可;
	while (i <leng1)
	{
		a[l_begin + i + j] = temp_L[i];
		i++;
	}
	while (j <leng2)
	{
		a[l_begin + i + j] = temp_R[j];
		j++;
	}

	return ;
}

執行結果

      原始序列

  

    排序結果

演算法分析

時間複雜度

       觀察圖1可以知道在每一層中每個元素都需要merge()函式進行歸併排序,每一層的時間複雜度為Θ(n),而在分解序列時,只需要找到序列的中點,這一步的時間複雜度為Θ(1),為低階項;進行二分歸併時,總共會有層,所以時間複雜度為=,忽略低階項後,演算法的時間複雜度為。(注:分析時間複雜度時常用表示但是在數學中代表)。

空間複雜度

     從程式碼中不難看出,在進行歸併時需要一個臨時的空間存放序列,所以不是原地排序,排序的空間複雜度為

排序穩定性

     歸併排序是穩定排序。

完整程式碼

     為了方便大家測試,在這裡提供所有程式碼;

//------------------------------------
//----潘正宇,歸併排序
//----2018.01.25
//------------------------------------
#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>
#include <vector>
#include <sstream>

using namespace std;


void merge(vector<int> &a, int l_begin, int l_end, int r_begin, int r_end);

bool merge_sort(vector<int> &a,int begin,int end)
{
	//--若只傳遞了一個引數,無需進行排序,直接返回  
	if (begin >= end)
	{
		return true;
	}


	int mid = (end + begin) / 2;
	merge_sort(a, begin, mid);
	merge_sort(a, mid + 1, end);

	//--經過上面的歸併後兩個子序列內部已經是有序的,若兩個子序列中第二個子序列的第一個元素已經大於第一個子序列的最後一個元素,則不需要排序;
	if (a[mid] <= a[mid + 1])
	{
		return true;
	}
	merge(a, begin, mid, mid + 1, end);
		
	
	return true;
}

void merge(vector<int> &a, int l_begin, int l_end,int r_begin,int r_end)
{
	
	//if (l_begin == l_end&&r_begin == r_end)     //--如果只有一個元素,則只需比較一次比較了;
	//{
	//	if (a[l_begin]>a[r_begin])
	//	{
	//		int s = a[r_begin];
	//		a[r_begin] = a[l_begin];
	//		a[l_begin] = s;		
	//	}
	//	return ;
	//}
	//
	int leng1 = l_end - l_begin + 1;
	int leng2 = r_end - r_begin + 1;


	//--建立兩個臨時的vector進行存放序列;
	vector<int> temp_L;
	vector<int> temp_R;

	for (int i = 0; i < leng1; i++)
	{
		temp_L.push_back(a[l_begin+i]);
	}
	for (int i = 0; i < leng2; i++)
	{
		temp_R.push_back(a[r_begin + i]);
	}

	int i = 0;
	int j = 0;
	while (i < leng1 && j < leng2)
	{
		if (temp_L[i] <= temp_R[j])
		{
			a[l_begin + i + j] = temp_L[i];
			i++;
		}
		else
		{
			a[l_begin + i + j] = temp_R[j];
			j++;
		}
	}

	//--當某個其中一個序列的元素已經全部被放置進入歸併後的序列,此時直接將第另一個剩下部分序列放入歸併序列即可;
	while (i <leng1)
	{
		a[l_begin + i + j] = temp_L[i];
		i++;
	}
	while (j <leng2)
	{
		a[l_begin + i + j] = temp_R[j];
		j++;
	}

	return ;
}


void main()
{
	ifstream inmyfile("123.txt");
	ofstream outmyfile("1234.txt");
	string line;
	vector<int> A;
	

	if (inmyfile)
	{
		int x;
		while (getline(inmyfile,line))
		{
			istringstream is(line);
			
			string s;
			while (is>>s)
			{
				x = atoi(s.c_str());
				A.push_back(x);
			}
			
		}
	}
	else
	{
		cout << "get input file was fail" << endl;
	}

	int len = A.size();

	merge_sort(A, 0, len-1);

	for (int i = 0; i < len; i++)
	{
		outmyfile << A[i] << " ";
		
	}
	cout << endl;
	cout << A[10];
	system("pause");
}



已完。。