归并排序

最近在看算法导论第三版,练习题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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章