[排序算法] 9. 归并排序递归与非递归实现及算法复杂度分析(分治算法、归并排序、复杂度分析)

1. 基本思想

在数列排序中,如果只有一个数,那么它本身就是有序的;如果只有两个数,那么一次比较就可以完成排序。也就是说,数越少,排序越容易。那么,如果有一个由大量数据组成的数列,我们很难快速地完成排序,该怎么办呢?可以考虑将其分解为很小的数列,直到只剩一个数时,本身已有序,再把这些有序的数列合并在一起,执行一个和分解相反的过程,从而完成整个数列的排序。

归并排序与快速排序的思想基本一致,唯一不同的是归并排序的基准值是数组的中间元素

快排 Link:[排序算法] 6. 快速排序多种递归、非递归实现及性能对比(交换排序)

在这里插入图片描述
合久必分,分久必合~

分治算法离不开递归啊,首先看看分解的递归操作:

void __MergeSort(int array[], int left, int right, int extra[]) {
	// 终止条件
	if (right == left + 1) {
		// 只剩一个数
		return;
	}

	if (right <= left) {
		// 区间内没有数了
		return;
	}

	int mid = left + (right - left) / 2;
	// [left, mid)	[mid, right)
	__MergeSort(array, left, mid, extra);
	__MergeSort(array, mid, right, extra);
	// 左右区间都有序
	Merge(array, left, mid, right, extra);
}

如果要排序的区间已经有序了,即满足size == 1时,直接返回,否则先找到中间位置,对左边部分进行排序,对右边部分进行排序,当满足左右区间都有序的前提了,合并两个有序数组,使整个数组有序。

合并也很简单,设置三个工作指针,分别为left_indexright_index,这两个指向两个待排序子序列中待比较的位置,extra_index指向辅助数组extra[]待放置元素的位置,比较array[left_index]array[right_index]的大小,将小的赋给extra[extra_index],同时相应的指针向后移动。由于分解的递归调用,调用栈调用完毕后即所有的元素处理完毕。最后将辅助数组中元素复制给原数组即可。下面来看个例子,这个来自《趣学算法》-陈小玉老师编写,讲解的很清楚~:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
重要:别漏了这点啊
在这里插入图片描述
由于分治法涉及到了递归、调用栈等,确实不好理解,建议这个归并排序与快排一起看看,都是较难的排序方法,却刚好有相同之处。

2. 代码实现

2.1 递归实现

// 归并排序-递归(递增)
// 合并两个有序序列
void Merge(int array[], int left, int mid, int right, int extra[]) {
	int size = right - left;
	int left_index = left;
	int right_index = mid;
	int extra_index = 0;
	
	while (left_index < mid && right_index < right) {
		if (array[left_index] <= array[right_index]) {
			extra[extra_index] = array[left_index];
			++left_index;
		}
		else {
			extra[extra_index] = array[right_index];
			++right_index;
		}

		extra_index++;
	}

	while (left_index < mid) {
		extra[extra_index++] = array[left_index++];
	}

	while (right_index < right) {
		extra[extra_index++] = array[right_index++];
	}

	for (int i = 0; i < size; i++) {
		array[left + i] = extra[i];
	}
}

void __MergeSort(int array[], int left, int right, int extra[]) {
	// 终止条件
	if (right == left + 1) {
		// 只剩一个数
		return;
	}

	if (right <= left) {
		// 区间内没有数了
		return;
	}

	int mid = left + (right - left) / 2;
	// [left, mid)	[mid, right)
	__MergeSort(array, left, mid, extra);
	__MergeSort(array, mid, right, extra);
	// 左右区间都有序
	Merge(array, left, mid, right, extra);
}

// 归并排序-递归(递增)
void MergeSort(int array[], int size) {
	int *extra = (int *)malloc(sizeof(int)* size);
	__MergeSort(array, 0, size, extra);
	free(extra);
}

测试数据:int array[] = { 3, 9, 1, 4, 2, 8, 2, 7, 5, 3, 6, 11, 9, 4, 2, 5, 0, 6 };
在这里插入图片描述

2.2 优化—非递归实现

// 归并排序(递增)
// 合并两个有序序列
void Merge(int array[], int left, int mid, int right, int extra[]) {
	int size = right - left;
	int left_index = left;
	int right_index = mid;
	int extra_index = 0;
	
	while (left_index < mid && right_index < right) {
		if (array[left_index] <= array[right_index]) {
			extra[extra_index] = array[left_index];
			++left_index;
		}
		else {
			extra[extra_index] = array[right_index];
			++right_index;
		}

		extra_index++;
	}

	while (left_index < mid) {
		extra[extra_index++] = array[left_index++];
	}

	while (right_index < right) {
		extra[extra_index++] = array[right_index++];
	}

	for (int i = 0; i < size; i++) {
		array[left + i] = extra[i];
	}
}

void MergeSortNor(int array[], int size) {
	int *extra = (int *)malloc(sizeof(int)* size);

	for (int i = 1; i < size; i = i * 2) {
		for (int j = 0; j < size; j = j + 2 * i) {
			int left, mid, right;
			left = j;
			mid = j + i;
			right = mid + i;
			if (mid >= size) {
				// 没有右边的区间 [mid, right)
				continue;
			}

			if (right > size) {
				right = size;
			}

			Merge(array, left, mid, right, extra);
		}
	}

	free(extra);
}

3. 性能分析

时间复杂度

  • 最坏 O ( n l o g n ) O(nlogn) O(nlogn)
  • 平均 O ( n l o g n ) O(nlogn) O(nlogn)
  • 最好 O ( n l o g n ) O(nlogn) O(nlogn)

空间复杂度

  • O ( n ) O(n) O(n)

排序稳定性

  • 稳定

外部排序

  • 支持大数据排序,多路归并要记住这个名词23333~

这时间复杂度的推导发现我是没这个习惯,主要原因平时见的太多,都已经默认成习惯了,但却忽略掉了它的推导过程。学习任何算法课程,首先就是学习时间、空间复杂度的计算,尤其是递归的推导时间复杂度,还是很有意思的,在这就先贴上陈小玉老师的推导结果,希望能够给自己一点警示吧:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
非递归形式在上面已经实现了,确实是个不错的优化~

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

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

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

打赏作者

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

抵扣说明:

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

余额充值