【八大排序】二路归并排序(非递归 + 递归)

目录

前言

一、排序规则

两两融合规则

二、代码实现

1.非递归实现

2.递归实现

总结


前言

归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并

这些子问题解决的方法都是类似的,解决掉这些小的问题之后,归并子问题的结果,就得到了“大”问题的解。

时间复杂度:平均情况O(nlogn);最好情况(nlogn);最坏情况O(nlogn)
空间复杂度:O(n)
稳定性:       稳定
基本思想:首先将所有数据看做一个个单独的个体(单独的个体有序),然后两两融合,直到所有的数据都在同一个组内。


一、排序规则

1.一开始就将所有值都看作单独的个体,这个时候,从另一个角度来讲,他们独自有序。

2.两个部分两个部分融合(保证融合后有序且稳定)。

3.重复第二步,直到所有的数据都在同一个队列中。

两两融合规则

左手右手各拿起左右两部分中第一个值(也即是最小值),进行比较,谁小,谁先放置(递增序列),如果相等则先放置左边,这是为了保证原有的顺序性。

此处的"放置",我们需要借助辅助空间,也就是另一个数组来帮助实现。

我们依旧设立一组数来帮助理解:{21  12  7  9  11  98  56  88  66  68};

下面图示中数字下的“横线”,表示分组;

上图只为表现归并思想,省去了辅助空间。

即不断进行两两融合,直到最后(第四次),数据全部存在于一个队列中,则排序结束。

上述为非递归式二路归并排序,而递归式仅是将整个队列规模层层降低,变成一个个单独的元素后再进行上述操作。

因非递归式较易理解,我们在此仅进行非递归分析。

二、代码实现

1.非递归实现

因为代码较长,且难以理解,在此简单分析一下:
首先,MergeSort函数中,给出了数据每次进行归并时的规模,并调用Merge。

Merge函数中,首先构造了辅助空间,其大小与原数据队列相同。

而后我们确定了,左右数据队列的范围。

左手的左边界:low1 = 0;   //即为数组最左边元素的位置

左手的右边界:high1 = low1 + gap - 1;  //右边界的确定与问题规模有关,若规模为1,则high = 0 + 1 - 1 = 0;即左手抓住一个值,以此类推。

右手的左边界:low2 = high + 1;   //因为左右两个数据队列相邻

右手的右边界:low2 + gap - 1 < len ? low2 + gap - 1 : len - 1;  //此处与左边空间规则相同,但需要考虑合法性问题,即右边界不可能超过数组。

在while中我们完成了将归并融合的值放入辅助空间的过程。

其余问题皆在注释中进行分析,逐句理解即可。

