当前位置: 首页 > news >正文

二分算法深度解析

二分算法深度解析

    • 一、二分算法基础概念
      • 1.1 算法核心思想
      • 1.2 算法基本流程
      • 1.3 算法复杂度分析
      • 1.4 算法正确性证明
    • 二、二分算法基础实现
      • 2.1 基本二分查找(有序数组)
      • 2.2 二分查找的递归实现
      • 2.3 二分查找的常见错误
    • 三、二分算法变种问题
      • 3.1 查找第一个等于目标值的位置
      • 3.2 查找最后一个等于目标值的位置
      • 3.3 查找第一个大于等于目标值的位置
      • 3.4 查找最后一个小于等于目标值的位置
    • 四、二分算法在特殊数组中的应用
      • 4.1 在旋转有序数组中搜索
      • 4.2 寻找峰值
      • 4.3 在二维有序矩阵中搜索
    • 五、二分答案思想与应用
      • 5.1 二分答案的核心思想
      • 5.2 经典应用:分割数组的最大值
      • 5.3 经典应用:寻找最小的k满足条件
    • 六、二分算法的优化与技巧
      • 6.1 避免mid计算溢出
      • 6.2 浮点数二分
      • 6.3 三分查找(适用于单峰函数)
    • 七、二分算法实际应用场景
      • 7.1 数据搜索与查询
      • 7.2 算法优化
      • 7.3 科学计算与数值分析
      • 7.4 工程实践

二分算法(Binary Search Algorithm)是一种高效的搜索策略,思想简洁而强大,从基本的有序数组搜索到复杂的算法问题求解,以其对数级的时间复杂度成为算法设计中的核心技术之一。本文我将系统讲解二分算法的核心原理、常见变种、实现细节、应用及优化技巧,带你全面掌握这一基础而重要的算法。

一、二分算法基础概念

1.1 算法核心思想

二分算法,又称二分查找或折半查找,其核心思想是通过不断将搜索区间减半,逐步缩小目标元素的可能位置,从而在对数时间内完成搜索。该算法的基本前提是数据结构有序,通过比较中间元素与目标值的大小关系,决定下一步搜索的区间,直至找到目标元素或确定目标元素不存在。

1.2 算法基本流程

  1. 初始化:确定搜索区间的左右边界,通常为left=0right=n-1(假设数组长度为n)。
  2. 计算中间位置:计算区间中间位置mid = left + (right - left) / 2,避免溢出的写法为mid = left + ((right - left) >> 1)
  3. 比较与缩小区间
    • 若中间元素等于目标值,返回中间位置。
    • 若中间元素大于目标值,更新右边界为mid - 1,在左半区间继续搜索。
    • 若中间元素小于目标值,更新左边界为mid + 1,在右半区间继续搜索。
  4. 终止条件:当left > right时,搜索区间为空,目标元素不存在。

1.3 算法复杂度分析

  • 时间复杂度:二分算法的时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn),其中n为搜索空间的大小。每次迭代将搜索区间减半,因此最多需要 log ⁡ 2 n \log_2 n log2n次迭代。
  • 空间复杂度:通常为 O ( 1 ) O(1) O(1),仅使用常数级别的额外空间。

1.4 算法正确性证明

二分算法的正确性可以通过数学归纳法证明:

  1. 当搜索区间长度为1时,算法正确判断元素是否存在。
  2. 假设搜索区间长度为k时算法正确,当长度为k+1时,通过比较中间元素,将问题分解为长度不超过k/2的子问题,由归纳假设知子问题正确,故原问题正确。

二、二分算法基础实现

2.1 基本二分查找(有序数组)

public class BinarySearch {/*** 在有序数组中查找目标值的索引* @param nums 有序数组* @param target 目标值* @return 目标值的索引,不存在返回-1*/public int search(int[] nums, int target) {int left = 0, right = nums.length - 1;while (left <= right) {int mid = left + ((right - left) >> 1); // 避免溢出的mid计算if (nums[mid] == target) {return mid;} else if (nums[mid] < target) {left = mid + 1;} else {right = mid - 1;}}return -1;}public static void main(String[] args) {BinarySearch bs = new BinarySearch();int[] nums = {1, 3, 5, 7, 9, 11, 13};System.out.println(bs.search(nums, 7));  // 输出3System.out.println(bs.search(nums, 8));  // 输出-1}
}

2.2 二分查找的递归实现

public class RecursiveBinarySearch {public int search(int[] nums, int target) {return recursiveSearch(nums, target, 0, nums.length - 1);}private int recursiveSearch(int[] nums, int target, int left, int right) {if (left > right) {return -1;}int mid = left + ((right - left) >> 1);if (nums[mid] == target) {return mid;} else if (nums[mid] < target) {return recursiveSearch(nums, target, mid + 1, right);} else {return recursiveSearch(nums, target, left, mid - 1);}}
}

2.3 二分查找的常见错误

