二分查找(Binary Search)

二分查找也叫作折半查找。二分查找有两个要求,一个是数列有序,另一个是数列使用顺序存储结构。他的思想很简单,但是在书写过程中如果边界条件无法正确的确定,很容易陷入到循环中无法跳出

二段性

二段性是集合中的元素有存在分界线,给定条件可以将集合中元素分为两部分,一部分满足条件,一部分不满足条件。

  1. 分界线之前的第一个元素,也就是最后一个满足条件的值,我们称为ML(Meeting Condition Last)
  2. 分界线之后的第一个元素,也就是第一个不满足该条件的值,我们称为NB(Not Meeting Condition Begining)

区间问题

此节主要讲二分查找的细节处理,如果要看二分的查找过程不建议看这篇,文中所讲的二分伪代码为:

l = -1,r = N + 1
while l + 1 != r
    m = l + (r - l >> 1)
    if judge(m) // 根据题目修改judge
        l = m
    else
        r = m
return l/r // 最终返回时根据需求进行返回

对于一个给定的数组 0    1    2    3    ⋯    ( N − 4 )    ( N − 3 )    ( N − 2 )    ( N − 1 )    N 0\ \ 1\ \ 2\ \ 3\ \ \cdots\ \ (N-4)\ \ (N-3)\ \ (N-2)\ \ (N-1)\ \ N 0  1  2  3    (N4)  (N3)  (N2)  (N1)  N进行二分查找时,需考虑以下步骤

  1. 确定左右边界:这里确定左右边界分别为 − 1 -1 1 N + 1 N+1 N+1

面对查找小于 0 0 0的最后一个数时,如果令左边界的值为 0 0 0,那么左边界的初始条件已经不满足问题要求了,因此采用0作为一个通用的二分求解方法的左边界并不是一个合理的方案。

同理,面对查找大于 N N N的第一个数,也不可以令右边界的值为1。

  1. 获得中点:使用 m = l + ( r − l > > 1 ) m = l + (r - l >> 1) m=l+(rl>>1)作为获得重点的公式。

采用其他的公式,比如 m = l + r > > 1 m=l+r>>1 m=l+r>>1,也是可以的,但是为了防止 l l l r r r相加导致的数值溢出使用上面的公式。

  1. 收缩区间:使用 l = m l=m l=m r = m r=m r=m作为收缩区间的公式。

为了避免写出死循环,必须保证每次进入 w h i l e while while之后,可行区间都在变小,且最终推出循环。在此先引出程序终止的条件为 l + 1 = r l+1=r l+1=r,当面对 l + 1 = r l+1=r l+1=r,此时以满足终止条件所以无论 l l l r r r取多少,都会退出循环;当面对 l + 2 = r l+2=r l+2=r,中点为 m = l + 1 m=l+1 m=l+1,此时循环体内的收缩公式会将 m m m赋值给 l l l或者 r r r,再此达到终止条件,退出循环;当面对 l + 3 = r l+3=r l+3=r,按照 l + 2 = r l+2=r l+2=r里的分析,其最终会进入到 l + 2 = r l+2=r l+2=r或者 l + 1 = r l+1=r l+1=r的条件。

  1. 确定终止条件:这里终止条件为 l + 1 = r l + 1 = r l+1=r,即循环条件为 l + 1   ! = r l + 1\ != r l+1 !=r

因为的目标时遍历整个数组,因此终止条件要确保所有的元素都可以被包含在内。同上面的收缩区间的公式相配合可以构成一个二分查找。

  1. l l l r r r的指向问题

l l l r r r的指向哪个数值,最终还是取决于judge条件。对于我们要查找小于零第一个数,其judge条件为 m < 0 m<0 m<0 l = − 1 l=-1 l=1 r = 0 r=0 r=0

对于一个例子 1 , 2 , 3 , 5 , 5 , 5 , 8 , 9 1, 2, 3, 5, 5, 5, 8, 9 1,2,3,5,5,5,8,9

  1. 找到**小于 t t t**的最后一个数:

    class Solution {
        public int binarySearch(int[] arr, int t) {
            int l = -1, r = arr.length;
            while (l + 1 != r) {
                int m = l + (r - l >> 1);
                if (arr[m] < t) l = m; // judge条件:如果小于t,则让l=m;否则r=m
                else r = m;
            }
            return l; // 返回l
        }
    }
    
    Solution solution = new Solution();
    System.out.println(solution.binarySearch(new int[]{1, 2, 3, 5, 5, 5, 8, 9}, 5)); // 返回2,对应的值为3
    
  2. 找到**小于等于 t t t**的最后一个数:

    class Solution {
        public int binarySearch(int[] arr, int t) {
            int l = -1, r = arr.length;
            while (l + 1 != r) {
                int m = l + (r - l >> 1);
                if (arr[m] <= t) l = m; // judge条件:如果小于等于t,则让l=m;否则r=m
                else r = m;
            }
            return l; // 返回l
        }
    }
    
    Solution solution = new Solution();
    System.out.println(solution.binarySearch(new int[]{1, 2, 3, 5, 5, 5, 8, 9}, 5)); // 返回5,对应的值为5
    
  3. 找到**大于 t t t**的第一个数:(判断条件等价于找到小于等于 t t t的最后一个数)

    class Solution {
        public int binarySearch(int[] arr, int t) {
            int l = -1, r = arr.length;
            while (l + 1 != r) {
                int m = l + (r - l >> 1);
                if (arr[m] <= t) l = m; // judge条件:如果小于等于t,则让l=m;否则r=m
                else r = m;
            }
            return r; // 返回r
        }
    }
    
    Solution solution = new Solution();
    System.out.println(solution.binarySearch(new int[]{1, 2, 3, 5, 5, 5, 8, 9}, 5)); // 返回6,对应的值为8
    
  4. 找到大于等于 t t t的第一个数:(判断条件等价于找到小于等于 t t t的最后一个数)

    class Solution {
        public int binarySearch(int[] arr, int t) {
            int l = -1, r = arr.length;
            while (l + 1 != r) {
                int m = l + (r - l >> 1);
                if (arr[m] < t) l = m; // judge条件:如果小于t,则让l=m;否则r=m
                else r = m;
            }
            return r; // 返回r
        }
    }
    
    Solution solution = new Solution();
    System.out.println(solution.binarySearch(new int[]{1, 2, 3, 5, 5, 5, 8, 9}, 5)); // 返回3,对应的值为5 
    

参考文献:

  1. 二分查找为什么总是写错?
  2. 二分法的边界问题详细分析
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值