合并排序(归并排序)

合并排序,大致思想便是先将数组中的元素拆分成若干小部分,然后再将这些小部分按照顺序进行重新组合,从而实现排序。很明显,这里用到了分治法的思想,即将一个大问题分成若干个相同的小问题,因为问题规模变小了,所以解决问题的难度也随之减小,试想一下,对一个拥有10000个元素的数组进行排序简单还是对一个只拥有1个元素的数组进行排序简单?答案很明显。

对于合并排序,将大问题分成若干小问题是一件很容易的事情,只需要不断递归即可,其中最主要的难点就是将这些小问题解决之后,如何再将这些小问题合并起来,若是处理不好,很可能就导致算法失败。那么接下来就开始正式的讲解。

先上伪代码

因为伪代码没有其他语言的语法限制,能够有助于我们更好地将注意力放在算法上,所以这里我们先用伪代码来讲述一下主要思想:

algorithm mergeSort(A[0..n-1])
//递归调用mergeSort来对数组A[0..n-1]排序
//输入:一个可排序的数组A[0..n-1]
//输出:非降序排列的数组A[0..n-1]
if n > 1  //将数组中的n个元素分成n组
	copy A[0..(n/2)] to B[0..(n/2)]
	copy A[(n/2)..n-1] to C[0..(n/2)-1]
	mergeSort(B[0..(n/2)])
	mergeSort(C[0..(n/2)-1])
	merge(B, C, A)  //将B和C合并到A中
	
algorithm merge(B[0..n-1], C[0..m-1], A[0..n+m-1])
//将两个有序数组B和C合并到A中
//输入:两个有序数组和一个待整合数据的数组
//输出:A[0..n+m-1]中已经有序存好了B和C中的数据
i <- 0;j <- 0;k <- 0
while i < n and j < m do
	if B[i] <= C[j]
		A[k] <- B[i]
		i <- i + 1
	else 
		A[k] <- C[j]
		j <- j + 1
	k <- k + 1
if i = n  //说明B中的所有元素已经存放到A中,即C中的部分元素为存入到A中
	copy C[j..m-1] to A[k..n+m-1]
else 
	copy B[i..n-1] to A[k..n+m-1]

因为不管怎样,一个元素的数组绝对是最好排序的,因为不需要排序,所以我们一开始就需要将整个数组分成n个组,之后再慢慢地合并,如下图便是合并排序的一个例子:

在这里插入图片描述

代码实现

这里我是用的C++来实现该算法:

关键部分:

void merge(int* B, int lenB, int* C, int lenC, int* A, int lenA) {
	int i = 0, j = 0, k = 0;
	while (i < lenB && j < lenC) {
		if (B[i] <= C[j]) A[k++] = B[i++];
		else A[k++] = C[j++];
	}
	if (i == lenB) {
		while (j < lenC) A[k++] = C[j++];
	} else {
		while (i < lenB)A[k++] = B[i++];
	}
}

void mergeSort(int* A, int lenA) {
	if (lenA > 1) {
		int n1 = lenA / 2;
		int n2 = lenA - n1;
		int* B = (int*)malloc(sizeof(int) * n1);
		int* C = (int*)malloc(sizeof(int) * n2);
		for (int i = 0; i < n1; i++) {
			B[i] = A[i];
		}
		for (int i = 0; i < n2; i++) {
			C[i] = A[n1 + i];
		}
		mergeSort(B, n1);
		mergeSort(C, n2);
		merge(B, n1, C, n2, A, lenA);
		free(B);
		free(C);
	}
}

然后在main()函数中调用:

int main() {
	cout << "排序前的数组:" << endl;
	for (int i = 0; i < 10; i++) {
		cout << A[i] << " ";
	}cout << endl;

	mergeSort(A, 10);

	cout << "排序后的数组:" << endl;
	for (int i = 0; i < 10; i++) {
		cout << A[i] << " ";
	}cout << endl;
	return 0;
}

运行结果如下:

在这里插入图片描述

复杂度分析

合并排序的复杂度要分成两部分来看,一部分是分治,另一部分则是合并,

其中合并部分,最差执行n-1次比较,最优执行n/2次比较,都是O(n),

因此得到如下公式:
T ( n ) = { 0 , n = 1 2 T ( n / 2 ) + O ( n ) , n > 1 T(n)=\begin{cases} 0 \quad\quad\quad\quad\quad\quad,n=1\\ 2T(n/2)+O(n), n>1\end{cases} T(n)={0n=12T(n/2)+O(n)n>1
再由主定理可得合并排序的时间复杂度为O(nlogn),并且无论是最优最差还是平均,复杂度都是O(nlogn)。

接下来我们再来看看合并排序的空间复杂度,

我们同样可以得到如下公式:
S ( n ) = { 0 , n = 1 2 S ( n / 2 ) + O ( n ) , n > 1 S(n)=\begin{cases} 0 \quad\quad\quad\quad\quad\quad,n=1\\ 2S(n/2)+O(n), n>1\end{cases} S(n)={0n=12S(n/2)+O(n)n>1
所以我们也可以马上知道合并排序的空间复杂度也为O(nlogn)。

由此我们也可以发现,虽然合并排序的时间复杂度比较好,但是空间复杂度确实太高了,相比冒泡排序、选择排序这些在位排序而言,占用的额外空间实在是太多了。

因此针对合并排序的改进方向便是减小空间复杂度。

参考资料

《算法设计与分析基础》第三版以及老师的课件

  • 22
    点赞
  • 115
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

花无凋零之时

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值