  1. 边界条件处理错误

    • 终止条件应为left <= right而非left < right,否则可能漏掉最后一个元素。
    • 更新边界时应为left = mid + 1right = mid - 1,避免陷入死循环。
  2. mid计算溢出

    • 错误写法:mid = (left + right) / 2,当leftright接近整数最大值时可能溢出。
    • 正确写法:mid = left + ((right - left) >> 1),使用减法和移位避免溢出。
  3. 返回条件错误

    • 找到目标值后应立即返回,避免继续无效搜索。

三、二分算法变种问题

3.1 查找第一个等于目标值的位置

public class FirstEqual {public int searchInsert(int[] nums, int target) {int left = 0, right = nums.length - 1;while (left <= right) {int mid = left + ((right - left) >> 1);if (nums[mid] >= target) {right = mid - 1;} else {left = mid + 1;}}return left;}public static void main(String[] args) {FirstEqual fe = new FirstEqual();int[] nums = {1, 3, 5, 7, 9};System.out.println(fe.searchInsert(nums, 6));  // 输出3}
}

3.2 查找最后一个等于目标值的位置

public class LastEqual {public int search(int[] nums, int target) {int left = 0, right = nums.length - 1;while (left <= right) {int mid = left + ((right - left) >> 1);if (nums[mid] <= target) {left = mid + 1;} else {right = mid - 1;}}return right;}
}

3.3 查找第一个大于等于目标值的位置

public class FirstGreaterEqual {public int search(int[] nums, int target) {int left = 0, right = nums.length - 1;while (left <= right) {int mid = left + ((right - left) >> 1);if (nums[mid] >= target) {right = mid - 1;} else {left = mid + 1;}}return left;}
}

3.4 查找最后一个小于等于目标值的位置

public class LastLessEqual {public int search(int[] nums, int target) {int left = 0, right = nums.length - 1;while (left <= right) {int mid = left + ((right - left) >> 1);if (nums[mid] <= target) {left = mid + 1;} else {right = mid - 1;}}return right;}
}

四、二分算法在特殊数组中的应用

4.1 在旋转有序数组中搜索

public class SearchInRotatedArray {public int search(int[] nums, int target) {if (nums == null || nums.length == 0) {return -1;}int left = 0, right = nums.length - 1;while (left <= right) {int mid = left + ((right - left) >> 1);if (nums[mid] == target) {return mid;}// 左半部分有序if (nums[left] <= nums[mid]) {if (nums[left] <= target && target < nums[mid]) {right = mid - 1;} else {left = mid + 1;}} else { // 右半部分有序if (nums[mid] < target && target <= nums[right]) {left = mid + 1;} else {right = mid - 1;}}}return -1;}
}

4.2 寻找峰值

public class FindPeakElement {public int findPeakElement(int[] nums) {int left = 0, right = nums.length - 1;while (left < right) {int mid = left + ((right - left) >> 1);if (nums[mid] > nums[mid + 1]) {// 峰值在左半部分right = mid;} else {// 峰值在右半部分left = mid + 1;}}return left;}
}

4.3 在二维有序矩阵中搜索

public class Search2DMatrix {public boolean searchMatrix(int[][] matrix, int target) {if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {return false;}int rows = matrix.length;int cols = matrix[0].length;int left = 0, right = rows * cols - 1;while (left <= right) {int mid = left + ((right - left) >> 1);int row = mid / cols;int col = mid % cols;if (matrix[row][col] == target) {return true;} else if (matrix[row][col] < target) {left = mid + 1;} else {right = mid - 1;}}return false;}
}

五、二分答案思想与应用

5.1 二分答案的核心思想

二分答案是二分算法的一种高级应用,适用于求解"最大化最小值"或"最小化最大值"类型的问题。其核心思想是:

