【数据结构从菜鸟到大牛】10分钟搞懂归并排序 ~ <归并排序算法>【图解算法】【归并排序非递归】

写给前面:

💛生活是活给自己看的,你有多大成色,世界才会给你多大脸色。——《人民日报》🧡


在这里插入图片描述
在这里插入图片描述

🌟递归实现归并排序

💫归并排序基本思想

归并排序的基本思想就是:分治法
即:先使每一个子区间有序,然后再使这两个子区间合起来有序
所谓归并就是:

有两个数组,利用两个标记去遍历两个数组,每一次都把小的拿到第三个数组
最后得到的第三个数组就是一个升序数组

归并排序的方法类似于二叉树的后序遍历

  • 分区间

    • 找一个mid表示数组的中间位置把数组分为左右子区间
    • 左右子区间递归找mid去不断分区间
    • 直到区间长度为1,完成分区键
  • 归并

    • 从最小的子区间,把左右区间归并排序
    • 然后返回上一层,把左右区间归并
    • 反复如此

在这里插入图片描述

🎥动图演示归并过程

在这里插入图片描述

📝参考代码

// 先划分区间 去递归左,然后递归右,最后再回来处理本层
// 归并的递归主要是完成区域的划分
// 排序是在最后的那一部分
void PartSort(int* a, int begin, int end, int* tmp)
{
	//如果划分区间只有一个 就不需要再分割了,直接回去合并
	if (begin >= end)
	{
		return;
	}
	//首先划分左右区间
	int mid = (begin + end) / 2;
	//[begin,mid],[mid+1,end]
	PartSort(a, begin, mid,tmp);//[0,0]
	PartSort(a, mid + 1, end,tmp);//[1,1]
	
	//归并
	//对[begin,mid]数组 和[mid+1,end]两个数组归并
	//j为下一次放到tmp数组中的位置,应和这一次的区间的起始位置相同
	int j = begin;
	int begin1 = begin;//第一个数组的起始位置
	int end1 = mid;//第一个数组的终止位置
	int begin2 = mid + 1;//第二个数组的起始位置
	int end2 = end;//第二个数组的终止位置
	while(begin1<=end1 && begin2<=end2)//有一个数组走完就跳出循环
	{
		if (a[begin1] <= a[begin2])
		{
			tmp[j++] = a[begin1++];
		}
		else
		{
			tmp[j++] = a[begin2++];
		}
	}
	//来到这说明有一个数组走完了
	//如果数组1没走完,就继续把数组1的元素拿到tmp数组
	while (begin1 <= end1)
	{
		tmp[j++] = a[begin1++];
	}
	//如果数组2没走完,就继续把数组2的元素拿到tmp数组
	while (begin2 <= end2)
	{
		tmp[j++] = a[begin2++];
	}
	//最后把这一次归并后的数据 拷贝回a
	//归并的哪部分就拷贝哪部分
	
		//a的起始位置  tmp起始位置              此次数组的长度
	memcpy(a+begin, tmp+begin, sizeof(int) * (end - begin + 1));
}
void MergeSort(int* a, int n)
{
	//首先开辟一个额外数组
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		exit(-1);
	}
	//进行排序
	PartSort(a, 0, n-1, tmp);

	//最后free掉tmp数组
	free(tmp);
}

⏱️复杂度分析

时间复杂度
归并排序每一次都是直接找中间位置,然后进行划分左右子区间
所以基本上每一次都是二分
递归log N层
每一层划分多个子区间,一层的所有子区间合计为N的时间复杂度
所以总计时间复杂度为 O(N*logN)

空间复杂度
归并排序需要借助一个 辅助数组 来进行归并
所以额外空间为O(N)
即空间复杂度为O(N)

🌟非递归版本

💫分析

我们知道快速排序是可以利用栈的性质来进行非递归的改版的
但是归并不一样!
归并属于后序方式遍历
所以不适合用栈
因为栈适合前序,处理完当前就扔了,递归左右子区间
但是后序的话,通过栈可以把区间分出来,但是 如果返回去的时候,如何找到上一个较大的区间来进行归并呢?
如:[0,0] ,[1,1]无法再分区间了,开始需要对[0,1]进行归并排序,找不回去了!

那么归并的非递归如何实现呢?
我们可以利用直接分组
归并无非就是每一次把两个小数组进行排序然后合并到原数组中

  • 我们利用一个gap标识每一次小数组的长度,gap从1开始,即每一次对两个长度为1的数组归并
  • 第一个小数组:[begin1,end1],第二个小数组:[begin2,end2]
  • 利用begin1来遍历数组,把每一对小数组都进行归并,并拷贝到tmp数组
  • 然后gap*2,改变两个小数组的长度,重复上面的过程
  • 直到gap>=数组长度n,此时已经把全部元素归并了

画图分析:
我们以gap==1gap==2为例

  • gap==1
    在这里插入图片描述
  • gap==2
    在这里插入图片描述

⏳注意细节

划分完子区间之后,有这样四个值
[begin1, end1]
[begin2,end2]

end1 = begin1 + gap-1 :因为[begin1,end1]之间有gap个元素
begin2 = begin1 + gap :因为begin1begin2之间隔着一个gap个元素的数组
end2 = begin2 + gap-1 :同理因为[begin2,end2]之间有gap个元素

因为begin1在for循环内部
所有begin1的范围一定是[0,n-1]之间
但是 end1begin2end2 都有可能越界
所以 麻烦就在这里:需要对不同情况进行判断和处理
我们需要对边界进行修正:谁越界了修正谁

  1. begin2越界
    如果begin2越界那么 end2也一定越界了,只需要让该数组2不存在即可
    如图:在这里插入图片描述

2.end1越界
如果end1越界,说明begin2end2也一定越界了
那么只需要让end1改为数组最后一个坐标
然后让begin2>end2 使第二个数组不存在即可
如图
在这里插入图片描述
3. end2越界
如果end2越界,说明第一个数组是没有问题的,数组2越界
只需要修改一下数组2的终止位置为n-1即可,让数组2合法
在这里插入图片描述

📝参考代码

//归并排序 非递归版本1

void MergeSortNoRe(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		exit(-1);
	}
	int gap = 1;
	while (gap<n)
	{
		for (int i = 0; i < n; i+=2*gap)
		{
			int begin1 = i;//i为左数组的起始位置
			int end1 = begin1 + gap - 1;//每一组有gap个元素,gap-1是为了 不算begin1位置
			int begin2 = begin1 + gap;//begin1与begin2之间有 gap个元素
			int end2 = begin2 + gap - 1;//第二组也有gap个元素

			//修正区间
			if (end1 >= n)
			{
				end1 = n - 1;
				begin2 = n;
				end2 = n - 1;//让数组2不存在
			}
			else if (begin2 >= n)
			{
				end2 = n - 1;//让数组2不存在
			}
			else if (end2 >= n)
			{
				end2 = n - 1;
			}

			//分完区间之后,归并
			int j = begin1;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}
			
		}
		memcpy(a, tmp, sizeof(int) * n);
		gap *= 2;
	}

	
	free(tmp);
}

⏱️复杂度分析

由于非递归版本每一次其实并不是完美的二分
所以时间复杂度略有差异
但是总体还是O(N*logN)

✨感谢阅读~ ✨
❤️码字不易,给个赞吧~❤️

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

2021狮子歌歌

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

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

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

打赏作者

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

抵扣说明:

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

余额充值