反正分治的套路就是 相同子问题,递归做,我之前有介绍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)。

霍纳规则的正确性:

公式的简单推理:

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

计算机的循环计算。

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 , 所以初试化满足循环不变式。

保持:假设当第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.

转载于:https://www.cnblogs.com/huenchao/p/5900802.html

算法分析-分治 归并排序,递归插入排序,二分查找相关推荐

  1. 【算法分析与设计】浅析二分查找

    二分查找与分治 需要说明,二分查找和分治法不一样,不要弄混淆-- 二分查找 每次都舍弃一半,从留下的一半中寻找目标 : 而分治法把一个大问题分成两个或多个小问题,递归地求这些小问题的解,最后再把它们合 ...

  2. java二分查找递归_java学习之—递归实现二分查找法

    /** * 递归实现二分查找法 * Create by Administrator * 2018/6/21 0021 * 上午 11:25 **/ class OrdArray{ private lo ...

  3. C语言——十四种内部排序算法【直接插入排序-冒泡排序-选择排序-插入排序-希尔排序-归并排序-快速排序-堆排序-折半插入排序-二分查找-路插入排序-表插入排序-简单选择排序-直接选择排序-树形选择】

    目录: 一:插入排序 A:直接插入排序 1.定义: 2.算法演示 实例1: 3.基本思想 4.排序流程图 实例1: B:希尔排序 1.定义: 2.算法演示 实例2: C:其他插入排序 a:折半插入排序 ...

  4. 算法 - 二分查找(非递归实现二分查找)

    package Algorithm.binarysearchnorecursion;public class BinaaySearchNoRecur {public static void main( ...

  5. 冒泡排序、递归、二分查找

    一.冒泡排序 给出一个纯数字列表. 请对列表进行排序. 思路: 1.完成a和b的数据交换. 例如, a = 10, b = 24 交换之后, a = 24, b = 10 2.循环列表. 判断a[i] ...

  6. 【算法】1.递归实现二分查找

    一.什么是二分查找 假设给定的数组中的元素是一个有序的状态,比如是单调递减或者是单调递增的状态,对于这种情况可以使用二分查找来完成.即二分查找需要满足两个条件: (1)数组存储 (2)元素有序,单调性 ...

  7. Python递归,二分查找

    """递归十以内相乘""" def x(a):if a == 10:return 10else:return a * x(a + 1)pri ...

  8. python递归实现二分查找_python二分查找算法的递归实现

    本文实例讲述了python二分查找算法的递归实现方法.分享给大家供大家参考,具体如下: 这里先提供一段二分查找的代码: def binarySearch(alist, item): first = 0 ...

  9. python递归实现二分查找_python二分查找算法的递归实现方法

    本文实例讲述了python二分查找算法的递归实现方法.分享给大家供大家参考,具体如下: 这里先提供一段二分查找的代码: def binarySearch(alist, item): first = 0 ...

最新文章

  1. Rion®-RCMS报表综合管理系统
  2. TCP的那些事(转载)
  3. java实现支付宝支付完整过程(沙箱测试环境,下篇整合ssm)
  4. Android 第七课 4种基本布局之FrameLayout和百分比布局
  5. arma3自定义服务器,Arma3 生存服架设教程,武装突袭3游戏服务器架设
  6. 前端学习(1262):fetch请求参数
  7. POJ 1577 Falling Leaves(二叉查找树)
  8. C++中类的拷贝控制
  9. 计算机英语讲课笔记01
  10. 转https_PDF怎么转成JPG最简单?分享免费的PDF转图片方法
  11. Facebook vs Chrome 关公秦琼的未来之战,互联网营销
  12. 【心得】Web设计师应参考的技术
  13. Thrift RPC实战(七) 基于zookeeper和thrift的RPC服务发布订阅
  14. 简单html倒计时器代码,js简单倒计时实现代码
  15. latex毕业论文模板(附源码)
  16. 微信Native支付申请接入流程-避免踩坑指南
  17. 新手程序员之初生牛犊不怕虎
  18. 手机电脑都能用,将照片转成PDF的免费方法
  19. 班级日常工作管理系统
  20. 艾美捷利妥昔单抗Rituximab参数及应用

热门文章

  1. python中如何打印两行代码间的空行_python 打印几行空行、 打印不换行
  2. python卷积神经网络cnn的训练算法_【深度学习系列】卷积神经网络CNN原理详解(一)——基本原理...
  3. java mvc mvp mvvm_一篇文章了解架构模式:MVC/MVP/MVVM
  4. python数组plot_Python Matplotlib:动态更新plot-数组长度未知
  5. 如何用计算机组添加打印机共享的打印机,工作组内打印机如何共享?
  6. java webengine_如何以Java实现网页截图技术
  7. 任务分配算法c语言,基于蚁群算法多Agent任务分配方法.pdf
  8. python if条件思维导图_跟老齐学Python之从if开始语句的征程
  9. 《编译原理》课程教学大纲
  10. ecshop根目录调用_ecshop列表页 调用二级分类教程