归并排序(递归,非递归实现)

本文介绍了归并排序的两种实现方式——递归和非递归。归并排序是一种基于分治策略的排序算法,其核心是将有序子序列合并。递归实现通过不断分解区间直至单元素,然后逐步归并;非递归实现则直接进行一一归并。两种方法的时间复杂度均为O(N*logN),但空间复杂度为O(N)。
摘要由CSDN通过智能技术生成

   归并排序是采用分治法的一个非常典型的应用。其基本思想是:将已有序的子序合并,从而得到完全有序的序列,即先使每个子序有序,再使子序列段间有序。

1.递归实现

基本思想:先使左区间有序再使右区间有序,然后归并左区间和右区间。

我们需要一直向下分解,直到每个区间只剩下一个元素时,此时该区间可以看成是有序的,然后再一一归并,两两归并,四四归并,,,,,使得整个序列有序。

此外我们还得创建一份与待排数组大小相同的空间,用于保存归并后的元素,然后再将其拷贝回元素组。(为何要创建这么一份空间?因为我们在归并两段序列时,无论是交换两个序列的元素还是将其中一个元素放到另一个序列中,都会产生错误。)

 代码如下:

void _MergeSort(int* arr, int* tmp, int begin, int end)
{
	//递归终止条件:序列中只剩下一个元素或序列不存在
	if (begin >= end)
		return;
	//使左右区间有序
	int mid = (begin + end) / 2;//中间元素下标
	_MergeSort(arr, tmp, begin, mid);//使左区间有序
	_MergeSort(arr, tmp, mid + 1, end);//使右区间有序
	//将左区间和右区间归并到tmp中:依次比较取最小的尾插
	int begin1 = begin, end1 = mid;//第一个区间中首尾元素的下标
	int begin2 = mid + 1, end2 = end;//第二个区间中首尾元素的下标
	int i = begin;//i是arr中的区间归并到对应的tmp中的区间的起始位置的下标
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (arr[begin1] <= arr[begin2])
			tmp[i++] = arr[begin1++];
		else
			tmp[i++] = arr[begin2++];
	}
	//将左区间或右区间中剩余的元素拷贝到tmp中
	while (begin1 <= end1)
		tmp[i++] = arr[begin1++];
	while (begin2 <= end2)
		tmp[i++] = arr[begin2++];
	//将tmp中归并的元素拷贝回arr中
	memcpy(arr + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
void MergeSort(int* arr, int n)
{
	//申请一份临时空间
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	//用子函数递归
	_MergeSort(arr, tmp, 0, n - 1);
	//释放掉申请的空间
	free(tmp);
	tmp = NULL;
}

 时间复杂度:O(N * logN),空间复杂度:O(N)

2.非递归实现

我们发现归并排序的递归实现,目的是将区间不断分解直到区间元素只剩下一个,然后进行一一归并,二二归并,四四归并,,,。

非递归实现正好与这种做法相反,它是直接一一归并,二二归并,四四归并,,,省去了分解的过程。

 但是该方法也需要注意三点:

归并最后两个区间时,

1.如果第一个区间有一部分超出数组范围,则不需要归并。

2.如果第一个区间在数组范围之内,但第二个区间全部元素超出数组范围,则不需要归并。

3.如果第一个区间在数组范围之内,但第二个区间有一部分元素超出数组范围,这时我们需要修改第二个区间的终止下标为n - 1;

代码如下:

void MergeSortNonR(int* arr, int n)
{
	//申请一份临时空间
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	int rangeN = 1;//一个元素和一个元素归并
	while (rangeN < n)//区间元素个数大于或等于数组元素个数时归并完毕,终止循环。
	{
		//归并两组区间元素
		int i = 0;//i是所要归并的两组元素中,第一组的第一个元素的下标。
		for (i = 0; i < n; i = i + rangeN)
		{
			int begin1 = i, end1 = i + rangeN - 1;//第一组起始元素下标,终止元素下标
			int begin2 = i + rangeN, end2 = i + 2 * rangeN - 1;//第二组起始元素下标,终止元素下标
			if (end1 >= n)//第一个区间元素个数不足rangeN个
				break;
			if (begin2 >= n)//第二个区间不存在
				break;
			if (end2 >= n)//第二个区间元素个数不足rangeN个
				end2 = n - 1;
			int j = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (arr[begin1] <= arr[begin2])
					tmp[j++] = arr[begin1];
				else
					tmp[j++] = arr[begin2];
			}
			while (begin1 <= end1)
				tmp[j++] = arr[begin1];
			while (begin2 <= end2)
				tmp[j++] = arr[begin2];
			memcpy(arr + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		rangeN *= 2;//2,2归并;4,4归并;8,8归并......
	}
	//释放申请的空间
	free(tmp);
	tmp = NULL;
}

时间复杂度:O(N * logN),空间复杂度:O(N)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值