目录
二分法(Binary Search)是一种高效的查找和逼近算法,核心思想是通过不断将问题规模对半分,缩小搜索范围,直到找到目标值或满足条件的结果。它广泛应用于有序数组查找、方程求根、最优化问题等场景。
一、基本思想
- 分治策略:将原问题分解为规模更小的子问题,每次排除一半不可能的解。
- 有序性前提:数据必须有序(如升序或降序),这是二分法高效的关键。
- 逐步逼近:通过比较中间值与目标值的关系,确定下一步搜索的区间。
二、二分法的典型应用
1. 有序数组查找
场景:在有序数组中快速找到目标值的位置。
步骤:
1.初始化左右指针
left=0, right=len(arr)-1
2.计算中间索引(避免整数溢出)
mid = left + (right - left) // 2
3.比较 中间值arr[mid]
与目标值 target
:
- 若
arr[mid] == target
,返回mid
。 - 若
arr[mid] < target
,说明目标在右半区,更新left = mid + 1
。 - 若
arr[mid] > target
,说明目标在左半区,更新right = mid - 1
。
4.重复直到 left > right
,未找到则返回 -1
。
代码示例:
def binary_search(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = left + (right - left) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
2. 数学求根问题
场景:求解连续函数 f(x)=0
的近似根(如 f(x)=x^3 - x - 2
)。
前提:函数在区间 [a, b]
内连续,且 f(a)
和 f(b)
异号(由介值定理保证存在根)。
步骤:
1.初始化区间 [a, b]
,确保 f(a) * f(b) < 0
。
2.计算中点 c = (a + b) / 2
。
3.判断 f(c)
的符号:
- 若
f(c) == 0
或达到精度要求,返回c
。 - 若
f(c) * f(a) < 0
,根在[a, c]
,更新b = c
。 - 否则根在
[c, b]
,更新a = c
。
4.重复直到区间长度小于预设精度(如 1e-6
)。
代码示例:
def find_root(f, a, b, epsilon=1e-6):
if f(a) * f(b) >= 0:
raise ValueError("区间 [a, b] 内无根或端点符号相同")
while b - a > epsilon:
c = (a + b) / 2
if f(c) == 0:
return c
if f(a) * f(c) < 0:
b = c
else:
a = c
return (a + b) / 2
三、时间复杂度与复杂度分析
- 时间复杂度:O(logn),每次将数据规模减半。
- 空间复杂度:O(1)(迭代实现)或 O(logn)(递归实现,取决于调用栈深度)。
四、优缺点
优点 | 缺点 |
高效,远快于线性搜索 O(n) | 要求数据必须有序 |
实现简单 | 仅适用于可随机访问的数据结构 |
适用于大规模数据 | 对非单调性问题不适用 |
五、常见变体与注意事项
- 查找第一个/最后一个出现的位置:通过调整条件处理重复元素。
- 旋转有序数组查找:结合二分法判断旋转点位置。
- 实数域上的精度控制:设置合理的终止条件(如区间长度小于
1e-6
)。 - 防止整数溢出:使用
mid = left + (right - left) // 2
而非(left + right) // 2
。
六、应用场景
- 数据库索引检索
- 游戏中的数值猜测(如猜数字)
- 调试中的“二分注释法”定位 Bug
- 机器学习中的超参数调优