归并排序应用之一:统计序列中逆序对的个数

一、归并排序

2-路归并的实现

归并排序中“归并”的含义是将两个或两个以上的有序表组合成一个新的有序表。实现归并的的方法很简单。我这里实现的是2-路归并,多路归并的道理是一样的。代码如下:

public static void merge(int[] a, int start, int mid, int end) {// 将两段有序序列进行合并
		int[] temp = new int[end - start + 1];// 辅助数组
		int i = start, j = mid + 1, k = 0;
		while (i <= mid && j <= end) {
			if (a[i] <= a[j]) {
				temp[k++] = a[i++];
			} else {
				temp[k++] = a[j++];
			}
		}
		while (i <= mid) {
			temp[k++] = a[i++];
		}
		while (j <= end) {
			temp[k++] = a[j++];
		}
		for (int u = 0; u < temp.length; ++u) {// 辅助数组得到的排序还原到a
			a[start++] = temp[u];
		}
	}

归并排序的实现

归并排序的思想是递归分治,具体来说就是将一个规模比较大的问题(通常来说这个规模较大的问题我们没办法直接解决)通过多次划分成为若干个规模很小的问题,而这些规模较小的问题我们又能够直接解决它,并且解决若干个小规模问题的方法和解决规模较大问题的方法又是一样的。可能我这样说比较绕、不直接明了,就拿归并排序来说吧来说吧:
2-路归并
原待排序列我们没办法直接一下得到它的有序序列,我们可以通过一次次划分,直到将原序列划分成若干个长度为1的序列,长度为1的序列自然是有序的序列。之后使用二路归并merge()方法就可以将有序的子序列两两归并成为有序的,这样多次merge之后,原序列就自然有序啦!实现划分和归并的递归形式的代码也很简洁明了,聪明的你一看便懂:

public static void mergeSort(int[] a, int start, int end) {// 将a[start...end]序列进行归并排序
		if (start < end) {
			mergeSort(a, start, (start + end) / 2);
			mergeSort(a, (start + end) / 2 + 1, end);
			merge(a, start, (start + end) / 2, end);
		}

二、逆序对的统计

什么是逆序对?

对于数组a来说,其中两个元素a[i]和a[j]若满足i<j并且a[i]>a[j],那么这两个元素就是一个逆序对。

统计逆序对的思想

还是拿数组a来说,在上面的代码要执行merge()方法的时候,a[start, (start + end) / 2]和a[ (start + end) / 2+1,end]两段序列都是分别有序的,且前段的下标i和后段下标j必然满足i<j,求这两个有序序列中的逆序对是不是就很简单了。就是找出a[i]>a[j]有多少对就可以了,请看代码:

    public static int num = 0;// 静态变量
/**
	 * left[start1,end1],right[start2,end2]
	 * left的下标必然是小于right的下标,所以求逆序对就是统计left[]>rigt[]的个数
	 */
	public static void count(int[] left, int[] right, int start1, int end1,
			int start2, int end2) {
		for (int i = start1; i <= end1; ++i) {
			for (int j = start2; j <= end2; ++j) {
				if (left[i] > right[j]) {
					num++;
				}
			}
		}
	}

我们定义了一个静态的全局变量,对于每次count()函数的执行,只要有满足条件的逆序对,num就会自加1。最后执行结束得到的num自然就是逆序对的个数了。这里还有疑问的话可以对照着前面的那张图片看一看你就会恍然大悟了。

什么时候统计逆序对?

到这里差不多这个算法就已经实现的差不多了,还差一步就是什么时候统计逆序对。上一步提到统计逆序对是在得到两个子有序列的时候,也就是在对两个有序列进行merge()之前。显然只需用对mergeSort()添加一行代码即可:

public static void mergeSort(int[] a, int start, int end) {// 将a[start...end]序列进行归并排序
		if (start < end) {
			mergeSort(a, start, (start + end) / 2);
			mergeSort(a, (start + end) / 2 + 1, end);
			/** 合并之前统计逆序对 */
			count(a, a, start, (start + end) / 2, (start + end) / 2 + 1, end);
			merge(a, start, (start + end) / 2, end);
		}
	}

测试

下面就来测试一下上面的代码,写个主函数:

	public static void main(String args[]) {
		int[] a = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
		mergeSort(a, 0, a.length - 1);
		System.out.print("2-路归并结果:");
		for (int i = 0; i < a.length; ++i) {
			System.out.print(a[i] + " ");
		}
		System.out.println();// 换行
		System.out.print("原序列中逆序对的个数:");
		System.out.print(num);
	}

运行得到:
在这里插入图片描述
为了便于验证,我将原序列设置为逆序的。理论上讲n个逆序的序列有序对有n(n-1)/2个(很容易推导出来的),那么10个元素的逆序序列的逆序对就是45个。所以该算法经过验证是正确的。
就这样了吧,还请大佬们多多指教!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值