zoukankan      html  css  js  c++  java
  • 【原创】编程之美之二分搜索

    二分搜索思想

    给定一个有序(不降序)数组a, 求任意一个i使得a[i]等于v,不存在返回-1。

    二分搜索的思路是:选一个数组中间值m,将有序数组一分为二。如果a[m]==v,搜索结束;如果a[m]>v,搜索[left, m),反之搜索(m, right]。如图所示。

    那么如果数组中没有v时,何时终止搜索呢?一个显而易见的条件是数组不能再进行划分搜索时,即left > right 。


    二分搜索可以有以下变种:

    1. 给定一个有序(不降序)数组a, 求最大的i使得a[i]等于v,不存在返回-1

    2. 给定一个有序(不降序)数组a, 求最小的i使得a[i]等于v,不存在返回-1

    3. 给定一个有序(不降序)数组a, 求最大的i使得a[i]小于v,不存在返回-1

    4. 给定一个有序(不降序)数组a, 求最小的i使得a[i]大于v,不存在返回-1

    变种的解法也简单,对变种1来说,当找到a[m] == v时,为了接着找到更大的(最右边的)元素,需要搜索[m, right]。看到差别了吧,之所以把m包进来,就是防止m右边再没有等于v的元素。

    变种3,当a[m] < v时,搜索[m, right]以找到比m更右边的元素。当a[m] >= v时,搜索[left, m)。终止条件

    这里说一下写代码时的思考——比如,为什么循环控制要用left < right -1 而不是 left < right 等。

    实现分析

    思路很简单,关键是循环控制

    先看一下中间值是怎么选的:mid = left + (right - left)/2 。 这个公式有个特点,当left==right或left==right -1 时,mid==left。什么意思呢?如图所示,(a)的下一次划分可能还是(a), (b)的下一次划分还是(b),这导致我们的循环不会停了。。。因为left永远不会大于right了。。。

    所以,为了让结束搜索,我们可以限制left、right最少夹三个元素。即当一下条件出现时,搜索终止。

    better solution

    记得搜索终止后,别忘了判断a[left]、a[right]是否符合条件。

    代码

    下面是java版本的变种3实现(变种1《编程之美》里已有代码):

     1 void checkValid(int[] a, int l, int r) {
     2         if (a == null || l < 0 || r < 0 || l > r)
     3             throw new IllegalArgumentException();
     4 }
     5 int bsMaxIndexLessThanV(int[] a, int left, int right, int v) {
     6 
     7             checkValid(a, left, right);
     8 
     9             int m, l = left, r = right;
    10             while(l < r-1){
    11                 m = l + (r - l)/2;
    12                 
    13                 if( a[m] < v)
    14                     l = m;
    15                 else
    16                     r = m - 1;
    17                     
    18             }
    19             
    20             if(v > a[r])
    21                 return r ;
    22             if(v > a[l])
    23                 return l;
    24             return -1;
    25 }

     测试用例

    1. 检查输入是否正确

    2. 分别用包含一个元素,两个元素,多个元素的数组测试。保证corner case:

     1     @Test
     2     public void testBsMaxLess() {
     3         int[] a1 = { 1 };
     4         Assert.assertEquals(-1, bsMaxIndexLessThanV(a1, 0, 0, 1));
     5         Assert.assertEquals(0, bsMaxIndexLessThanV(a1, 0, 0, 2));
     6         
     7 
     8         int[] a2 = { 1, 2 };
     9         Assert.assertEquals(-1, bsMaxIndexLessThanV(a2, 0, 1, 1));
    10         Assert.assertEquals(0, bsMaxIndexLessThanV(a2, 0, 1, 2));
    11         Assert.assertEquals(1, bsMaxIndexLessThanV(a2, 0, 1, 3));
    12         
    13         int[] a3 = new int[] { 2, 3, 3, 3, 3, 3, 3, 3, 4 };
    14         Assert.assertEquals(-1, bsMaxIndexLessThanV(a3, 0, a3.length - 1, 2));
    15         Assert.assertEquals(8, bsMaxIndexLessThanV(a3, 0, a3.length - 1, 10));
    16         Assert.assertEquals(0, bsMaxIndexLessThanV(a3, 0, a3.length - 1, 3));
    17         Assert.assertEquals(7, bsMaxIndexLessThanV(a3, 0, a3.length - 1, 4));
    18     }
    Test case
  • 相关阅读:
    C#针对js escape解码
    .NET XML序列化与反序列化
    数据库文档生成工具(1)
    小欣外贸助手-国际站排名查询工具限量封测了
    七夕小福利
    利用正则表达式获取博客园随笔(四)
    利用正则表达式获取博客园随笔
    利用正则表达式获取博客园随笔(三)
    利用正则表达式获取博客园随笔(二)
    利用正则表达式获取博客园随笔(一)
  • 原文地址:https://www.cnblogs.com/lina/p/3183886.html
Copyright © 2011-2022 走看看