算法导论——InversionNumbers

本文探讨了如何利用归并排序策略解决算法导论中关于逆序对的问题。通过将数组分成两半,分别计算子数组的逆序对,然后在合并过程中统计跨子数组的逆序对,实现了O(nlgn)的最坏情况运行时间。关键在于合并阶段,当left[i]>right[j]时,需要累计left[i]之后所有元素与right[j]的逆序对。

         这是算法导论第二章后面的思考题,问题描述:设A[1..n]是一个包含n个不同数的数组。如果在i<j的情况下,有A[i]>A[j],则(i,j)就称为A的一个逆序对(inversion)。例如 ( 2,3,8,6,1 ) 中包含5个逆序:(1,5),(2,5),(3,5),(3,4),(4,5).

        现在要求给出一个算法,能够用O(nlgn)的最坏情况运行时间,确定n个元素的任何排列中逆序对的数目。(提示:修改归并排序)


          由给出的提示,以及给出的时间复杂度O(nlgn)的要求,可以想到分治的策略进一步可以由主定理猜测,递归式为T(n)=2T(n/2)+O(n)。其中T(n/2)表示子问题的规模,O(n)是合并子问题的开销。

        和归并排序是一个框架,大致如下:

        (1)将数组A分成两个子数组;

        (2)分别求两个子数组中的逆序;

        (3)将子数组合并起来,求整体的逆序;


         要注意的是,在第二步求子数组逆序时,两个数组之间的逆序没有考虑,这需要在合并的时候进行处理。若子数组left中的第i个大于子数组right中的第j个,这两个数构成一个逆序。合并子数组时,子数组已经排好序,可知left中下标大于i的元素都大于right[j]。

         由归并操作的流程,涉及left[i]和right[j]的归并操作有且只有一次。合并之后,将按照升序一同出现在后续操作的同一个子数组中。因此,每次遇到left[i]>right[j]的时候,要将left[i]之后元素和right[j]构成的逆序一起进行统计。

       代码如下:

public class CountInversion {

	static int countInver(int[] a,int l,int r)
	{
		int inversion=0;
		if(l<r){
			int mid=(l+r)/2;
			inversion+=countInver(a,l,mid);
			inversion+=countInver(a,mid+1,r);
			inversion+=mergeInver(a,l,mid,r);
		}
		return inversion;
	}
		
	static int mergeInver(int[] a,int l,int mid,int r)
	{
		int n1=mid-l+1;
		int n2=r-mid;
		int[] left=new int[n1];
		int[] right=new int[n2];
		for(int i=0;i<n1;i++)
			left[i]=a[l+i];
		for(int j=0;j<n2;j++)
			right[j]=a[mid+1+j];
		
		int inversion=0;
		
		int i=0,j=0,k=l;
		while(i<n1&&j<n2)
		{
			
			if(left[i]<right[j])
			{
				a[k++]=left[i++];
			}
			else
			if(left[i]>right[j])
			{
				inversion+=n1-i;    //[i~n1-1]的元素都大于right[j]
				a[k++]=right[j++];
			}
			else
			{
				a[k++]=right[j++];
				a[k++]=left[i++];
			}
		}
		while(i<n1)
			a[k++]=left[i++];
		while(j<n2)
			a[k++]=right[j++];
		return  inversion;
	}
	public static void main(String[] args)
	{
			int[] test1=new int[]{1,2,3,4,5,6,7};
			int[] test2=new int[]{6,5,4,3,2,1,0};
			int[] test3=new int[]{5,6,4,3,7,1,2};
			int ans1=countInver(test1,0,test1.length-1);
			int ans2=countInver(test2,0,test2.length-1);
			int ans3=countInver(test3,0,test3.length-1);
			System.out.println(ans1);
			System.out.println(ans2);
			System.out.println(ans3);
	}
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值