归并排序的正确性证明及时间复杂度分析

template<class _Ty> void merge(_Ty* _begin, _Ty* _mid, _Ty* _end) {
     
	const auto l = _end - _begin + 1;
	_Ty* t = new _Ty[l], * i = _begin, * j = _mid + 1, * k = t;
	while (i <= _mid && j <= _end) {
     
		if (*i <= *j) {
      *k = *i; ++i; ++k; }
		else {
      *k = *j; ++j; ++k; }
	}
	while (i <= _mid) {
      *k = *i; ++i; ++k; }
	while (j <= _end) {
      *k = *j; ++j; ++k; }
	std::copy(t, t + l, _begin);
	delete[] t;
}
template<class _Ty> void merge_sort(_Ty* _begin, _Ty* _end) {
     
	if (_begin < _end) {
     
		_Ty* _mid = _begin + (_end - _begin) / 2;
		merge_sort(_begin, _mid);
		merge_sort(_mid + 1, _end);
		merge(_begin, _mid, _end);
	}
}

归并排序是一种采用了分治思想的排序算法。执行归并排序需要调用merge_sort函数,它分为三个过程:
·将数列划分为两部分。
·递归地分别对两个子序列进行归并排序。
递归终止条件是子数列的长度为1或0。长度为1时无法进入归并函数的三个while循环,递归调用直接返回;长度为0时,merge_sort函数直接返回,merge函数不会被调用。
·合并两个子序列。

这里我们采用尽量将数组划分成相等的两部分递归排序的方案。划分过程的正确性证明如下:
设一个临时数列t,用于归并。
指针i和j分别指向两个子数列的开头。子数列的范围分别是_begin到_mid、_mid + 1到_end。
不断从两个子数列中各取一个元素(即指针i和j分别指向的元素)比较,将较小的数放到数列t中,对应的指针递增(当两个元素相等时,默认将地址较低的数组中的数,即i指向的数放入t)。当其中一个子数列取完后,该循环结束。
将没取完元素的子数列的全部元素都复制到数列t中。
由上面的递归终止条件得:合并开始之前,所有数组的长度都为1。
一个长度为1的数组和一个长度为n的数组进行合并时,要么先将长度为1的数组的唯一一个元素a放入t,要么先将长度为n的数组的所有不比a大的元素都放入t。所以,总能得到一个有序的数组,其长度为n + 1。
两个有序数组合并时,第一个while循环总是先将这两个数组从小到大放到数组t中。当一个数组的数全部放入t后,由于另一个数组剩下的数都是有序的,因此只要将这些数直接复制到临时数组t,就可以得到一个长度为参与合并的两个数组之和的更大的数组,并且它是有序的。
下面证明:长度为n(n > 1)的任何数组,要么由长度分别为1和n – 1的两个数组合并而来,要么由两个长度都大于1但是一定有序的数组合并而来。前一种情况上面已经证明了,接下来用反证法证明第二种情况。
假设长度为n(n > 1)的数组由两个长度都大于1的数组合并而来,但有一个或两个数组是无序的。查看无序数组的合并过程:只要合并出这个无序数组的两个数组有其中一个长度大于1,就再考察这个参与合并该无序数组的数组的合并过程。由于一个数组无序意味着它至少有一部分是无序的,经过这样的倒推我们发现:这部分无序的数组继续回溯下去,发现其源头都至少有1个长度为1的数组参与合并。但前面已经证明,这种情况下,用这样的代码合并出来的数组一定是有序的。而对于两个有序的数组继续合并,也已经证明了合并结果是有序的。这就产生了矛盾,所以该命题只能成立。
所以,进入数列t的元素总是从小到大的。QED.

下面分析归并排序的时间复杂度。
设归并排序的时间复杂度为T(n),n为需要排序的数组长度。
容易看出,递归树的深度为ceil(log⁡n)。当递归树的每一层都满(即n是2的整数次幂)时,每一层贡献的时间复杂度都最大(对应合并操作的时间复杂度),达到O(n)。
所以T(n)=O(n log⁡n)。
归并排序的正确性证明及时间复杂度分析_第1张图片
归并排序的正确性证明及时间复杂度分析_第2张图片
归并排序的正确性证明及时间复杂度分析_第3张图片
在这里插入图片描述

你可能感兴趣的:(#,算法(基础),#,C,/,C++)