【数据结构基础整理】排序--04:归并排序

0x01.关于归并排序

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

0x02.递归方式的归并排序

先看代码:

void MergeSort(SqList* L)//单独设立一个函数是为了方便子函数的递归调用
{
	MSort(L->r, L->r,1,L->length);
}

//归并排序递归函数
//SR是初始的顺序表,TR1是最终要排序得到的顺序表
//s是要归并的起始点,t是要归并的终点
void MSort(int* SR, int* TR1, int s, int t)
{
	int m;
	int TR2[MAXSIZE + 1];
	if (s == t)//最后分到只有一个,就把这个值给TR1数组
	{
		TR1[s] = SR[s];
	}
	else
	{
		m = (s + t) / 2;//将原来的顺序表平分,m为中间值
		MSort(SR, TR2, s, m);//将SR[s]到SR[m]的值归并为有序的序列TR2[s]到TR2[m]
		MSort(SR, TR2, m + 1, t);//将SR[m+1]到SR[t]的值归并为有序的序列TR2[m+1]到TR2[t]
		Merge(TR2, TR1, s, m, t);//将TR2[s]到TR2[m]的值和TR2[m+1]到TR2[t]的值归并为有序的序列TR1[s]到TR1[t]
	}
}

//真正起到归并排序的函数
//将SR[i]到SR[m]的值和SR[m+1]到SR[n]的值归并为有序的序列TR[i]到TR[n]
void Merge(int* SR, int* TR, int i, int m, int n)
{
	int j, k, l;
	//k用来控制需要得到的有序序列的下标
	//i用来控制序列SR[i]到SR[m]的下标
	//j用来控制序列SR[m+1]到SR[n]的下标
	//每一次循环将会往TR里面放一个值
	//循环的条件是两个部分的下标都没有达到它们序列的终点值
	//因为两个序列都分别是有序的,所以只需要依次拿出一个最小值进行比较,就可以得到最终的有序序列SR
	for (j = m + 1, k = i; i <= m && j < n; k++)
	{
		if (SR[i] < SR[j])
		{
			TR[k] = SR[i++];
		}
		else
		{
			TR[k] = SR[j++];
		}
	}
	if (i <= m)//如果归并后第一个序列还有剩余的元素,都放到TR末尾
	{
		for (l = 0; l <= m - i; l++)
		{
			TR[k + l] = SR[i + l];
		}
	}
	if (j <= n)//如果第二个序列还有剩余的元素,也都放到TR末尾
	{
		for (l = 0; l <= n - j; l++)
		{
			TR[k + l] = SR[j + l];
		}
	}
}

原理理解:

归并排序的原理就是不断的把一个大序列分为两个小序列,然后再分别把小序列排序后组合起来,最终得到一个有序的数组。

第二个函数,递归的函数,所完全的工作就是不断的把序列分小,再分别对小序列排序,一个序列,经过不断的二分,最终一定会得到一个只包含一个元素的序列,我们可以把这个只有一个元素得序列理解成有序序列,然后再对这些有序序列归并,对于一个递归的用途,我们首先要确定在什么时候返回,返回方式有两种,一种是最后分到只有一个元素的序列,或者是执行完Merge函数返回,这两种返回方式,其实都把这个小序列归并成了有序的序列,然后看第一步调用,是先处理序列的左半部分,再处理序列的右半部分,最后合在一起处理,最终得到了一个有序的序列。‘

Merge函数所完成的工作,就是整合两个有序序列,整合有序序列就有一个比较巧妙的地方了,因为都是有序的,所以只要依次拿出最小值来比较,每次把最小的放入有序序列里,最后再把剩下的全部放尾部就完成了整合的工作。

0x03.非递归方式的归并排序

代码如下:

//最终得到的有序序列在TR中
void MergeSort2(SqList* L)
{
	int* TR = (int*)malloc(L->length * sizeof(int));//因为长度不是常量,所以必须动态分配
	int k = 1;
	while (k < L->length)//从1开始,最小序列的长度会由1变为2变为4,这样不断的变成2的倍数
	{
		MergePass(L->r, TR, k, L->length);//两两整合间距为s的序列
		k *= 2;//序列长度加倍
		MergePass(TR, L->r, k, L->length);
		k *= 2;
	}
}


//每次整合SR中间距为s的子序列并两两归并到TR中
void MergePass(int* SR, int* TR, int s, int n)
{
	int i = 1;//用来记录每次整合的位置
	int j;
	while (i <= n - 2 * s + 1)//这个2*s的含义是两两整合的间距,所以n-2*s就是最后一个两两整合的位置,再加1就是下一个位置
	{
		//将SR中的SR[i]到SR[i+s-1]的值和SR[i+s]到SR[i+2*s-1]的值整合到TR中
		//i到i+s-1是相隔s个元素的区间,i+s到i+2*s-1是另一个区间
		Merge(SR, TR, i, i + s - 1, i + 2 * s - 1);
		i = i + 2 * s;//定位下一个两两整合的位置
	}
	//n-s+1是最后一个两两整合的另一个位置的下一个,如果上面循环后,i还在这之前,说明是最后两个序列了,最后再整合一次
	if (i < n - s + 1)
	{
		Merge(SR, TR, i, i + s - 1, n);
	}
	else//i在这个之后,就是只剩下单个的子序列了,直接把后面子序列的值放到TR末尾
	{
		for (j = i; j <= n; j++)
		{
			TR[j] = SR[j];
		}
	}
}

原理理解:

不管递归还是不递归,最终用到的归并排序的思想是一样的,在这个非递归中,最显眼的就是两两整合这个思想,在递归中,由于递归的反复调用,其实每一次调用的返回,都是已经整合过一次了的,也是两两整合,不过没有相同间距这个概念,在非递归的实现中,从首先的每次整合两个,然后再每次整合4个,这样不断的每次整合,最后整合成一个有序序列。

 

归并排序的总体的时间复杂度为O(n*longn)

 

  • 21
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ATFWUS

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

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

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

打赏作者

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

抵扣说明:

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

余额充值