二分查找的个人朴素实用理解

背景

二分查找主要用于在有序数组中查找符合条件的特定值, 更进一步可以拓展到查找大于特定值的最小数和小于特定值的最大数的边界值问题, 在数据量很大的场景下合理利用有序或者说单调性这一特性大大提高查找效率, 能在对数时间内解决问题。

虽然理解起来很简单, 但是二分法是很常用也很巧妙的分治算法思想, 经常在某些复杂问题中可以利用数据有序特性来快速二分解决, 属于学起来简单但是能想到并且用到就很高效的算法, 姑且按照目前的个人理解记录一篇留待以后温习或补充完善。

主要思路

  • 数据必须是有序且存储在数组这种支持随机访问的连续数据结构, 在这个前提下才能通过下标随机访问中间位置数据, 且能根据判断结果来决定舍弃一半区域不会符合条件的数据, 比如在升序数组中如果arr[i]大于目标值就能确定所有下标大于i的区域值也都是大于目标值的, 下一步就应该在[0, i-1]范围去查找是否存在目标值;

  • 本文所有数据都假定按照升序排列;

  • 先介绍查找特定值是否存在的基础用途步骤:

    • 定义两个变量left和right, 初始值分别为0和arr.length - 1, 闭区间表示初始数组有效范围;
    • 取中间位置mid = (left + right) / 2, 一般为了防止加法数值溢出会采用 left + ((right - left) >> 1) 计算mid值;
    • 如果arr[mid]与目标值相等就直接返回结果;
    • 如果arr[mid]小于目标值就将right更新为mid-1重复第二步;
    • 如果arr[mid]大于目标值就将left更新为mid+1重复第二步;
  • 再介绍更进一步查找边界值(以大于特定值的最小数为例)的步骤:

    • 定义两个变量left和right, 初始值分别为0和length - 1, 在定义一个ans记录最终答案;
    • 取中间位置mid = (left + right) / 2;
    • 如果arr[mid]大于目标值, ans更新为mid, right更新为mid-1尝试去找是否存在更小的数符合条件;
    • 如果arr[mid]不大于目标值, left更新为mid+1, 重复第二步去更大值区域查找是否存在符合条件的数;
  • 总结二分查找法的几个核心步骤/要点:

    1. 确定答案所处范围;
    2. 确定答案所具备的单调性, 也就是二分过程中舍弃一半区域的依据;
    3. 确定二分判定函数逻辑, 用来决定下次二分是要收缩到哪一半区域进行;
    4. 上述两点确定之后只剩下按部就班二分直到退出循环即可, 基本上都能用统一模板写出代码;
  • 这里不再赘述网上常见的所谓开区间、左闭右开等其它实现方式, 最开始学习掌握二分法的时候确实被循环结束条件和到底该更新成mid还是mid + 1或是mid - 1绕晕, 后面醒悟过来重要的是掌握这种思路, 只要记住一种实现方式解决问题就行, 其它花里花哨实现方式最终都是同一个结果没必要再费心思区分学习, 理解思路后看到其它方式自然也就能明白无非是不同细节而已并不影响最终结果和性能。

代码示例

  • 在升序数组中查找等于特定值的基础代码示例:

      /**
       * 在升序数组中查找是否存在与目标值相等的元素下标, 如果不存在则返回-1
       * 不考虑重复数据, 返回任意一个下标即可
       */
      public static int binarySearch(int[] arr, int target) {
          int left = 0, right = arr.length - 1;
          while (left <= right) {
              int mid = left + ((right - left) >> 1);
              if (arr[mid] == target) {
                  return mid;
              }
              if (arr[mid] > target) {
                  right = mid - 1;
              }
              else {
                  left = mid + 1;
              }
          }
          return -1;
      }
    
  • 在升序数组中查找大于特定值的最小数的代码示例:

      /**
       * 在升序数组中查找大于目标值的最小元素下标, 如果不存在则返回-1
       */
      public static int binarySearch(int[] arr, int target) {
          int left = 0, right = arr.length - 1, ans = -1;
          while (left <= right) {
              int mid = left + ((right - left) >> 1);
              if (arr[mid] > target) {
                  // 直接在符合条件的分支中更新ans, 不用再思考循环结束时怎么用left或right来计算结果, 代码可读性最高
                  ans = mid;
                  right = mid - 1;
              }
              else {
                  left = mid + 1;
              }
          }
          return ans;
      }
    

相关题目

PS: 仅列出几个典型的基础题目, 后续可能再有补充但是二分法整体应用很广泛所以不会过多列举, 目前接触到的也不仅限于本人博客随笔"二分查找"标签下的那些题目, 感兴趣的也可以自行探索。

参考链接

posted on 2024-05-07 21:07  真不如烂笔头  阅读(10)  评论(0编辑  收藏  举报

导航