二分算法深度解析
二分算法深度解析
- 一、二分算法基础概念
- 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 算法基本流程
- 初始化:确定搜索区间的左右边界,通常为
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 基本二分查找(有序数组)
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 二分查找的常见错误
-
边界条件处理错误:
- 终止条件应为
left <= right
而非left < right
,否则可能漏掉最后一个元素。 - 更新边界时应为
left = mid + 1
和right = mid - 1
,避免陷入死循环。
- 终止条件应为
-
mid计算溢出:
- 错误写法:
mid = (left + right) / 2
,当left
和right
接近整数最大值时可能溢出。 - 正确写法:
mid = left + ((right - left) >> 1)
,使用减法和移位避免溢出。
- 错误写法:
-
返回条件错误:
- 找到目标值后应立即返回,避免继续无效搜索。
三、二分算法变种问题
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 二分答案的核心思想
二分答案是二分算法的一种高级应用,适用于求解"最大化最小值"或"最小化最大值"类型的问题。其核心思想是:
- 将求解问题转化为判断问题。
- 确定答案的可能范围(左边界和右边界)。
- 对可能的答案进行二分搜索,每次判断该答案是否可行。
- 根据判断结果调整搜索范围,最终得到最优解。
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!
觉得有用就点个赞
、收进收藏
夹吧!关注
我,获取更多干货~