zoukankan      html  css  js  c++  java
  • 算法录 之 逆序对。

      问题如下:

      给一个长度为N的数组,a1,a2。。。aN,问其中逆序对的个数,其中逆序对的定义是 i<j 且 ai>aj 。

      1,比较朴素的想法是直接for循环,内层再一个for循环找到这个数前面比他大的个数。

    1 int getans() {
    2     int ret=0;
    3     for(int i=1;i<=N;++i)
    4         for(int j=1;j<i;++j)
    5             if(a[j]>a[i])
    6                 ++ret;
    7     return ret;
    8 }

      这样的复杂度是N^2的,比较慢。

      2,前面的算法比较慢,能不能优化?

      对于外层的for循环的每个ai,如果不用内层for循环而是用其他的方法更快的算出前面比ai大的个数,那么就快多了。

      用数据结构BST来维护。

      对于每个数,求出BST中比他大的个数,然后ret直接加上,然后再在BST中插入这个数。

     1 int getans() {
     2     BIT tree;
     3     int ret=0;
     4 
     5     for(int i=1;i<=N;++i) {
     6         ret+=tree.findMax(a[i]);
     7         tree.insert(a[i]);
     8     }
     9 
    10     return ret;
    11 }

      这样的话复杂度就是NlogN了,已经足够快了。

      3,还有一种也是NlogN的算法,而且速度比较稳定。需要用到归并排序的知识。

      先来看看归并排序,把a1,a2,a3。。。aN拆成两半,分别递归排序,然后再把这两个数组用O(N)来进行合并。

      那么在其中再加上一些代码。

      假设要求N个数的逆序对个数,如果先递归求好a1,a2。。。aN/2 和 aN/2+1。。。aN 这两个子数组的逆序对个数。

      然后在求i在左边且j在右边的逆序对个数,加起来就是答案了。

      因为i在左边,j在右边,如果用for循环来算的话,又变成N^2来算了。

      现在注意一个性质:因为左边那个和右边那个已经分别递归求好了,那么现在求i在左边,j在右边的逆序对的时候,两个子数组中数的的顺序已经没所谓了,所以先分别给左边的数和右边的数排好序。

      那么for循环右边那个,对于每个数二分搜索找到左边的有多少个比他大的。

     1 int getans(int L,int R) {
     2     if(R==L) return 0;
     3 
     4     int M=(L+R)/2;
     5     int ret=getans(L,M)+getans(M+1,R);            // 递归求解两个子序列并且排序(归并排序的内容)
     6 
     7     for(int i=M+1;i<=R;++i)
     8         ret+=M-find(L,M,a[i])+1;            // 二分查找L到M中比ai大的第一个数的位置。
     9 
    10     merge(L,R);            // 归并排序的合并步骤。
    11 }

      例子如下:

      求 2 4 3 5 1 7 9 8 的逆序对个数:

      先分别递归求出 2 4 3 5 和 1 7 9 8 的逆序对个数是1和1,然后求i在左边,j在右边的。先分别排序 2 3 4 5 和 1 7 8 9

      然后求左边中比1大的个数(因为是有序的,所以直接二分就行了。)是 4 ,所以ret加上4。

      再求左边中比7大的,再求比8大的,再求比9大的。都加起来的话就是答案了。

      这样在合并的时候是NlogN(二分搜索)的复杂度,那么根据主定理来求 T(N)=2*T(N/2)+NlogN 得 T(N)=NlogNlogN的复杂度。

      4,上面的算法还是效率不高,还能再优化。

      find这个二分搜索需要log的复杂度,可以优化到O(1)来。

      因为左右两个都是有序的,如果对于右数组的 ai 来说,设左数组中比他大的第一个数的位置是 bi,那么可以证明 bi 是递增的。

      比如例子:

      2 4 5 7 和 1 3 6 9

      比1大的左数组的第一个数的位置是1,比3大的第一个位置是2,比6大的第一个位置是4,比9大的第一个位置是5(超出表示没有),也就是这四个b是递增的。

      那么每次找左数组中比x大的数的位置时,可以不用find函数了,而是直接设一个指针递增。

      代码如下:

     1 int getans(int L,int R) {
     2     if(R==L) return 0;
     3 
     4     int M=(L+R)/2;
     5     int ret=getans(L,M)+getans(M+1,R);            // 递归求解两个子序列并且排序(归并排序的内容)
     6 
     7     int p=L;
     8     for(int i=M+1;i<=R;++i) {
     9         while(p<=M && a[p]<=a[i]) ++p;            // 因为位置是递增的。
    10         ret+=M-p+1;
    11     }
    12 
    13     merge(L,R);            // 归并排序的合并步骤。
    14 }

      复杂度的话,因为p只会递增不会递减,所以while最多N次,均摊下来的话复杂度也是O(1)的对于每个for,那么总复杂度就是O(N)的来进行合并计算。

      T(N)=2*T(N/2)+N 求出 T(N)=NlogN 的复杂度。

  • 相关阅读:
    手机app打开的web,在打开chrome浏览器
    linux环境下安装yaf
    redis在ubuntu下的安装
    验证一个值是否在多维数组中
    20个正则表达式
    nginx+memcached缓存图片
    cocos2dx加载骨骼动画,获取骨骼位置
    listview的pushBackDefaultItem中的item属性被修改问题
    lua保留n位小数方法
    cocos2dx中node的pause函数(lua)
  • 原文地址:https://www.cnblogs.com/whywhy/p/5066440.html
Copyright © 2011-2022 走看看