数据结构与算法——二分查找

前言:

作为查找板块中最重要的算法和思想,二分查找是典型的一看就会,一做就废。要不要加=?要不要+1?要不要-1?这是二分查找最让人头痛的地方,作为一个思想不难,细节制胜的算法,拒绝死记硬背,本文将详细解析它的算法思路和原理。

目录

一、查找确切值

 1、左闭右闭区间

2、左闭右开区间

二、寻找左侧边界(最大值最小化or在单调序列中找x或其前驱)

三、寻找右侧边界(最小值最大化or在单调序列中找x或其后继)

四、二分查找的经典情景应用

五、二分的好处和局限性

1、好处

2、局限性

六、总结


一、查找确切值

 1、左闭右闭区间

顾名思义,该方法是在一段区间内寻找target目标值,并且左边界和右边界都可以取到。

左边界和右边界都可以取到,看似非常简单,但是是本方法控制边界和细节的关键所在。

int binarySearch(vector<int> &nums, int target) {
    //初始化区间
    int left = 0, right = nums.size() - 1;
    while (left <= right) 
   {
        //小细节:为什么不直接用(left+right)/2
        //这就涉及到程序执行先后顺序的问题了
        //直接先i+j的话有可能发生整数溢出的问题
        //而下面这种方法便可巧妙地一定程度上规避这种问题
        int mid = left + (right - left) / 2; 
        if (nums[mid] < target)   
            left = mid + 1;
        else if (nums[mid] > target) 
            right = mid - 1;
        else 
            return mid;
    }
    // 未找到目标元素,返回 -1
    return -1;
}
  • 因为是双闭区间,左右边界的值都可以取到,所以我们在取左右边界值时分别取它可以取到的最小和最大值,即left = 0, right = nums.size() - 1;
  • 因为是双闭区间,左右边界的值都可以取到,所以我们在缩小边界时左右边界都要进行移动

1.当target>nums[mid]时,说明target在[mid+1,right]上,因此left=mid+1;

2.当target<nums[mid]时,说明target在[left,mid-1]上,因此right=mid-1;

3.当target=nums[mid]时,说明已找到,返回target;

  • 为什么时left<=right?

同样,还是因为它是双闭区间,left=right时也可以存在区间[left,right]

看到这里可能还是理解的不太深刻,看接下来这个对比例子就明白了

2、左闭右开区间

int binarySearch(vector<int> &nums, int target) {
    //初始化区间
    int left = 0, right = nums.size();
    while (left < right) 
   {
        //小细节:为什么不直接用(left+right)/2
        //这就涉及到程序执行先后顺序的问题了
        //直接先i+j的话有可能发生整数溢出的问题
        //而下面这种方法便可巧妙地一定程度上规避这种问题
        int mid = left + (right - left) / 2; 
        if (nums[mid] < target)   
            left = mid + 1;
        else if (nums[mid] > target) 
            right = mid ;
        else 
            return mid;
    }
    // 未找到目标元素,返回 -1
    return -1;
}
  • 因为左闭右开,右边界的值不能确切取到,所以在设置right时要+1
  • 同样,还是因为左闭右开,右边界的值不能确切取到,所以在缩小区间时,right不用-1,反正它也取不到,直接等于mid即可,其实仔细想想效果是一样的
  • 同样,还还是因为左闭右开,右边界的值不能确切取到,所以left=right时不可能存在区间[left,right),自然也就无法继续了,故循环条件为left<right而没有=

根据上面的对比分析,我们可以发现,它的细节其实原理非常简单,一切细节的设计都是围绕区间的开闭展开,不过要特别说明的是,我们通常更习惯的是采用左闭右闭的双闭区间形式,这样左右都是对称操作的,更不容易出错。

本文下面提到的算法均采用双闭区间的形式。

二、寻找左侧边界(最大值最小化or在单调序列中找x或其前驱)

int binarySearchInsertion(vector<int> &nums, int target) {
    int l = 0, r = nums.size() - 1; 
    while (l <= r) {
        int m = l + (r - l) / 2;
        if (nums[m] < target) {
            l = m + 1;
        } else if (nums[m] > target) {
            r = m - 1; 
        } else {
            l = m - 1; 
        }
    }
    return l;
}
  • 当target>nums[m]或target<nums[m]时,说明还没有找到target,因此仍采用普通二分区间的缩小区间操作,使指针l和r向target靠近
  • 当nums[m]==target时,使r向小于target的元素靠近,因此用r=m-1来缩小区间(因为此处是寻找左侧边界,所以缩小边界时向左缩小)
  • 循环结束后,l指向最左边的target,r指向首个小于target的元素,所以l就是我们的左侧边界

不难发现,二分查找无非就是给指针i和j分别设定搜索目标,在不断的循环二分中,让l和r都逐渐逼近预先设定的目标,总的来说就是一个不断向目标奔赴的过程。

三、寻找右侧边界(最小值最大化or在单调序列中找x或其后继)

int binarySearchInsertion(vector<int> &nums, int target) {
    int l = 0, r = nums.size() - 1; 
    while (l <= r) {
        int m = l + (r - l) / 2;
        if (nums[m] < target) {
            l = m + 1;
        } else if (nums[m] > target) {
            r = m - 1; 
        } else {
            l = m +1; 
        }
    }
    return r;
}
  • 与找左侧边界的思想相同,在没有找到时都是进行普通的二分区间操作缩小区间,唯一变化的两个地方在于:
  • ①当nums[m]==target时,使l向大于target的元素靠近,因此用l=m+1来缩小区间(因为此处是寻找右侧边界,所以缩小边界时向右缩小)
  • ②循环结束后,r指向最右边的target,l指向首个大于target的元素,所以r就是我们的左侧边界

四、二分查找的经典情景应用

通过上面三种算法的介绍,我们已经初步掌握了二分的思想,并且这三种方法可以用来解决绝大部分二分的题目,不过要注意巧妙灵活的变形和运用

下面介绍二分的一种经典应用思想:
当我们容易得出结果具有单调性时,可以先从小到大假设结果,然后设置一个check函数看该答案是否能使题目成立,主要用到的二分思想是寻找左侧边界和右侧边界,因为题目通常是问最小值或最大值,下面是该思想的几道经典题目:

1.扫地机器人 - 蓝桥云课 (lanqiao.cn)

1.跳石头 - 蓝桥云课 (lanqiao.cn)

[蓝桥杯 2017 省 AB] 分巧克力 - 洛谷

1631. 最小体力消耗路径 - 力扣(LeetCode)

五、二分的好处和局限性

1、好处

  • 二分的查找效率非常高,每次都砍掉一半不可能的值,定量分析也可以得出
  • 无需额外空间

2、局限性

  • 仅限于有序数据
  • 仅适用于顺序表,不能用于链表
  • 当数据量较小时,线性查找反而比二分查找更快

六、总结

       本文详细介绍了二分的细节实现,这是一种很重要的思想,一定要多加练习,想不清楚时多画图进行实践验证。

参考资料

10.1   二分查找 - Hello 算法 (hello-algo.com)

  • 10
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值