逆序对
Posted yhxcs
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了逆序对相关的知识,希望对你有一定的参考价值。
本文讨论的逆序对基于归并排序。
逆序对可以说是排序的入门问题,因为排序的本质就是消除逆序对,而一个长度为N的序列最大可含有N^2级别的逆序对,一种较为简单的方法是使用分治divide and conquer的思想来求解,类似于归并排序。
首先是将序列对半分成两段,序列的逆序对等于左半边的逆序对+右半边逆序对+左右两边共同构成的逆序对,这里其实也用到逆序对的一个小性质,就是在一定区间内无论做出任何改变,都不会影响外面元素和内部元素形成的逆序对的个数,改变的只是那个区间内的逆序对的个数。所以如果每次相邻元素比较交换,就只能至多减少一对逆序对,这也是为什么冒泡排序插入排序这类算法低效的原因。
在求左右两端的逆序对的时候,有些地方需要注意,比如序列{1, 2, 3, 4}中,如果按照上述步骤,分为两半,左边为{1, 2},右边为{3, 4},这样左右两边就是没有逆序对的;但是如果原始序列是{3, 4, 1, 2}那么分开成左右两半就是{3, 4}和{1, 2},这就有逆序对了,说这些看似是废话,实际上是想强调,左右两边的顺序很重要,在计算逆序对时,不要忽略这一点。
那么两边的逆序对如何求呢?就拿上面的{3, 4}和{1, 2}举例,这里把它合并,在合并的过程中来计算。首先设置两个指针i,j分别指向两个子序列第一个元素,然后按照归并排序的方式合并,只是要注意:凡是右半边元素插入到新数组中时,就要加上左边元素剩余的元素。在这里先插入1,此时就要加上左边集合中剩余的两个元素,然后插入2,再加上剩余的两个元素;接着后面插入3和4就不再需要加了。这是因为插入右边元素的时候,说明在比较中它是小于左边某个元素的值的,而右边元素处于右边,这就意味着右边元素比左边元素小,所以要加上前面集合中当前所剩的元素。用length_left - i来表示。
原理讲完,代码如下:
public static int count(int[] a, int left, int right) {
if (left == right)
return 0;
int mid = left + (right - left) / 2;
// 左边的逆序+右边的逆序,然后后面再求这两个的逆序
int tmp = count(a, left, mid) + count(a, mid + 1, right);
int i = left, j = mid + 1;
int k = 0;
int[] c = new int[right - left + 1];
// 相当于归并排序,只是中间步骤多了一步计数
// 这里的tmp+也是有顺序的,有数组前一半和后一半的区别
while (i <= mid && j <= right) {
if (a[i] < a[j]) {
// 这里不用计数
c[k++] = a[i++];
} else {
c[k++] = a[j++];
// 计数
tmp += mid - i + 1;//
}
}
// 补齐
if (i > mid) {
while (j <= right)
c[k++] = a[j++];
} else {
while (i <= mid)
c[k++] = a[i++];
}
// 拷贝到原数组
// 这里有错显然,最初写的是a[i] = c[i]
for (i = left; i <= right; i++)
a[i] = c[i - left];
return tmp;
}
其实这段代码执行后,数组就是有序的,因为其实就是归并排序加了点东西而已。。。
以上是关于逆序对的主要内容,如果未能解决你的问题,请参考以下文章