【排序】归并排序(递归+非递归图示详解哦)

文章详细介绍了归并排序的原理和两种实现方式——递归实现和非递归实现。在递归实现中,通过不断分割数组直至单个元素,然后逐步归并;非递归实现则通过动态调整间隔(gap)来合并相邻的有序子数组。两种方法都确保了排序的稳定性。
摘要由CSDN通过智能技术生成

引言

在本篇文章中,将继续介绍一种排序算法:归并排序。
归并排序运用了归并的思想,即将两个有序数列归并为一个有序数列。在前面的合并两个有序链表时,运用了这种思想:戳我看归并实现合并两个有序链表(方法二)

归并排序

思路

归并排序需要一块与数组大小相同的空间,用于临时存储归并后的数据;

排序时,先将整个数组平分为两份,再分为4份……以此类推,直到不能再平分后(区间只剩一个元素);
向上归并,即将两个区间归并到临时空间中的对应位置;
再将临时数组中对应区间中已经排序好的数据拷回原数组;
一直向上归并,直到排序完成。
(需要注意的是,这里的平分不是同时进行的,在递归中,是一条线一条线进行的。左边递归到头后,返回给上一级,继续递归倒数第二层的右边,右边到底返回到上一级,执行最底层左右区间的归并。归并结束后返回到倒数第三层,然后递归该层的右边……)

在这里插入图片描述

递归实现

与快排的递归模式不同,快排是对最大的区间操作完成后,向下递归,对左右区间进行操作,直到排序结束;而归排是先递归到最小区间,再向上归并。所以在实现快排时,每层的操作在前,函数递归在后;而快排是递归在前,每层的操作在后;

函数有4个参数:原数组、区间的左右下标、临时空间。
初始传给函数的参数为整个数组,向下递归,当begin>=end时,return结束递归;
创建midi,记录区间的中间位置,再将左右区间分别递归;

之后实现对区间的操作:
首先用 begin1 、end1 、begin2 、end2记录要归并区间的左右区间的始末下标,并用k记录要归并的区间在临时空间temp中的对应起始位置;
然后将左右区间归并到temp的对应位置;
最后将temp对应空间中的数据拷贝回来:

在这里插入图片描述

对于归并的过程,即将两个区间中的数据依次比较,将较小的元素尾插到临时空间中:

在这里插入图片描述

void _MergeSort(int* a, int begin, int end, int* temp)
{
	if (begin >= end)
	{
		return;
	}
	int midi = (begin + end) / 2;
	_MergeSort(a, begin, midi, temp);
	_MergeSort(a, midi+1, end, temp);
	
	int begin1 = begin, end1 = midi;
	int begin2 = midi+1, end2 = end;
	int k = begin1;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			temp[k++] = a[begin1++];
		}
		else
		{
			temp[k++] = a[begin2++];
		}
	}
	while (begin1 <= end1)
	{
		temp[k++] = a[begin1++];      
	}
	while (begin2 <= end2)
	{
		temp[k++] = a[begin2++];
	}

	memcpy(a + begin, temp + begin, sizeof(int)*(end - begin + 1));
}

void MergeSort(int* a, int n) //递归实现
{
	int left = 0;
	int right = n - 1;
	int* temp = (int*)malloc(sizeof(int) * n);
	if (temp == NULL)
	{
		perror("malloc");
		return;
	}
	_MergeSort(a, left, right, temp);
	free(temp);
}

归排非递归

思路

非递归实现归排时,与快排不同,不用记录上一层的区间,将某个区间归并后,转回原数组即可。所以我们只需要将数组分组,归并每两组的值,每组元素的数量从1开始,每次循环后,每组元素个数乘2,直到大于数组长度循环结束;

每层循环内,需要将这一层中,两个为一组的区间全部归并完成,并将这些数据转回原数组中:

(图)

实现

实现非递归时,首先动态开辟一块临时空间temp;
然后,创建gap为每层中每个需要归并区间的元素个数,并初始化为1;

外层while循环控制gap的值,大于等于数组的元素个数时,循环结束;
内层for循环控制每层中要归并的分组(两个元素个数为gap的小区间为一组归并):
左区间的起始位置begin1为i,结束位置end1为begin1+gap-1;右区间的起始位置begin2为end1+1,结束位置end2为begin2+gap-1;k为对应在temp中的起始位置下标;
然后需要判断的是,这组区间是否越界。由于for循环只是控制了i<n,即begin1<n,所以end1、begin2、end2都有可能越界;
当end1或begin2越界时,说明这组(左右)区间已经少了一个区间,不用归并了。当end2越界时,说明一组左右区间是存在的,需要归并,将end2改为数组最后一个元素的下标即可;

然后对左右区间进行归并,归并到temp中的对应位置;
每组(左右)区间归并后,就将数组拷回原数组:
【需要注意的是,由于之前的end2是可能越界的,所以不能直接拷贝2*gap个数据回去,而应该为end2-i+1个数据】

在这里插入图片描述

void MergeSortNonR2(int* a, int n) //非递归实现(分别转移)
{
	int gap = 1;
	int* temp = (int*)malloc(sizeof(int) * n);
	if (temp == NULL)
	{
		perror("malloc");
		return;
	}

	while (gap < n)
	{
		for (int i = 0; i < n; i += (gap * 2))//每层
		{
			int begin1 = i, end1 = begin1 + gap - 1;
			int begin2 = end1 + 1, end2 = begin2 + gap - 1;
			int k = begin1;

			//判断是否越界(如果越界,直接break跳过归并)
			if (end1 >= n || begin2 >= n)
			{
				break;
			}
			else if (end2 >= n)
			{
				end2 = n - 1;
			}

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

			memcpy(a + i, temp + i, (end2 - i + 1) * sizeof(int));//每次归并都转移
		}

		gap *= 2;//走向下一层
	}
}

总结

到此,关于归并排序的内容就已经介绍完了,同时排序算法的讲解也暂告一段落了
如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出

如果本文对你有帮助,希望一键三连哦

希望与大家共同进步哦

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

阿qiu不熬夜

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

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

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

打赏作者

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

抵扣说明:

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

余额充值