归并排序及延伸(找出数组中的逆序对)

//
//归并排序,时间复杂度nlog(n),空间复杂度o(n),稳定排序
先把数字折半划分递归下去,直到每组的个数为1,
///然后相邻的两组各自排序,然后合并。递归回来合并
///较大数组。log(n)层完成排序,每层比较n次。
int dosort(int *pdata,int *pcopy,int ibegin,int iend);
int sortandfind(int *pdata ,int n)
{
	if(NULL == pdata)
		return 0;
	int *pcopy = new int[n];
	memcpy(pcopy,pdata,sizeof(int)*n);
	int inum = dosort(pdata,pcopy,0,n-1);
	printf("num of %d group\n",inum);
	for(int i =0 ;i<n;i++)
		printf("\n    %d     ",pcopy[i]);
	delete []pcopy;
	pcopy = NULL;
}
int dosort(int *pdata,int *pcopy,int ibegin,int iend)
{
	if(NULL == pdata || NULL == pcopy)
		return 0;
	if(ibegin == iend)//递归结束条件
		return 0;
	int imid = (ibegin + iend)/2;//中间下标
	//将进来的数据分成两组递归调用
	int ileft = dosort(pcopy,pdata,ibegin,imid);//请注意这边的第二个参数,pdata出来后是已经排序的序列,用来下面的比较
	int iright = dosort(pcopy,pdata,imid+1,iend);
	int ibegin1 = ibegin;// 第一组开始的下标
	int iend1 = imid;//第一组的末尾下标
	int ibegin2 = imid + 1;
	int iend2 = iend;
	int icopyindex = iend;//从最后一位往前填充
	int isum = 0;//逆序对总算
	while(iend1 >= ibegin1 && iend2 >= ibegin2)
	{
		if(pdata[iend1] > pdata[iend2])// 两组已排序,大于第二组的最后一个,则逆序对为第二组的个数
		{
			pcopy[icopyindex --] = pdata[iend1--];
			isum += iend2 - ibegin2 +1;
		}
		else
		{
			pcopy[icopyindex--] = pdata[iend2--];
		}
	}
	while(iend1 >= ibegin1)
	{
		pcopy[icopyindex--] = pdata[iend1--];
	}
	while(iend2 >= ibegin2)
	{
		pcopy[icopyindex--] = pdata[iend2--];
	}
	return ileft + iright + isum;
	
}

void main()
{
	int a[] = {9,2,7,8};
	sortandfind(a,4);

}

给定一个数组,找出这个数组包含的逆序对

比如{9,2,7,8},共有9,2;9,7;9,8三对逆序对


