歸併排序

最近在看算法導論第三版,練習題2.3-2要求根據書中介紹,使用不帶哨兵的歸併排序算法。

歸併排序的基本思想可以理解爲:將數組二分,分成不能再分的單個元素爲止,然後將兩個單個的元素比較,按照大小順序合併,一層一層向上遞歸。
遞歸合併如果不使用哨兵,就需要人工去檢測邊界,根據邊界的變化處理合並時,可能出現的越界情況,算法性能分析參考算法導論。

歸併算法流程:(注意這裏使用的下標跟算法導論中原始的下標有出入,本作者自己覺着原書的寫法用起來不方便)

算法流程:

Merge_sort(A,p,q):
if(p<q):
r=floor(p+q);
Merge_sort(A,p,r);
Merge_sort(A,r+1,q);
Merge(A,p,r,q);

template<typename T>
void merge_sort(std::vector<T> & input,int p,int q)
{
	if (p < q)           //左邊界必須要於右邊界
	{
		int r = (int)floor((q + p) / 2);
		merge_sort(input, p, r);        //將數組分成兩個部分---前部分
		merge_sort(input, r + 1, q);    //將數組分成兩個部分---後部分
		merge<T>(input, p, r, q);       //合併
	}
}

最關鍵的部分就是合併!
算法導論中帶哨兵的算法流程上圖爲算法導論帶哨兵的算法流程,我做的改進是在加入條件判斷。在合併的時候,由於分出左右數組,左右數組的元素個數是在合併之前知道的,所以如果有一個數組的元素都被合並進去了,則直接將另外一個數組的元素按現有順序放到新的數組中即可,知道填滿。

template<typename T>
void merge(std::vector<T> &input, int p, int r, int q)
{
	int n1 = r - p+1;   //計算合併的所有左數組元素個數
	int n2 = q - r;		//計算合併的右數組元素個數

	std::vector<T> l_vector, r_vector;

	for (int i = 0; i < n1+n2; i++)    //拷貝數據
	{
		if (i < n1)
			l_vector.push_back(input[p + i]);
		else
			r_vector.push_back(input[p + i]);
	}

	int i = 0;
	int j = 0;

	for (int k = 0; k <= q - p; k++)
	{
		if (i < n1 && j < n2)    //如果左右數組都存在未被拷貝完的元素,選擇小的拷貝,並將相應的指針向後移動一個位置,直到結束
		{
			if (l_vector[i] < r_vector[j])
			{
				input[p+k] = l_vector[i];
				i++;
			}
			else
			{
				input[p+k] = r_vector[j];
				j++;
			}
		}

		else if (i < n1 && j == n2)   //這是右側數組元素拷貝完成而左側數組拷貝未完成,直接將左側數組元素按照順序向合併數組拷貝即可
		{
			input[p+k] = l_vector[i];
			i++;
		}

		else if(i==n1 && j<n2)  //這是左側數組元素拷貝完成而左側數組拷貝未完成,直接將右側數組元素按照順序向合併數組拷貝即可
		{
			input[p+k] = r_vector[j];
			j++;
		}
		else if (n1 == 1 && n2 == 0)  //處理邊界情況,即存在級數個數是一定會發生只有一個元素,此時左側有元素,右側無元素,直接將單個元素拷貝即可,不需要比較大小
		{
			input[p+k] = l_vector[0];
		}
	}
}

測試用例如下:

void test_merge_sort()
{
	std::vector<int> v = { 4,3,1,1 };
	merge_sort(v, 0, v.size()-1);
	for (auto x : v)
		std::cout << x << std::endl;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章