归并排序【超详细+代码】(递归+非递归)

常见排序

在这里插入图片描述

归并排序

1、基本思路:将已有序的子序合并,从而得到完全有序的序列,即先使每个子序有序,再使子序列段间有序。
2、过程

  1. 把数组从中间划分成两个子数组。
  2. 一直递归地把子数组划分成更小的数组,直到子数组里面只有一个元素。
  3. 依次按照递归的返回顺序,不断合并排好序的子数组,直到最后把整个数组顺序排好。
    在这里插入图片描述

3、时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

当序列分解到只有一个元素或是没有元素时,就可以认为是有序了,这时分解就结束了,开始合并:
在这里插入图片描述

递归实现

归并排序的递归过程十分类似于二叉树的后序遍历,先将区间分为左区间和右区间,划分到不能继续划分时,进行归并。
步骤:

  1. 创建一个临时数组,在临时数组中进行归并,防止归并时将原顺序打乱,整体归并完之后再将数据拷贝回原数组。
  2. 将区间划分为[begin , mid ] [mid+1,end]两段子区间,进行后序遍历递归,当划分到只有一个元素时返回。
  3. 进行归并。使用begin1和end1控制左区间,begin2和end2控制右区间,比较两个区间中的值将其插入到temp数组中,当其中一个区间归并结束时停止。
  4. 将未排完序的数组接着全放入temp数组中。
  5. 最后将temp中的值拷贝到原数组中。
递归实现归并代码:

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

	//将区间分为左右两半
	int mid = (begin + end) / 2;
	//[begin,end]--->[begin , mid ] [mid+1,end]
	//开始递归拆开
	_MergeSort(a, begin, mid, temp);
	_MergeSort(a, mid + 1, end, temp);

	//合并
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	//i要从相对位置起
	int i = begin;
	//如果两个区间有一个结束,就停止比较归并
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			temp[i++] = a[begin1++];
		}
		else
		{
			temp[i++] = a[begin2++];
		}
	}
	//使用两个while将未归并完的数组进行追加
	while (begin1 <= end1)
	{
		temp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		temp[i++] = a[begin2++];
	}
	//拷贝回去要使用相对位置
	memcpy(a + begin, temp + begin, (end - begin + 1) * sizeof(int));
}
void MergeSort(int* a, int n)
{
	int* temp = (int*)malloc(sizeof(int) * n);
	if (temp == NULL)
	{
		perror("malloc fail");
		return;
	}

	//使用一个子函数进行排序
	_MergeSort(a, 0, n - 1, temp);
	free(temp);
}

非递归实现

实现思路:

  1. 模拟最后一层递归中排列间距为1的子序列,例[0,0]和[1,1]……,即最后一层的归并,然后再进行间距为2的子序列,直到归并完成;
  2. 寻找规律推出左、右区间的公式: [i , i+gap-1] [i+gap,i+2*gap-1],然后就是正常的递归合并.
  3. i 的变化是i+=2*gap,表示每次跳过两个区间,进入下一段区间.
  4. 当gap每次*=2 , 表示进入了下一层递归 , 直到gap >= n 时停止.
  5. 将临时数组拷贝回原数组.
非递归实现归并代码:
//归并排序--非递归
void MergeSortNonR(int* a, int n)
{
	int* temp = (int*)malloc(sizeof(int) * n);
	if (temp == NULL)
	{
		perror("malloc fail");
		return;
	}
	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2*gap - 1;
			//[begin1,end1] [begin2,end2]   归并
			
			
			//如果第二组不存在,这一组就不用归并
			if (end1 >= n || begin2 >= n)
			{
				break;
			}

			//如果第二组的右边界越界,修正一下
			if (end2 >= n)
			{
				end2 = n - 1;
			}

			int index = i;
			//如果两个区间有一个结束,就停止比较归并
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					temp[index++] = a[begin1++];
				}
				else
				{
					temp[index++] = a[begin2++];
				}
			}
			//使用两个while将未归并完的数组进行追加
			while (begin1 <= end1)
			{
				temp[index++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				temp[index++] = a[begin2++];
			}
			//拷贝回去要使用相对位置
			memcpy(a + i, i + temp, (end2 - i + 1) * sizeof(int));
		}
		gap *= 2;
	}
}
  • 15
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值