  1. 将求解问题转化为判断问题。
  2. 确定答案的可能范围(左边界和右边界)。
  3. 对可能的答案进行二分搜索,每次判断该答案是否可行。
  4. 根据判断结果调整搜索范围,最终得到最优解。

5.2 经典应用:分割数组的最大值

public class SplitArray {public int splitArray(int[] nums, int m) {int left = 0, right = 0;for (int num : nums) {left = Math.max(left, num); // 左边界为数组中的最大值right += num; // 右边界为数组总和}while (left < right) {int mid = left + ((right - left) >> 1);if (isValid(nums, m, mid)) {right = mid;} else {left = mid + 1;}}return left;}private boolean isValid(int[] nums, int m, int maxSum) {int count = 1;int currentSum = 0;for (int num : nums) {if (currentSum + num > maxSum) {count++;currentSum = num;if (count > m) {return false;}} else {currentSum += num;}}return true;}
}

5.3 经典应用:寻找最小的k满足条件

public class FindMinK {public int findMinK(int[] nums, int target) {int left = 0, right = nums.length - 1;int result = -1;while (left <= right) {int mid = left + ((right - left) >> 1);if (isSatisfied(nums, mid, target)) {result = mid;right = mid - 1; // 寻找更小的k} else {left = mid + 1;}}return result;}private boolean isSatisfied(int[] nums, int k, int target) {// 判断k是否满足条件// 具体逻辑根据问题而定return nums[k] >= target;}
}

六、二分算法的优化与技巧

6.1 避免mid计算溢出

// 错误写法(可能溢出)
int mid = (left + right) / 2;// 正确写法(推荐)
int mid = left + ((right - left) >> 1);// 另一种正确写法
int mid = left + (right - left) / 2;

6.2 浮点数二分

public class FloatingBinarySearch {public double findRoot(double n) {double left = 0, right = n;// 浮点数二分需要设置精度final double EPS = 1e-10;while (right - left > EPS) {double mid = left + (right - left) / 2;if (mid * mid < n) {left = mid;} else {right = mid;}}return left;}
}

6.3 三分查找(适用于单峰函数)

public class TernarySearch {public double findMax(double[] nums) {double left = 0, right = nums.length - 1;final double EPS = 1e-10;while (right - left > EPS) {double mid1 = left + (right - left) / 3;double mid2 = right - (right - left) / 3;if (nums[(int)mid1] < nums[(int)mid2]) {left = mid1;} else {right = mid2;}}return nums[(int)left];}
}

七、二分算法实际应用场景

7.1 数据搜索与查询

  • 数据库索引查询:B树和B+树的查找过程本质上是多路二分搜索。
  • 有序数组中的快速查找:如Java的Arrays.binarySearch()方法。

7.2 算法优化

  • 快速排序中的分区优化:通过二分思想优化基准值的选择。
  • 动态规划中的状态优化:如使用二分查找减少状态转移的时间。

7.3 科学计算与数值分析

  • 求解方程近似解:如二分法求解非线性方程的根。
  • 数值积分与优化:结合二分思想进行数值计算。

7.4 工程实践

  • 网络协议中的滑动窗口大小调整。
  • 系统资源分配中的阈值确定。
  • 机器学习中的超参数优化。

That’s all, thanks for reading!
觉得有用就点个赞、收进收藏夹吧!关注我,获取更多干货~

http://www.lryc.cn/news/571310.html

相关文章:

  • 简说 python
  • C++ vector(2)
  • 【编译工具】CodeRider 2.0:驭码 CodeRider 2.0 全流程智能研发协作平台深度技术测评报告
  • Java在IDEA中终端窗口输出正常,但打包成JAR后中文乱码问题
  • 『大模型笔记』第3篇:多长的 Prompt 会阻塞其他请求?优化策略解析
  • Java线程池全面解析:原理、实现与最佳实践
  • Socket 编程 UDP
  • 【Linux】UDP与TCP协议
  • Kubernetes RDMA 概述与实战(大模型场景)
  • UE5 游戏模板 —— Puzzle 拼图游戏
  • 【配置教程】新版OpenCV+Android Studio环境配置(4.11测试通过)
  • 在线教学课程视频AI智能大纲代码与演示
  • 【Docker安装PostgreSQL】psql:致命错误: 用户 Password 认证失败
  • 在 MongoDB 中复制一个 collection(集合)
  • 以下是系统化的 Python基础学习框架,分为4个核心阶段,结合理论与实践,适合零基础快速入门并建立扎实的编程基础:
  • 【WPF】WPF ComboBox 数据驱动不刷新?SelectedItem 与 SelectedIndex 解析!
  • 什么是数据仓库的ETL
  • TortoiseSVN迁移到本地git
  • Tomcat 核心配置解析:4 大文件、乱码处理、端口与 Manager 配置
  • 企业ERP致胜秘籍:从流程革新到智能决策
  • 关系数据库-数据库事务处理与ACID原则
  • Android 开发问题:CardView 的阴影效果会受到父容器的裁切
  • STM32 实现解析自定义协议
  • HTTP 请求中的 `Content-Type` 类型详解及前后端示例(Vue + Spring Boot)
  • 为什么您应该停止使用 1080 玻璃
  • eBPF(6)--uprobe
  • MRI学习笔记-BrainNet Viewer
  • python大学生志愿者管理系统-高校志愿者管理信息系统
  • llama_index chromadb实现RAG的简单应用
  • 基于Java的Excel列数据提取工具实现