归并排序算法之错误修正

本文探讨了归并排序中的二路归并思想,通过一个实例展示了算法过程,并指出了一处可能的错误:在MSort函数中直接对S进行排序和归并。修正方法是使用临时变量进行排序,避免原始数据被修改。最终展示了解决问题后的排序结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

归并排序:将两个或者两个以上的有序表合并成一个新的有序表,二路归并就是一组数组中前后相邻的两个有序序列归并为一个有序序列

二路归并的核心思想:假设有n个序列,然后两两归并,得到[n/2]个长度为2或者1的子序列;然后再两两归并,……,直至得到一个长度为n的有序序列为止

看下面2路归并算法的实例:

int MSort(MergeType S, MergeType *pT, int nStart, int nEnd)
{
	int nMidPos = 0;
	if ( !S.elem || nStart > nEnd)
	{
		return -1;
	}
	
	if (nStart == nEnd)
	{
		pT->elem[nStart] = S.elem[nStart];
		return 0;
	}

	nMidPos = (nStart + nEnd) / 2;
	MSort(S, pT, nStart, nMidPos);
	MSort(S, pT, nMidPos + 1, nEnd);
	Merge(S, pT, nStart, nMidPos, nEnd);

	return 0;
}

上面的步骤是将数据对半分割,直至只有一个数据,然后再进行归并,这是个递归的操作,看一下如何进行归并操作:

/*S:原数据列表,pT:排序好的数据列表,其他数据和函数定义请参考:冒泡算法的改进*/

int Merge(MergeType S, MergeType *pT, int nStart, int nPos, int nEnd)
{
	int nSi = nStart, nSj = nPos + 1;
	int nTi = nStart;

	for ( ; nSi <= nPos && nSj <= nEnd; )
	{   //这里出现错误,因为s与pt是同一个变量,改变一个会影响另一个
		pT->elem[nTi++] = (S.elem[nSi] <= S.elem[nSj])?S.elem[nSi++]:S.elem[nSj++];
	}

	//剩余的数据
	while (nSi <= nPos)
	{
		pT->elem[nTi++] = S.elem[nSi++];
	}

	while (nSj <= nEnd)
	{
		pT->elem[nTi++] = S.elem[nSj++];
	}
	return 0;
}
归并的时候,最初是先归并两个数组(只有一个数据),归并为一个数组,当然还有两个不同的数组(数组维数n>=2)归并为一个数组。但是还会有一些情况:

假设一个数组为(0,10,24) ,另一个为(32,45),这时候(0,10,24)会被填充进数组T中,但是另一个数组(32,45)还没有插入数组T,后面两个while循环,只需将剩余插入数组T尾部,这时候不用进行插入排序比较,一定是插在数组的尾部,for循环比较已经知道剩余数据的位置了。

表面上看,上面的算法是正确的,其实在归并的时候就有问题,已在注释中说明地方,请思考如何修正错误?错误不一定就是那里!

主要是MSort的时候,首先将S排序为pT:MSort(S, &Temp, nStart, nMidPos);然后再将S归并为pT,其实这本身就有问题,其实应该将pT归并为……,PT?这里就有个疑问,是否是pT呢,按理说pT这时候还没有排好序,无所谓,其实是有所谓的,在后面的排序过程中,会修改有些数据的位置,就是注释的地方,所以这里一定要设置一个临时变量,修改如下:

int MSort(SortType &S, SortType *pT, int nStart, int nEnd)
{
	int nMidPos = 0;
	SortType Temp;
	Temp.elem	= (int*)malloc(sizeof(int)*S.len);	
	Temp.len	= S.len;
	Temp.size	= S.len;
	memset(Temp.elem, 0, S.len*sizeof(int));

	if ( !S.elem || nStart > nEnd)
	{
		return -1;
	}
	
	if (nStart == nEnd)
	{
		pT->elem[nStart] = S.elem[nStart];
		return 0;
	}

	nMidPos = (nStart + nEnd) / 2;
	MSort(S, &Temp, nStart, nMidPos);
	MSort(S, &Temp, nMidPos + 1, nEnd);
	Merge(Temp, pT, nStart, nMidPos, nEnd);
	free(Temp.elem);
	Temp.elem = NULL;

	return 0;
},
这里我将MergeType类型 修改为SortType,便于记忆,其中的形参MergeType S已经修改为SortType &S,使用了引用形参,这里主要是为了减少形参的复制,且不修改原来列表,其实这里类型应该定义为const SortType &S,但是要修改多处,减少麻烦。如下测试实例:

	SortType pList;
	SortType pT;  

	pList.elem = (int*)malloc(sizeof(int)*LISTLEN);
	pList.len  = LISTLEN;
	pList.size = LISTLEN;
	ScanfList(&pList);

#if 1
	pT.elem	= (int*)malloc(sizeof(int)*LISTLEN);	
	pT.len	= 10;
	pT.size	= LISTLEN;
	memset(pT.elem, 0, LISTLEN*sizeof(int));
#endif

	/*归并排序*/
	//MSort(pList, &pT, 0, LISTLEN);
	MSort(pList, &pT, 0, pList.len-1);
	
	PrintList(&pList);
	PrintList(&pT);
	
	free(pList.elem);
	free(pT.elem);
	pList.elem = NULL;
	pT.elem = NULL;
测试结果如下:

--- SortTest ---
Old List        : 0 10 18 24 28 30 30 28 24 18  
Sort List       : 0 10 18 24 28 30 30 28 24 18
Sort List       : 0 10 18 18 24 24 28 28 30 30

第一行为pList原始数据,第二行为排序后pList中的数据,第三行排序后pT中的数据,可以看出应经排序好了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值