zoukankan      html  css  js  c++  java
  • 算法分析-分治 归并排序,递归插入排序,二分查找

    反正分治的套路就是 相同子问题,递归做,我之前有介绍express源码,其中的中间件使用就是用next()函数一直递归,想看的看我的express源码分析:

    分治3步骤:

    1. 分解
    2. 处理
    3. 归并

    下面给出归并排序的js代码:

     1 var A = [5, 2, 4, 6, 1, 3];
     2 var len = A.length - 1;
     3 
     4 MERGE_SORT(A, 0, len);
     5 
     6 A.forEach(function (element, index, arr) {
     7     console.log(index, "-----------", element);
     8 });
     9 
    10 function MERGE_SORT(A, start, end) {
    11     if (start < end) {
    12         var middle = Math.floor((start + end) / 2); //向下去整
    13         MERGE_SORT(A, start, middle); //左边递归
    14         MERGE_SORT(A, middle + 1, end); //右边递归
    15         MERGE(A, start, middle, end);
    16     }
    17 }
    18 
    19 function MERGE(A, start, middle, end) {
    20     var Arr1 = A.slice(start, middle + 1),
    21         Arr2 = A.slice(middle + 1, end + 1),//slice(start,end) end不包括,所以加1
    22         len1 = Arr1.length,
    23         len2 = Arr2.length,
    24         i = 0,
    25         j = 0;
    26     for (i, j; i < len1 && j < len2;) {
    27         (Arr1[i] < Arr2[j]) ? (A[start++] = Arr1[i++]) : (A[start++] = Arr2[j++]);
    28     }
    29 
    30     while (i == len1 && j < len2) {
    31 
    32         A[start++] = Arr2[j++];
    33     }
    34     while (j == len2 && i < len1) {
    35         A[start++] = Arr1[i++];
    36     }
    37 
    38 }

    像这种分治递归的,你需要找到一个出口来结束递归。

    还记得插入排序吗,我们换成递归的写法,怎么写呢,思路是这样的,将A[n],插入A[0...n-1]内,而A[0..n-1]是已经排序好的。上代码:

     1 var A = [5, 2, 4, 6, 1, 3];
     2 
     3 RECURSIVE_INSERT_SORT(A, 6);
     4 A.forEach(function (element, index, arr) {
     5     console.log(index, "-----------", element);
     6 });
     7 
     8 function RECURSIVE_INSERT_SORT(A, len) {
     9     if (len > 1) {
    10         var last_var = A[len - 1];
    11         A.splice(--len, 1);
    12         RECURSIVE_INSERT_SORT(A, len);
    13         INSERT(A, last_var);
    14     }
    15 }
    16 
    17 function INSERT(A, last_var) {
    18     var len = A.length,
    19         k = len - 1;
    20     while (A[k] > last_var && k >= 0) {
    21 
    22         A[k + 1] = A[k];
    23         k--;
    24     }
    25     A[k + 1] = last_var;
    26 
    27 }

    那思考一下,如果A=[1,2,3,5,6,7]是有序的,用二分查找去查找v=3,v=4,怎么写,有思路吗?

     1 var result = {index: false},
     2     A = [1, 2, 3, 5, 6, 7];
     3 function BINARY_SEARCH(A, start, end, val) {
     4     if (end >= start) {
     5         var middle = Math.floor((start + end) / 2);
     6         if (A[middle] == val) {
     7             result.index = middle;
     8             console.log("找到了~");
     9             return;
    10         }
    11         if (A[middle] < val) {
    12 
    13             BINARY_SEARCH(A, middle + 1, end, val);
    14         }
    15         if (A[middle] > val) {
    16             BINARY_SEARCH(A, start, middle - 1, val);
    17         }
    18     }
    19 }
    20 
    21 BINARY_SEARCH(A, 0, 5, 7);
    22 console.log(result.index);

    既然我们已经学会了二分查找策略,我们能不能将插入排序再改一改,将while循环改成二分查找策略呢?

     1 var A = [99, 12, 77, 103, 1000, 3, 11, 4324, 3, 321, 545, 65, 76765, 78, 889, 98, 324, 23, 4, 544, 6, 2];
     2 
     3 BINARY_INSERT_SROT(A);
     4 A.forEach(function (element, index, arr) {
     5     console.log(index, "-----------", element);
     6 });
     7 
     8 function BINARY_INSERT_SROT(A) {
     9     var len = A.length;
    10     for (var i = 1; i < len; i++) {
    11         var key = A[i];
    12         var j = i - 1;
    13 
    14         /*
    15          *  
    16          *  A是要修改的数组对象
    17          *  0表示已经排号序列的start下标,
    18          *  j表示已经排号序列的end下标,
    19          *  key 表示需要插入的值,也就是第五个参数need_insert_index下标对应的值
    20          *   need_insert_index 就是我们要插入的key值的下标。也就是j+1;
    21          * 
    22          * */
    23         BINARY_SEARCH(A, 0, j, key, j + 1); //这里替换掉while(key<A[j]&&j>=0)
    24     }
    25 }
    26 function BINARY_SEARCH(A, start, end, val, need_insert_index) {
    27 
    28     /*
    29     * 
    30     * 我们知道二分查找是查找中间下标对于的值是否为所求,是就找到,不是,就对半再递归查找。
    31     * 
    32     * 这一步在我们这个程序里还是需要的,毕竟可能会直接找到相同的,找到后,我们默认将值插在后面,
    33     * 
    34     * 这里有个关键步骤,也就是第5个参数的用处,我们是通过对比查找的。我们如果往后移动,也一定要在
    35     * 
    36     * 这个middle下标到need_insert_index下标之间全部移动,不然可能只移动了一部分。
    37     * 
    38     * 当start==end的时候,或者start + 1 ==end的时候,middle都等于stsrt,这时候,其实都是比较一个数,
    39     * 
    40     * 下标的移动和middle这个找到的特殊情况一样,当找不到的时候根据条件去移动。
    41     * 
    42     * */
    43     
    44     
    45     if (end >= start) {
    46         var middle = Math.floor((start + end) / 2);
    47         if (A[middle] == val) {
    48             var j = middle;
    49             while (need_insert_index > middle) {
    50                 A[need_insert_index] = A[need_insert_index - 1];
    51                 need_insert_index--;
    52             }
    53             A[middle + 1] = val; //默认是插后的
    54             return;
    55         }
    56         if (middle == start) {
    57             if (A[start] <= val && A[end] >= val) {
    58                 while (need_insert_index > start) {
    59                     A[need_insert_index] = A[need_insert_index - 1];
    60                     need_insert_index--;
    61                 }
    62                 A[start + 1] = val;
    63 
    64             } else if (A[start] <= val && A[end] <= val) {
    65                 while (need_insert_index > end) {
    66                     A[need_insert_index] = A[need_insert_index - 1];
    67                     need_insert_index--;
    68                 }
    69                 A[end + 1] = val;
    70             } else {
    71                 while (need_insert_index > start) {
    72                     A[need_insert_index] = A[need_insert_index - 1];
    73                     need_insert_index--;
    74                 }
    75                 A[start] = val;
    76             }
    77             return
    78         }
    79         (A[middle] > val) ? (BINARY_SEARCH(A, start, middle - 1, val, need_insert_index)) : (BINARY_SEARCH(A, middle + 1, end, val, need_insert_index));
    80     }
    81 }

     思考:请给出一个运行时间为O(nlgn)的算法,使之能在给定一个由n个整数构成的集合S和另一个整数x时,判断出S中是否存在有两个其和等于x的元素。

    分析:

           若要整个算法的时间复杂度为O(nlgn),那么只要算法中最复杂的模块的复杂度为O(nlgn)就可以了。

     1 var A = [99, 12, 77, 103, 1000, 3, 11, 4324, 3, 321, 545, 65, 76765, 78, 889, 98, 324, 23, 4, 544, 6, 2],
     2     result = {index: false};
     3 var isfind = isExisted(A, 89);
     4 console.log(isfind);
     5 
     6 function isExisted(A, val) {
     7     var len = A.length;
     8     //先给集合排序
     9     MERGE_SORT(A, 0, len - 1);
    10 
    11     //循环次数最多为n
    12     for (var i = 0; i < len; i++) {
    13         //每次次二分搜索,时间消耗lgn
    14         BINARY_SEARCH(A, 0, len - 1, val - A[i]);
    15         if (result.index) {
    16             //下面这个判断是考虑到了x的值是序列中某个元素的2倍的情况
    17             if (result.index != i) {
    18                 result.index = true;
    19                 break;
    20             }else {
    21                 result.index = false;
    22             }
    23         }
    24     }
    25     return  result.index;
    26 }
    27 
    28 
    29  //复制粘贴
    30 function BINARY_SEARCH(A, start, end, val) {
    31     if (end >= start) {
    32         var middle = Math.floor((start + end) / 2);
    33         if (A[middle] == val) {
    34             result.index = middle;
    35             console.log("找到了~");
    36             return;
    37         }
    38         if (A[middle] < val) {
    39 
    40             BINARY_SEARCH(A, middle + 1, end, val);
    41         }
    42         if (A[middle] > val) {
    43             BINARY_SEARCH(A, start, middle - 1, val);
    44         }
    45     }
    46 }
    47 
    48  //复制粘贴
    49 function MERGE_SORT(A, start, end) {
    50     if (start < end) {
    51         let middle = Math.floor((end + start) / 2);
    52         MERGE_SORT(A, start, middle);
    53         MERGE_SORT(A, middle + 1, end);
    54         MERGE(A, start, middle, end);
    55     }
    56 }
    57 
    58  //复制粘贴
    59 function MERGE(A, start, middle, end) {
    60     var arr1 = A.slice(start, middle + 1);
    61     var arr2 = A.slice(middle + 1, end + 1);
    62     var len1 = arr1.length;
    63     var len2 = arr2.length;
    64     var i = 0;
    65     var j = 0;
    66     for (i, j; i < len1 && j < len2;) {
    67         (arr1[i] < arr2[j]) ? (A[start++] = arr1[i++]) : (A[start++] = arr2[j++]);
    68     }
    69     while (i == len1 && j < len2) {
    70         A[start++] = arr2[j++];
    71     }
    72     while (j == len2 && i < len1) {
    73         A[start++] = arr1[i++];
    74     }
    75 }

    总结:

              merge_sort这个函数是归并排序算法,它的时间复杂度是O(nlgn)。

               第12行处,这个for循环中,有一个二分查找算法。它的时间复杂度是O(lgn),所以整个for循环模块的时间复杂度为O(nlgn)。

    霍纳规则的正确性:

    公式的简单推理:

    NewImagea0+a1*x+a2*x^2+a3*x^3+a4*x^4+…+ak*x^k+…+an*x^n

    NewImage

    计算机的循环计算。

    1      y = 0                                      时间花费  1

    2      for i=n down to 0                                   n+1

    3              y = ai + x*y                                     n

                                                                            总时间花费  2n+2

    这样循环计算出来的y就是上面汇总的值。

    a)、Θ(n), 推理过程看上面。

    b)、伪代码实现的朴素的多项式求值算法。

    下面是一个取巧的算法,时间消耗是 3n, 在n >2 时 时间消耗大于 2n+2

    void Ploynomial()                                  时间消耗 = 3n
    {
            int t;                                                      1
            sum = a[0];                                          1
            for (i = 1; i < n; i++)                            n
            {
                    sum += a[i]*x;                             n-1
                    x = x*x;                                         n-1
            }
    }

    c)、

    初始化: 有 y = 0, i = n , 这样 计算 下面公式的右边 为 0 , 所以初试化满足循环不变式。 

    NewImage

              

    保持:假设当第i=j满足时,考察i=j-1。

    终止: 当循环结束时候,有 i= -1,

    ------------

    由于0从0到n-(i+1),因此有:
    y = Σ ak+i+1 * x^k
      = ak+i+1 + ak+i+2 * x + ... + an * x^(n-(i+1))
    霍纳规则代码段循环不变式证明如下:
    初始:
        i=n,y[n] = 0,迭代开始时,循环后有y[n] = a[n]。
    保持:
        对于任意 0 ≤ i ≤ n,循环后有:
            y[i] = a[i] + y[i+1] * x = a[i] + (a[i+1] * x + a[i+2] * x + ... + a[n] * x^(n-(i+1))) * x
                 = a[i] + a[i+1] * x + a[i+2] * x^2 + ... + a[n] * x^(n-i)
    终止:
        i小于0时终止,此时有 y[0] = a[0] + a[1] * x + a[2] * x^2 + a[n] * x^n

    证明和y = Σ a[k+i+1] * x^k的关系:
        k 从0到n-(i+1),等价于 0 ≤ k ≤ n-(i+1)。因此
            y = Σ a[k+i+1] * x^k
                = a[i+1] + a[i+2] * x + ... + a[n-(i+1)+i+1] * x^(n-i)
                = a[i+1] + a[i+2] * x + ... + a[n] * x^(n-i)
        由于i+1循环之后和i循环之前的值相等,用y'[i]表示i循环之前的值,则有:
            y'[i] = y[i+1]
        霍纳规则循环不变式的结果表明:
            y[i] = a[i] + a[i+1] * x + a[i+2] * x^2 + ... + a[n] * x^(n-i)
        因此有:
            y'[i] = y[i+1] = a[i+1] + a[i+2] * x + ... + a[n] * x^(n-(i+1))
        令k=n-(i+1),则n=k+i+1,所以:
            y'[i] = a[i+1] + a[i+2] * x + ... + a[k+i+1] * x^(k+i+1-(i+1))
                    = a[i+1] + a[i+2] * x + ... + a[k+i+1] * x^k
        用y表示y'[i],则有:
            y = a[i+1] + a[i+2] * x + ... + a[k+i+1] * x^k
                = Σ a[k+i+1] * x^k
        其中 k从0到n-(i+1)
        证毕。

    思考题:逆序对
    设A[1..n]是一个包含n个不同数的数组。如果i<j且A[i]>A[j],则(i,j)就称为A中的一个逆序对(inversion)。
    a)列出数组〈2,3,8,6,1〉的5个逆序。
    b)如果数组的元素取自集合{1, 2, ..., n},那么,怎样的数组含有最多的逆序对?它包含多少个逆序对?
    c)插入排序的运行时间与输入数组中逆序对的数量之间有怎样的关系?说明你的理由。
    d)给出一个算法,它能用Θ(nlgn)的最坏情况运行时间,确定n个元素的任何排列中逆序对的数目。(提示:修改合并排序)

    a)  (2,1)  (3,1) (8,1) (6,1),(8,6)

    b) 数组从大到小有序排列时,逆序对最多,为n(n-1)/2个。

    c) 逆序对增加时,插入排序时间增加。
    没有逆序对时,插入排序时间最少,为Θ(n)。
    逆序对最多时,插入排序时间最多,为Θ(n^2)。

    d)  归并算法, 每次移动牌,次数加1, 合计的次数就是逆序对的个数。

    给出修改后的程序:

     1 var num = 0,A = [5,2,4,6,1,3]; 2 MERGE_SORT(A, 0, A.length - 1);
     3 console.log("最终结果是:",num);
     4 
     5 function MERGE_SORT(A, start, end) {
     6     if (start < end) {
     7         let middle = Math.floor((end + start) / 2);
     8         MERGE_SORT(A, start, middle);
     9         MERGE_SORT(A, middle + 1, end);
    10         MERGE(A, start, middle, end);
    11     }
    12 }
    13 
    14 function MERGE(A, start, middle, end) {
    15     var arr1 = A.slice(start, middle + 1);
    16     var arr2 = A.slice(middle + 1, end + 1);
    17     var len1 = arr1.length;
    18     var len2 = arr2.length;
    19     var i = 0;
    20     var j = 0;
    21     for (i, j; i < len1 && j < len2;) {
    22         /*  (arr1[i] < arr2[j]) ? (A[start++] = arr1[i++]) : (A[start++] = arr2[j++]);*/
    23         if (arr1[i] > arr2[j]) {
    24             num += (len1 - i);
    25             A[start++] = arr2[j++];
    26 
    27         } else {
    28             A[start++] = arr1[i++]
    29         }
    30     }
    31     while (i == len1 && j < len2) {
    32         A[start++] = arr2[j++];
    33 
    34     }
    35     while (j == len2 && i < len1) {
    36         A[start++] = arr1[i++];
    37 
    38     }
    39     console.log(A, "对应的num数目:",num);
    40 
    41 }

    分析:

        我们使用归并排序,其实递归执行的时候 ,是从左往右的,大家可以画图:

                               [5,2,4,6,1,3]

                           [5,2,4]        [6,1,3]

                      [5,2]   [4]      [6,1]  [3]

                  [5] [2]             [6]  [1] 

         我们发现,当归并[5] [2] 的时候,因为左边大于右边,所以数目加1,归并后变成[2,5],[2,5]和[4]归并,因为5大于4,数目加1变为2,

       归并后为[2,4,5],同理分析右边,最后归并[2,4,5] [1,3,6] 这里是关键,也就是为什么代码中是绿色的部分,因为2大于1,所以2后面的所有都大于1

     4大于3,4后面的全大于3.

  • 相关阅读:
    Hibernate延迟加载机制
    Hibernate中Criteria的完整用法
    HibernateTemplate的常规用法
    Java各类在线API
    HibernateTemplate的常规用法
    Hibernate中Criteria的完整用法
    Java各类在线API
    Hibernate3的DetachedCriteria使用
    Hibernate延迟加载机制
    Hibernate3的DetachedCriteria使用
  • 原文地址:https://www.cnblogs.com/huenchao/p/5900802.html
Copyright © 2011-2022 走看看