二分算法深度解析
-
- 一、二分算法基础概念
-
- [1.1 算法核心思想](#1.1 算法核心思想)
- [1.2 算法基本流程](#1.2 算法基本流程)
- [1.3 算法复杂度分析](#1.3 算法复杂度分析)
- [1.4 算法正确性证明](#1.4 算法正确性证明)
- 二、二分算法基础实现
-
- [2.1 基本二分查找(有序数组)](#2.1 基本二分查找(有序数组))
- [2.2 二分查找的递归实现](#2.2 二分查找的递归实现)
- [2.3 二分查找的常见错误](#2.3 二分查找的常见错误)
- 三、二分算法变种问题
-
- [3.1 查找第一个等于目标值的位置](#3.1 查找第一个等于目标值的位置)
- [3.2 查找最后一个等于目标值的位置](#3.2 查找最后一个等于目标值的位置)
- [3.3 查找第一个大于等于目标值的位置](#3.3 查找第一个大于等于目标值的位置)
- [3.4 查找最后一个小于等于目标值的位置](#3.4 查找最后一个小于等于目标值的位置)
- 四、二分算法在特殊数组中的应用
-
- [4.1 在旋转有序数组中搜索](#4.1 在旋转有序数组中搜索)
- [4.2 寻找峰值](#4.2 寻找峰值)
- [4.3 在二维有序矩阵中搜索](#4.3 在二维有序矩阵中搜索)
- 五、二分答案思想与应用
-
- [5.1 二分答案的核心思想](#5.1 二分答案的核心思想)
- [5.2 经典应用:分割数组的最大值](#5.2 经典应用:分割数组的最大值)
- [5.3 经典应用:寻找最小的k满足条件](#5.3 经典应用:寻找最小的k满足条件)
- 六、二分算法的优化与技巧
-
- [6.1 避免mid计算溢出](#6.1 避免mid计算溢出)
- [6.2 浮点数二分](#6.2 浮点数二分)
- [6.3 三分查找(适用于单峰函数)](#6.3 三分查找(适用于单峰函数))
- 七、二分算法实际应用场景
-
- [7.1 数据搜索与查询](#7.1 数据搜索与查询)
- [7.2 算法优化](#7.2 算法优化)
- [7.3 科学计算与数值分析](#7.3 科学计算与数值分析)
- [7.4 工程实践](#7.4 工程实践)
二分算法(Binary Search Algorithm)是一种高效的搜索策略,思想简洁而强大,从基本的有序数组搜索到复杂的算法问题求解,以其对数级的时间复杂度成为算法设计中的核心技术之一。本文我将系统讲解二分算法的核心原理、常见变种、实现细节、应用及优化技巧,带你全面掌握这一基础而重要的算法。
一、二分算法基础概念
1.1 算法核心思想
二分算法,又称二分查找或折半查找,其核心思想是通过不断将搜索区间减半,逐步缩小目标元素的可能位置,从而在对数时间内完成搜索。该算法的基本前提是数据结构有序,通过比较中间元素与目标值的大小关系,决定下一步搜索的区间,直至找到目标元素或确定目标元素不存在。
1.2 算法基本流程
- 初始化 :确定搜索区间的左右边界,通常为
left=0
和right=n-1
(假设数组长度为n)。 - 计算中间位置 :计算区间中间位置
mid = left + (right - left) / 2
,避免溢出的写法为mid = left + ((right - left) >> 1)
。 - 比较与缩小区间 :
- 若中间元素等于目标值,返回中间位置。
- 若中间元素大于目标值,更新右边界为
mid - 1
,在左半区间继续搜索。 - 若中间元素小于目标值,更新左边界为
mid + 1
,在右半区间继续搜索。
- 终止条件 :当
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时,算法正确判断元素是否存在。
- 假设搜索区间长度为k时算法正确,当长度为k+1时,通过比较中间元素,将问题分解为长度不超过k/2的子问题,由归纳假设知子问题正确,故原问题正确。
二、二分算法基础实现
2.1 基本二分查找(有序数组)
java
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)); // 输出3
System.out.println(bs.search(nums, 8)); // 输出-1
}
}
2.2 二分查找的递归实现
java
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 二分查找的常见错误
-
边界条件处理错误:
- 终止条件应为
left <= right
而非left < right
,否则可能漏掉最后一个元素。 - 更新边界时应为
left = mid + 1
和right = mid - 1
,避免陷入死循环。
- 终止条件应为
-
mid计算溢出:
- 错误写法:
mid = (left + right) / 2
,当left
和right
接近整数最大值时可能溢出。 - 正确写法:
mid = left + ((right - left) >> 1)
,使用减法和移位避免溢出。
- 错误写法:
-
返回条件错误:
- 找到目标值后应立即返回,避免继续无效搜索。
三、二分算法变种问题
3.1 查找第一个等于目标值的位置
java
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 查找最后一个等于目标值的位置
java
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 查找第一个大于等于目标值的位置
java
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 查找最后一个小于等于目标值的位置
java
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 在旋转有序数组中搜索
java
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 寻找峰值
java
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 在二维有序矩阵中搜索
java
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 二分答案的核心思想
二分答案是二分算法的一种高级应用,适用于求解"最大化最小值"或"最小化最大值"类型的问题。其核心思想是:
- 将求解问题转化为判断问题。
- 确定答案的可能范围(左边界和右边界)。
- 对可能的答案进行二分搜索,每次判断该答案是否可行。
- 根据判断结果调整搜索范围,最终得到最优解。
5.2 经典应用:分割数组的最大值
java
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满足条件
java
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计算溢出
java
// 错误写法(可能溢出)
int mid = (left + right) / 2;
// 正确写法(推荐)
int mid = left + ((right - left) >> 1);
// 另一种正确写法
int mid = left + (right - left) / 2;
6.2 浮点数二分
java
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 三分查找(适用于单峰函数)
java
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!
觉得有用就
点个赞
、收进收藏
夹吧!关注
我,获取更多干货~