归并排序算法的理解与实现(递归与非递归的两种实现)

目录

归并排序

代码实现

特性总结

概念与思路

概念

归并排序是建立在 归并操作 上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。

将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。

若将两个有序表合并成一个有序表,称为二路归并。

思路

思路由下面动图演示:

在这里插入图片描述

比较begin1与begin2处的元素,将小的放到tmp中,然后该下标位置继续向后走,循环往复直到一个数组全部放入tmp中,最后将另一个数组拷贝进tmp中

  • 实际实现需要先将数组逐渐分解再合并,可以使用递归实现

在这里插入图片描述

代码实现

递归实现

思路如上图

void _MergeSort(int* a, int left, int right, int* tmp)
{
	if (left >= right) // left 
		return;

	int mid = (left + right) / 2;
	// 递归排序 [left, mid] 和 [mid+1, right] 两数组
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid + 1, right, tmp);

	// a[left, mid] a[mid+1, right] 归并到tmp数组

	// 数组被分为[left, mid] [mid+1, right]
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int i = left; // tmp 的起始位置
	while (begin1 <= end1 && begin2 <= end2)
	{
		//左右两边哪个元素更小,就放到tmp中并读到下一位
		if (a[begin1] < a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}
	//出循环则证明某组元素全部插入到tmp
	//再将另一组元素拷贝进tmp中
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}

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

	// 最后将tmp数组拷贝回a
	for (int j = left; j <= right; ++j)
	{
		a[j] = tmp[j];
	}
}

void MergeSort(int* a, int n)
{
	//tmp动态开辟空间
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}

	_MergeSort(a, 0, n - 1, tmp);
	// 释放tmp
	free(tmp);
	tmp = NULL;
}

非递归实现

法一

归并排序非递归可以用栈和队列的方式实现,不妨使用循环尝试

思路如下:

在这里插入图片描述
在这里插入图片描述

当出现越界后,由于最后需要将tmp中的所有元素拷回a中,则需要进行元素修正;

比如当end1>=n,此时end=n-1,后续end1便可以拷贝到tmp中,最后拷贝tmp到a中也不会差错。

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

	//定义间隔
	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			//作为下标将元素存进tmp
			int index = i;
			// [i,i+gap-1] [i+gap,i+2*gap-1] - 用该下标实现分组
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;

			//处理越界问题
			//end1越界,[begin2, end2]不存在
			if (end1 >= n)
			{
				end1 = n - 1;
			}

			//[begin1, end1]存在 [begin2, end2]不存在
			if (begin2 >= n)
			{
				begin2 = n;
				end2 = n - 1;//不会进入下面的循环
			}

			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++];
				}
			}

			//将另一组拷贝给tmp
			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[index++] = a[begin2++];
			}
		}

		//把归并后的数据拷贝回原数组
		for (int j = 0; j < n; j++)
		{
			a[j] = tmp[j];
		}

		gap *= 2;//gap扩大一倍
	}

	free(tmp);
	tmp = NULL;
}
法二

当end1/begin2越界时,实际并没有必要继续归并(后面的所有元素都越界),break即可;

当end2越界时,直接修正即可(后面无元素)。

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

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

			//end1、begin2、end2都有可能越界
			// end1越界 或者 begin2 越界都不需要归并(后面元素均越界)
			if (end1 >= n || begin2 >= n)
			{
				break;
			}

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

			int index = i;
			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 j = i; j <= end2; ++j)
			{
				a[j] = tmp[j];
			}
		}

		gap *= 2;
	}

	free(tmp);
	tmp = NULL;
}

特性总结

  1. 归并的 缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度: O(NlogN)*
  3. 空间复杂度: O(N)
  4. 稳定性: 稳定
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值