算法之逆序对

算法之逆序对

逆序对问题

​ 假设A[1..n]是一个有n个不同数的数组。若iA[j],则对偶(i, j)称为A的一个逆序对(inversion)。

  1. 列出数组{2, 3, 8, 6, 1}的5个逆序对
  2. 由集合{1, 2, ..., n}中的元素构成的什么数组具有最多的逆序对?它有多少逆序对?
  3. 插入排序的运行时间与输入数组中的逆序对的数量有什么关系?
  4. 给出一个求在n个元素的任何排列中逆序对数量的算法,最坏时间复杂度为: \(\Theta\)(nlgn)
  5. 根据定义易得,逆序对为:(2, 1)、(3, 1)、(8, 6)、(8, 1)、(6, 1)
  6. 当数组元素恰好为n个元素从大到小排列时,拥有最多的逆序对。此时逆序对为: (n - 1) + (n -2) + (n - 3) + ... + 1 = n(n - 1) / 2
  7. 根据插入排序的实现过程,不难得出每次从未排序数组选择一个值arr[j]插入已排序数组的时候,所需要的移动次数,即为以arr[j]为右侧值的逆序对的个数。这个特性也可以设计出一个时间复杂度为: \(\Theta\)(\(n^2\))的算法。当然这种指数级别复杂度的算法我们直接PASS
  8. 不难想到\(\Theta\)(nlgn)算法复杂度的归并排序。其实归并排序在分治的时候不会改变逆序对的个数。只有在合并的时候,才会因为逆序对的出现导致右侧提前被合入原数组。其实修改点主要在两个方面:
  • 声明一个全局变量用来存储总的次数
  • 在右侧提前被合入原数组的时候对总次数进行累加(只需要改归并排序的merge方法, 归并排序请参照http://www.cnblogs.com/Kidezyq/p/8379267.html

具体源代码如下:

private static int count;

private static void merge(int[] arr, int startIndex, int midIndex, int endIndex) {
    /**
     * 合并策略: 
     * 1. 新建两个数组,分别存取左半部分排好序的数组和右半部分排好序的数组
     * 2. 分别从左右两个数组最开始下标开始遍历,选取较小的依次放入原数组对应位置
     * 3. 最终如果左右数组中有一个已经遍历完成,另一个数组所剩的元素直接放入元素组后面部分即可
     */
    // STEP1
    int[] leftArr = new int[midIndex - startIndex];
    int[] rightArr = new int[endIndex - midIndex];
    System.arraycopy(arr, startIndex, leftArr, 0, leftArr.length);
    System.arraycopy(arr, midIndex, rightArr, 0, rightArr.length);
    
    // STEP2
    int k = startIndex; // 存储原数组中的下标
    int i = 0;          // 存储左边数组的下标
    int j = 0;          // 存储右边数组的下标
    while (i < leftArr.length && j < rightArr.length) {
        // 将较小的元素复制到元素组对应下标k,并且移动较小元素所在数组下标
        if (leftArr[i] < rightArr[j]) {
            arr[k++] = leftArr[i++];    
        } else {
            // 此时说明 arr[i]到arr[leftArr.length - 1]的值都比arr[j]大,即此时以arr[j]结尾的逆序对个数为: 
            count += leftArr.length - i;
            arr[k++] = rightArr[j++];
        }
    }
    
    // STEP3
    if (i < leftArr.length) {
        System.arraycopy(leftArr, i, arr, k, leftArr.length - i);
    } else if (j <= rightArr.length) {
        System.arraycopy(rightArr, j, arr, k, rightArr.length - j);
    }
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏HansBug's Lab

1798: [Ahoi2009]Seq 维护序列seq

1798: [Ahoi2009]Seq 维护序列seq Time Limit: 30 Sec  Memory Limit: 64 MB Submit: 2930...

2945
来自专栏偏前端工程师的驿站

JS魔法堂:再识Number type

Brief                                   本来只打算理解JS中0.1 + 0.2 == 0.300000000000000...

2115
来自专栏iOS技术杂谈

iOS block探究(一): 基础详解你要知道的block都在这里

你要知道的block都在这里 转载请注明出处 https://cloud.tencent.com/developer/user/1605429 本文大纲 blo...

3248
来自专栏前端布道

操作数组不要只会for循环

很多时候,我们在操作数组的时候往往就是一个for循环干到底,对数组提供的其它方法视而不见。看完本文,希望你可以换种思路处理数组,然后可以写出更加漂亮、简洁、函数...

2989
来自专栏用户2442861的专栏

JavaScript 正则表达式上——基本语法

JavaScript种正则表达式有两种定义方式,定义一个匹配类似 <%XXX%> 的字符串

521
来自专栏技术博客

C#基础知识系列十(集合)

  本节主要是来了解学习集合,以方便在程序编写时,什么地方该选用什么集合,让程序更健壮的运行起来。在学习了解集合之前,首先需要了解一些数据结构方面的知识。下面我...

1053
来自专栏Hongten

java中的移位运算符:<<,>>,>>>总结

value >>> num     --   num 指定要移位值value 移动的位数。

1505
来自专栏Golang语言社区

【Go 语言社区】POJ 1047 Round and Round We Go 循环数新解

题目描述: 给定一字符串表示的高精度数,判断它是否是可循环的。如果假设字符串num的长为n,则将num从1开始乘到n,如果每次得到的结果包含的字符元素都和a是相...

35111
来自专栏cmazxiaoma的架构师之路

Java数据结构和算法(2)--《Java数据结构和算法》第二版 Robert lafore第二章【数组】编码作业

1943
来自专栏我是业余自学C/C++的

循环链表(测试代码,Qt5.1 for windows)

2824

扫码关注云+社区