逆序对

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;
}

其实这段代码执行后,数组就是有序的,因为其实就是归并排序加了点东西而已。。。

以上是关于逆序对的主要内容,如果未能解决你的问题,请参考以下文章

c++ 逆序对

第三次过程性考核

逆序对的求解逆序对个数问题

递归逆序的使用

逆序对

树状数组求逆序对