void Merge(int arr[], int len, int gap)//gap:代表合并的两个组中单独的组个数是多少(几几合并的几)
{
	//第一步,先整出来辅助空间brr
	int* brr = (int*)malloc(len * sizeof(int));
	assert(brr != NULL);
	int i = 0; //i是辅助空间brr的下标   代表下一个值插入位置

	int low1 = 0;//左手的左边界
	int high1 = low1 + gap - 1;//左手的右边界
	int low2 = high1 + 1;//右手的左边界
	int high2 = low2 + gap - 1 < len ? low2 + gap - 1 : len - 1;//右手的右边界

	while (low2 <= high2)//确保左手和右手都抓到值(左右手都不空),只要右手还有值,左手肯定抓满值了
	{
        //代表着,融合比较过程中,左右手还不空
        //有一个空了,就不用比较了,直接向brr挪动另一个手中的数据即可
		while (low1 <= high1 && low2 <= high2)
		{
			if (arr[low1] <= arr[low2])//如果左手的第一个值 <= 右手的第一个值
			{
				brr[i++] = arr[low1++];//将左手的第一个值放到brr中
			}
			else//如果左手的第一个值 > 右手的第一个值
			{
				brr[i++] = arr[low2++];//将右手的第一个值放到brr中
			}
		}

		//当内层while循环退出,代表着肯定有一个手空了
		//这里可以不用判断到底哪个手空了,简单粗暴一点(两个数组都调用一下,将其剩余数据向brr挪动)
		while (low1 <= high1)//将左手剩余数据向brr中挪动
		{
			brr[i++] = arr[low1++];
		}
		while (low2 <= high2)//将右手剩余数据向brr中挪动
		{
			brr[i++] = arr[low2++];
		}

		//左手右手数据处理完毕,平移,让左手右手再抓两个组
		low1 = high2 + 1;
		high1 = low1 + gap - 1;
		low2 = high1 + 1;
		high2 = low2 + gap - 1 < len ? low2 + gap - 1 : len - 1;
	}

	//此时,代表着处理到最后一组了:这时候退出有两种情况:1.左右手都没有抓到值 2.左手不空,右手空 
	//会不会存在第三种情况:左手空,右手不空?  这种情况不存在
	//那么怎么处理上面两种可以出现的情况:第一种皆大欢喜,不用管了
	//                                    第二种,需要处理一下

	//处理2:左手的值放到brr里,右手肯定为空 不用管
	while (low1 < len)//将左手剩余数据向brr中挪动  //这里是low1<len
	{
		brr[i++] = arr[low1++];
	}

	//最后一步,brr里放满了值, 这时只需要将brr的值同步到arr里即可
	for (int k = 0; k < len; k++)
	{
		arr[k] = brr[k];
	}

	free(brr);//释放辅助空间brr
	brr = NULL;

}
void MergeSort(int* arr, int len)
{
    assert(arr != NULL);
	//i代表几几合并的几   当i小于len时,才有必要进行一次融合
	//当i大于等于len时,没有必要进行一次融合,因为这时所有的值都在同一个组内
	for (int i = 1; i < len; i *= 2) //logn
	{
		Merge(arr, len, i);
	}
}

2.递归实现

递归实现中,Merge函数的实现与非递归基本相同,代码量还有些许减少。

主要问题在于理解MergePass中递归思想,其将整个规模层层剥离减少,到达非递归中的初始,即一个元素为一个队列时开始回归,而后进行与非递归相同操作。

需要注意的是,递归式中的辅助空间则是在外部申请,因为需要防止在递归中同时申请大量空间的情况。

void Copy(int* src, int* dest, int left, int right)
{
	while (left <= right)
	{
		src[left] = dest[left];
		left++;
	}
}
void Merge(int* src, int* dest, int left, int m, int right)
{
	int i = left; //i是辅助空间brr的下标   代表下一个值插入位置
	int j = m + 1;
	int k = left;
	while (i <= m && j <= right)//代表着,融合比较过程中,左右手还不空(有一个空了,就不用比较了,直接向brr挪动另一个手中的数据即可)
	{
		if (src[i] <= src[j])//如果左手的第一个值 <= 右手的第一个值
		{
			dest[k++] = src[i++];//将左手的第一个值放到brr中
		}
		else//如果左手的第一个值 > 右手的第一个值
		{
			dest[k++] = src[j++];//将右手的第一个值放到brr中
		}
	}

	//当内层while循环退出,代表着肯定有一个手空了
	//这里可以不用判断到底哪个手空了,简单粗暴一点(两个数组都调用一下,将其剩余数据向brr挪动)
	while (i <= m)//将左手剩余数据向brr中挪动
	{
		dest[k++] = src[i++];
	}
	while (j <= right)//将右手剩余数据向brr中挪动
	{
		dest[k++] = src[j++];
	}
}
void MergePass(int* src, int* dest, int left, int right)
{
	if (left < right)
	{
		int mid = (left + right) / 2;
		MergePass(src, dest, left, mid);
		MergePass(src, dest, mid + 1, right);

		Merge(src, dest, left, mid, right);
		Copy(src, dest, left, right);
	}
}
void MergeSort(int* arr, int len)
{
	if (len < 2 || arr == NULL)
	{
		return;
	}
	int* tmp = (int*)malloc(sizeof(int) * len);
	MergePass(arr, tmp, 0, len - 1);
	free(tmp);
}

总结

以上就是今天学习的内容,本文介绍了归并排序的排序规则和代码实现,归并排序速度仅次于快速排序,为稳定排序算法,一般用于对总体无序,但是各子项相对有序的数列。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值