【图解C语言】非递归 实现 归并排序

这一篇文章,我们来讲解一下使用非递归的方式来实现归并排序:

如果有小伙伴没有看过归并排序的常规写法,或者想了解其他的排序方法,可以看下面这篇博客:
【图解C语言】只是朴实无华的排序罢了

首先我们我们要思考,我们是使用循环还是栈模拟来代替递归:

对于这个问题,一般我是这样选择的:递归是把一个问题有最大分解到最小,而循环是把一个问题由最小还原到最大,所以,如果一个问题从最小大最大 或者 最大到最小 都可以解决,那么一般我们使用循环去实现递归,否则,使用栈模拟。

可能这样讲比较抽象,我们以斐波那契数列为例:

Fib(N)=Fib(N-1)+F(N-2)

我们使用递归的解法是:
在这里插入图片描述
很显然,对于递归,就是一个从上到下的过程,而循环,就是一个从下到上的过程。所以,对于斐波那契数列的模拟,我们可以用循环替代递归。
·

我们依然以数组[10, 6 ,7 , 1, 3, 9, 4, 2]为例:

既然我们选择用循环模拟,就应该从递归的方面思考,从局部到整体排序:
在这里插入图片描述
从图里可以看出,我们的思路很明确,先一个一组,每组排序(一个元素默认有序)先两个一组,每组排序,再四个一组,每组排序,最后八个一组,整体排序,

总结为一句话,我们使数组由局部有序 到整体有序

当然,这里有同学要问:这个数组排序前哪里局部有序了?当然有,如果分组的时候我们只以一个元素为一组,每一组就是有序的。

为了控制我们每组元素的个数 以及在合并两个组的时候方便排序,我们要对每个组的两个边界做标记。在这里我们以begin作左边界,end作右边界.

文字描述有点抽象,因此我们可以通过画图表现大致的过程:

  1. 最开始,每一组中元素只有一个,相邻两组合并。
    在这里插入图片描述

2.经过一次归并,数组中元素两两有序,现在我们以两个元素为一组,相邻组合并:
在这里插入图片描述

  1. 经过两次归并,数组中元素四四有序,现在我们以四个元素为一组,相邻组合并

在这里插入图片描述
4.经过三次归并,数组中元素全部有序,排序结束。

注意,在两个组的归并中,由于数组是一段连续的空间,所以,我们对数组的局部排序在原数组上并不方便,所以,我们得特别开辟一个动态数组,我们从两个组中取数放到临时数组中,排好序后,再将临时数组中的数据拷贝回原数组的对应位置。具体的分析在之前的 归并常规写法中有。

相信讲到这里,思路已经很清晰了,我们现在以代码的形式写出:

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);
	int groupNum = 1;
	while (groupNum < n)
	{
		for (int i = 0; i < n; i += 2 * groupNum)
		{
			// [begin1][end1] [begin2,end2] 
			// 归并
			int begin1 = i, end1 = i + groupNum - 1;
			int begin2 = i + groupNum, end2 = i + groupNum * 2 - 1;
			int index = begin1;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}

			while (begin2 <= end2)
			{
				tmp[index++] = a[begin2++];
			}
		}
		// 拷贝回原数组
		for (int i = 0; i < n; ++i)
		{
			a[i] = tmp[i];
		}

		groupNum *= 2;

		//PrintArray(a, n);
	}
	
	free(tmp);
}

这个时候,运行我们的代码,却会发现程序报错:
在这里插入图片描述
显然,我们的程序存在越界相关的问题,逻辑并不严谨。

那么问题出在哪里?

我们将之前的示范数组添加两个数,然后通过画图看一下运行过程:
在这里插入图片描述
通过画图,问题很显然了,在我们第二次归并的时候,我们的组数不是偶数,导致最后一组没有其他组和它合并。

也就是说:数组数据个数,并不一定是按整数倍,所以划分的分组可能越界或者不存在

在这里插入图片描述
这个问题并不难解决:

           if (begin2 >= n)
			{
				begin2 = n + 1;
				end2 = n;
			}

当出现这个问题,我们将[begin2,end2]这个区间修正为一个不存在的区间,即end2>begin2,这样我们就直接跳过两个组的归并过程,不处理这个多出的组。

组数可能是奇数,那么数组的元素个数是奇数也会是个问题,而且与上面的情况不一样,所以我们得使用其他方法修正.

在这里插入图片描述
这个时候begin2没有越界,end2越界,所以我们针对这一点进行修正:

            if (end2 >= n)
			{
				end2 = n - 1;
			}

我们将end2修正为n-1,也就是begin2==end2,这样就可以使程序正常运作了,并且将这个多出的数参与排序。

同理,可能出现begin1没有越界,end1越界,我们用同样方法修正:

            if (end1 >= n)
			{
				end1 = n - 1;
			}

至此,我们可以写出完整的程序:

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);
	int groupNum = 1;
	while (groupNum < n)
	{
		for (int i = 0; i < n; i += 2 * groupNum)
		{
			// [begin1][end1] [begin2,end2] 
			// 归并
			int begin1 = i, end1 = i + groupNum - 1;
			int begin2 = i + groupNum, end2 = i + groupNum * 2 - 1;
			int index = begin1;

			// 数组数据个数,并不一定是按整数倍,所以划分的分组可能越界或者不存在
			// 1、[begin2,end2] 不存在, 修正为一个不存在的区间
			if (begin2 >= n)
			{
				begin2 = n + 1;
				end2 = n;
			}

			// 2、end1越界,修正一下
			if (end1 >= n)
			{
				end1 = n - 1;
			}

			// 3、end2越界,需要修正后归并
			if (end2 >= n)
			{
				end2 = n - 1;
			}

			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}

			while (begin2 <= end2)
			{
				tmp[index++] = a[begin2++];
			}
		}
		// 拷贝回原数组
		for (int i = 0; i < n; ++i)
		{
			a[i] = tmp[i];
		}

		groupNum *= 2;

		//PrintArray(a, n);
	}
	
	free(tmp);
}
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Ornamrr

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

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

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

打赏作者

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

抵扣说明:

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

余额充值