如何写一个正确的二分法代码

本文深入探讨了二分搜索的基本原理与常见错误,分享了实用的经验法则,包括跨步移动、left<=right惯例及如何正确处理边界条件。文章还详细介绍了二分搜索的三种变种:lower_bound和upper_bound的实现,寻找第一个或最后一个与key相等的元素,以及查找最后一个等于或小于key的元素。这些技巧和代码示例有助于提高算法理解和编程效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

二分原理简单,但是实现起来经常出错,下面一些经验很有用:

  1. 跨步移动(left=mid+1,right=mid-1)是惯例,跨步移动保证每次搜索范围至少缩小1。如果不跨步,不管left<=right还是left<right都可能死循环,除非能保证提前退出的情况下才可以不跨步移动。
  2. 跨步移动的前提下,假设搜索范围是闭区间[0,n-1],left退出范围是[0,n],right是[-1,n-1]
  3. 跨步移动是可以牺牲掉一边的,如果两边都牺牲跨步移动,同时left<=right,就会陷入死循环。牺牲跨步移动的例子:https://blog.youkuaiyun.com/taoqick/article/details/100052403
  4.  left<=right情况下,如果依靠循环退出,left刚好在right右边一个,left<right退出刚好两个是相等的。选择<还是<=主要是等一下思考一下codes在相等情况下要不要再走一遍。left<=right更常用一些
  5. 分的时候,重点考虑一下if里面等于的情况!!!
  6. 实际操作中需要迅速理解位置x和目标函数f(x)之间的单调性关系,例如:https://blog.youkuaiyun.com/taoqick/article/details/106783353

有了跨步移动left<=right两个惯例,二分不确定性降低很多,只要思考if的时候甩哪半if里面等于的情况就好,这取决于数据单调性、重复性、是否一定有解、退出范围等各种问题。在这两个惯例的前提下,二分代码简洁而且有深刻含义,具体如下:

变种一:lower_bound和upper_bound (https://blog.youkuaiyun.com/taoqick/article/details/23260157

  1. lower_bound,重复数据里大于等于target的第一个元素,插入target的第一个恰当位置:
     
    int searchFirstEqualOrLarger(int *arr, int n, int key)
    {
        int left=0, right=n-1;
        while(left<=right) 
        {
            int mid = left+(right-left)/2;
            if(arr[mid] >= key) 
                right = mid-1;
            else if (arr[mid] < key) 
                left = mid+1;
        }
        return left;
    }

     

  2. upper_bound,重复数据里大于target的第一个元素,插入target的最后一个恰当位置:
     
    int searchFirstLarger(int *arr, int n, int key)
    {
        int left=0, right=n-1;
        while(left<=right)
        {
            int mid = left+(right-left)/2;
            if(arr[mid] > key) 
                right = mid-1;
            else if (arr[mid] <= key) 
                left = mid+1;
        }
        return left;
    }

     

变种二:

  1. 找出第一个与key相等的元素
     
    int searchFirstEqual(int *arr, int n, int key)
    {
        int left = 0, right = n-1;
        while(left<=right)
        {
            int mid = left+(right-left)/2;
            if(arr[mid] >= key) 
                right = mid - 1;
            else if(arr[mid] < key) 
                left = mid + 1;
        }
        if( left < n && arr[left] == key) 
                return left;
        return -1;
    }

     

  2. 找出最后一个与key相等的元素
     
    int searchLastEqual(int *arr, int n, int key)
    {
        int left = 0, right = n-1;
        while(left<=right) {
            int mid = (left+right)/2;
            if(arr[mid] > key) 
                right = mid - 1;
            else if(arr[mid] <= key) 
                left = mid + 1; 
        }
        if( right>=0 && arr[right] == key) 
                return right;
        return -1;
    }

     

变种三:

  1. 查找最后一个等于或者小于key的元素
     
    int searchLastEqualOrSmaller(int *arr, int n, int key)
    {
        int left=0, right=n-1;
        while(left<=right) 
        {
            int mid = left+(right-left)/2;
            if(arr[m] > key) 
                 right = m-1;
            else if (arr[m] <= key) 
                 left = m+1;
        }
        return right;
    }

     

  2. 查找最后一个小于key的元素
    int searchLastSmaller(int *arr, int n, int key)
    {
        int left=0, right=n-1;
        while(left<=right) {
            int mid = left+(right-left)/2;
            if(arr[mid] >= key) 
                 right = mid-1;
            else if (arr[mid] < key) 
                 left = mid+1;
        }
        return right;
    }



     
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值