二分查找与循环不变量
二分法和循环不变量:
循环不变量的意义:
变量的意义要不变,始终要在有效的范围内,这是保证程序正确性的一种方法
循环不变量主要用来帮助我们理解算法的正确性。关于循环不变式,我们必须证明三条性质:
- 初始化:循环的第一次迭代之前,它为真。
- 保持:如果循环的某次迭代之前它为真,那么下次迭代之前它仍为真。
- 终止:在循环终止时,不变式为我们提供一个有用的性质,该性质有助于证明算法是正确的。
二分法中使用循环不变量来确定循环终止条件和区间更新式的写法
二分查找一般由三个主要部分组成:
预处理 —— 如果集合未排序,则进行排序。
二分查找 —— 使用循环或递归在每次比较后将查找空间划分为两半。
后处理 —— 在剩余空间中确定可行的候选者。
为什么二分法经常写乱?
我们写二分法时常常无法确定循环终止条件是否取等号,不知道区间更新式成right = mid还是right = mid - 1?
写二分法经常写乱,是因为区间的定义没有想清楚,二分查找的边界条件(区间)的定义就是不变量。
怎样使用不变量原则确定循环终止条件和区间更新式
在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,让循环内的区间始终满足定义的性质,这就是循环不变量规则。
区间的定义一般为两种,左闭右闭即[left, right](target一定在[left, right]内)或者左闭右开即[left, right),即target一定在[left, right)内。
我们需要在二分的初始化和保持(while循环)中使得循环终止条件和区间更新式满足:target一定在区间内。
一个力扣题目:35. Search Insert Position
import sys
class Solution:
def searchInsert(self, nums, target: int) -> int:
'''
二分查找的最终结果与target的大小关系是不确定的,
所以要加条件判断,二分还是感觉有点慌,终止条件和上下取整不确定
'''
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] > target:
right = mid - 1
elif nums[mid] < target:
left = mid + 1
print(left, mid, right)
'''
这一句带逻辑的return其实是额外的,本身不属于二分,两点需要注意
1. 原版的二分查找不会找插入位置,而是找不到就直接返回-1,
最后插入哪个位置要看问题的逻辑,对推出循环的区间做一点小的逻辑分析
2. 循环中一直满足:不变量(区间)[left, right]包括了target,因此
因此一定要不满足这个性质时才退出循环,(left==right满足性质,因此在循环内)
'''
return mid + 1 if nums[mid] < target else mid
if __name__ == '__main__':
obj = Solution()
nums = [1,3,5,6]
target = 5
nums = [1,3,5,6]
target = 2
nums = [1,3,5,6]
target = 4
sys.stdout.write(str(obj.searchInsert(nums, target)))
参考:https://kelvinmao.github.io/利用循环不变式写出正确的二分查找及其衍生算法/
为什么官方的二分法的题解很多都是写的low + (high - low) // 2 而不是 (high + low) // 2
low+high在low和high特别大的时候可能会造成溢出,使用减法避免了溢出发生
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?