<think>嗯,我现在需要理解如何用归并排序来计算数组中的逆序对数量。归并排序本身是分治算法,分而治之,分成小数组然后合并。逆序对指的是前面的数比后面的数大的情况,比如数组[3,1,2]中的逆序对(3,1)(3,2),所以数量是2。现在问题是如何在归并的过程中统计这些逆序对。 首先,归并排序在合并两个已排序的子数组时,可以比较左右两边的元素。当左边的元素比右边的大时,说明存在逆序对。比如左边是[4,5],右边是[1,3],当合并时,4比1大,那么这时候左边的所有剩余元素(4和5)都会和右边的1形成逆序对。所以这时候逆序对的数量应该是左边剩余元素的个数。 那具体怎么实现呢?比如在合并过程中,当左半部分的当前元素大于右半部分的当前元素时,除了将右元素放入合并后的数组,还要累加逆序对的数量。这里可能需要一个计数器,每次左元素大于右元素时,计数器加上左半部分剩余元素的个数。 比如,假设左半部分是L,右半部分是R,当前左指针i,右指针j。当L[i] > R[j]时,此时逆序对的数量应该是mid - i + 1,其中mid是左半部分的结束位置。因为左半部分已经排序,所以从i到mid的所有元素都会比R[j]大,形成逆序对。 不过这里需要注意mid的计算是否正确。比如在归并排序中,通常是将数组分成左右两部分,左半部分的结束位置是mid,而右半部分从mid+1开始。所以当L[i] > R[j]时,左半部分剩余的元素数量应该是左半部分的长度减去i的当前位置,即比如左半部分的长度是mid - left + 1,当前i的位置相对于左半部分来说是从0开始的,或者要看具体的实现方式。 可能需要具体例子来验证。例如,数组为[3,1,2],拆分到最底层的时候,左右可能分别是[3]和[1],合并的时候,3>1,所以逆序对加1。接着合并后的左边是[1,3],然后再和右边的[2]合并。此时比较左半部分的3和右半部分的2,3>2,所以逆序对加1。总共有2个逆序对,正确。 那在代码实现时,归并排序的合并函数需要返回逆序对的数量,并且在递归过程中累加左右子数组逆序对以及合并时的逆序对。具体来说,递归调用左半部分返回逆序数,右半部分同样,再加上合并时产生的逆序数。 参考引用中的代码结构,可能是在merge函数中统计逆序数。例如,引用[2]中提到merge函数用于合并两个有序子数组并计算逆序对数。那在merge的过程中,当左边的元素大于右边的元素时,说明存在逆序对,此时可以计算左边的剩余元素数量,并将这个数值加到逆序对的总数中。 例如,假设在合并两个子数组left和right时,i是left的指针,j是right的指针。当left[i] > right[j]时,逆序对的数量增加mid - i + 1(这里的mid可能是指左半部分的结束索引)。比如,如果左半部分是从索引0到mid,那么当i是当前左指针的位置,剩下的元素数目是mid - i + 1。因此,每当遇到右半部分的元素较小的情况,就加上左半部分剩余的元素数目。 因此,在归并排序的合并过程中,通过这种方式统计逆序对的数量。总逆序对的数量等于左半部分的逆序对数、右半部分的逆序对数,以及合并过程中产生的逆序对数之和。 现在需要确保代码正确实现这一点。比如,在Python中,归并排序的递归函数应该返回逆序对的数量,而合并函数在合并过程中统计新增的逆序数,并返回这个数值,同时完成合并后的数组。 例如,假设merge_sort函数返回排序后的数组逆序对的总数。merge函数在合并两个有序数组时,不仅合并,还统计逆序对数。当处理left和right数组时,每次从left和right的开头取元素,如果left[i] > right[j],那么说明有逆序对,并且此时left中i之后的所有元素都会比right[j]大,因此逆序对数目增加len(left) - i,因为left数组已经排过序,剩下的元素数目是len(left) - i。 比如,left数组是[3,4,5],right是[1,2],当i=0时,left[0]=3 > right[0]=1,此时逆序对数目增加3(因为left数组有3个元素,从i=0开始,剩下的元素数目是3-0=3)。然后j移动,继续比较,直到所有元素合并完成。 这样在每次合并时统计逆序对,累加到总数中。最终得到整个数组逆序对数目。 现在需要将这个逻辑转化为代码。例如,在Python中,归并排序的递归函数分割数组为左右两部分,分别调用自身得到排序后的左右数组和对应的逆序数,然后合并,并返回合并后的数组以及总逆序数(左+右+合并时的数目)。 所以,函数的大致结构可能是这样的: def merge_sort(arr): if len(arr) <= 1: return arr, 0 mid = len(arr) // 2 left, count_left = merge_sort(arr[:mid]) right, count_right = merge_sort(arr[mid:]) merged, count_merge = merge(left, right) total = count_left + count_right + count_merge return merged, total 然后merge函数实现合并,并统计逆序数: def merge(left, right): merged = [] i = j = 0 count = 0 while i < len(left) and j < len(right): if left[i] <= right[j]: merged.append(left[i]) i += 1 else: merged.append(right[j]) count += len(left) - i j += 1 merged += left[i:] merged += right[j:] return merged, count 这样,当left[i] > right[j]时,将right[j]添加到merged,并增加count的值,此时count += len(left) - i,因为left数组中i之后的元素都比right[j]大,所以每个都会形成一个逆序对。 例如,测试数组[3,1,2],调用merge_sort,返回排序后的数组和总逆序数。分解过程: 第一次分割为[3]和[1,2]。左边的逆序数是0,右边的merge_sort([1,2])分割为[1]和[2],合并时没有逆序,所以右边的总逆序数是0+0+0=0。然后合并左边[3]和右边[1,2]时,3>1,此时count += 1(因为len(left)=1,i=0,1-0=1)。接着right[j]是2,比较3>2,count += 1(此时i=0,len(left)-i=1)。所以合并时的count是2。总逆序数是左边0+右边0 +合并时2=2,正确。 所以这样的代码是正确的。因此,使用归并排序时,在合并阶段统计右元素被提前放入合并数组时,左半部分剩余元素的个数,就可以得到逆序对的数量。</think>### 使用归并排序计算逆序对数量的方法 在归并排序的合并阶段统计逆序对数量是经典的分治策略应用,具体步骤如下: #### 1. **分治拆分** 将数组递归拆分为左右两个子数组,直到子数组长度为1。此时每个子数组自然有序,且无逆序对。 #### 2. **合并与统计** 合并两个有序子数组时,若左子数组元素 $left[i]$ 大于右子数组元素 $right[j]$,则左子数组中 $i$ 之后的所有元素均与 $right[j]$ 构成逆序对。此时逆序对数量增加 $len(left) - i$(左子数组剩余元素数量)[^4]。 #### 3. **递归累加** 总逆序对数 = 左子数组逆序对数 + 右子数组逆序对数 + 合并阶段新增的逆序对数[^3]。 ```python def merge_sort(arr): if len(arr) <= 1: return arr, 0 mid = len(arr) // 2 left, count_left = merge_sort(arr[:mid]) right, count_right = merge_sort(arr[mid:]) merged, count_merge = merge(left, right) return merged, count_left + count_right + count_merge def merge(left, right): merged = [] i = j = 0 count = 0 while i < len(left) and j < len(right): if left[i] <= right[j]: merged.append(left[i]) i += 1 else: merged.append(right[j]) count += len(left) - i # 统计逆序对 j += 1 merged += left[i:] merged += right[j:] return merged, count ``` #### **示例分析** 对于数组 $[3,1,2]$,合并过程如下: 1. 拆分至最小单元:$[3]$、$[1]$、$[2]$。 2. 合并 $[1]$ 和 $[2]$:无逆序对。 3. 合并 $[3]$ 和 $[1,2]$: - $3 > 1$,逆序对数增加 $1$(左子数组剩余 $1$ 个元素)。 - $3 > 2$,逆序对数再增加 $1$。 - 总计 $2$ 个逆序对[^4]。 #### **复杂度分析** - **时间复杂度**:$O(n \log n)$,与归并排序一致[^1]。 - **空间复杂度**:$O(n)$,用于合并时的临时数组[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值