掌握七大排序(4)--- 归并排序

不要停止奔跑,不要回顾来路,来路无可眷恋,值得期待的只有前方。 – 《马男波杰克》
在这里插入图片描述

一.归并排序

1.基本思想及动图演示:

基本思想:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
在这里插入图片描述
动图演示:
在这里插入图片描述

2.递归代码实现:

  • 下面是主函数MergeSort,先开辟一个和待排数组a大小相同的临时数组,我们要用递归实现,不可能每次递归调用都开辟一次空间。所以我们在主函数里创建。
  • 然后就去调用我们的子函数
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	_MergeSort(a,0,n-1,tmp);

	free(tmp);
	tmp = NULL;
}

下面就是子函数_MergeSort的具体实现

void _MergeSort(int* a, int begin, int end,int* tmp)
{
	if (begin >= end)
		return;

	int mid = (begin + end) / 2;
	_MergeSort(a, begin, mid,tmp);
	_MergeSort(a, mid + 1, end,tmp);

	//归并
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] <= a[begin2])
			tmp[i++] = a[begin1++];
		else
			tmp[i++] = a[begin2++];
	}

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

	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}

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

  • 这个过程就是对数组进行不断的二分
	if (begin >= end)
		return;

	int mid = (begin + end) / 2;
	_MergeSort(a, begin, mid,tmp);
	_MergeSort(a, mid + 1, end,tmp);

如图:当begin >= end 时,递归调用结束
在这里插入图片描述
10 6这个子序列的左右递归调用返回后,就可以对这个子序列进行归并排序。
在这里插入图片描述
下面是具体归并的代码:

	//归并
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] <= a[begin2])
			tmp[i++] = a[begin1++];
		else
			tmp[i++] = a[begin2++];
	}

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

	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}

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

画了个图,大家可以看看。
在这里插入图片描述

这里的memcpy函数需要注意一下

在这里插入图片描述
因为每次数组拷贝的起始地址不同,所以我们要加上begin

  • 例如:我们要拷贝tmp数组里的1和7,我们需要使用1的起始地址即(tmp+begin)拷贝到a数组的起始地址即(a+begin)
    在这里插入图片描述
    然后就是对6 10 1 7 进行归并排序,过程和上面类似。

3.非递归实现

使用到一个【rangeN】的变量来控制每次归并区间的大小
在这里插入图片描述

3.1break版

代码实现:

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	int rangeN = 1;
	while (rangeN < n)
	{
		for (int i = 0; i < n; i += 2 * rangeN)
		{
			int begin1 = i, end1 = begin1 + rangeN - 1;
			int begin2 = begin1 + rangeN, end2 = begin2 + rangeN - 1;
			int j = i;

			printf("[%d,%d][%d,%d]\n", begin1, end1, begin2, end2);

			if (end1 >= n)
			{
				break;
			}
			else if (begin2 >= n)
			{
				break;
			}
			else if (end2 >= n)
			{
				end2 = n - 1;
			}

			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 + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		rangeN *= 2;
	}

	free(tmp);
	tmp = NULL;

}
  • 需要注意下面代码的处理:
			if (end1 >= n)
			{
				break;
			}
			else if (begin2 >= n)
			{
				break;
			}
			else if (end2 >= n)
			{
				end2 = n - 1;
			}

当我们没有上面的代码时,我们来观察一下边界情况。可以分为三种
在这里插入图片描述
在这里插入图片描述
三种情况分别处理。

  • 当end1 > n 时,我们可以直接break。
  • 当begin2 > n时,我们也可以直接break。
  • 当end2 > n时,我们需要修正end2的区间。
  • 注意
    break版本只能部分拷贝,不可以全部拷贝,因为break后,没有数据进tmp数组,整体拷贝会让原数组丢失数据。

3.2修区间版

//修正版
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	int rangeN = 1;
	while (rangeN < n)
	{
		for (int i = 0; i < n; i += 2 * rangeN)
		{
			int begin1 = i, end1 = begin1 + rangeN - 1;
			int begin2 = begin1 + rangeN, end2 = begin2 + rangeN - 1;

			int j = i;

			printf("[%d,%d][%d,%d]\n", begin1, end1, begin2, end2);


			if (end1 >= n)
			{
				end1 = n - 1;
				// 不存在区间
				begin2 = n;
				end2 = n - 1;
			}
			else if (begin2 >= n)
			{
				// 不存在区间
				begin2 = n;
				end2 = n - 1;
			}
			else if (end2 >= n)
			{
				end2 = n - 1;
			}


			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 + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		//也可以整体归并完了再拷贝
		//memcpy(a, tmp, sizeof(int) * (n));

		rangeN *= 2;
	}


	free(tmp);
    tmp = NULL;
}

注意体会下面代码的含义

			if (end1 >= n)
			{
				end1 = n - 1;
				// 不存在区间
				begin2 = n;
				end2 = n - 1;
			}
			else if (begin2 >= n)
			{
				// 不存在区间
				begin2 = n;
				end2 = n - 1;
			}
			else if (end2 >= n)
			{
				end2 = n - 1;
			}
  • 当end1 > n 时,我们将end1修到最后一位,begin2和end2修为不存在的区间。
  • 当begin2 > n时,我们将begin2和end2修为不存在的区间。
  • 当end2 > n时,我们将end2修到最后一位。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Jayce..

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

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

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

打赏作者

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

抵扣说明:

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

余额充值