二分查找是个很基础有很常用的算法,虽然他的代码很简单,但是其中却有几个十分容易混乱的地方,尤其是看不同教材或不同人写的二分代码,会有一些微小但关键的差异,自己写的时候也容易出错,写下这篇博客,主要为了将来自己糊涂时查阅,如果能帮助到其他人,那就是功德一件了。
代码
def BinarySearch(nums:list, target:int)->int:
'''
nums是一个有序的整形列表,target是一个整数。该函数用于查找target在nums中是否存在,如果存在返回其在列表中的下标,如果不存在返回-1
'''
left, right = 0, len(nums)#思考1:为什么这里是len(nums)而不是len(nums)-1
'''
知识点:运算符//
又叫地板初,自然是商向下取整,如15//2 = 7
'''
while left < right:#思考3:这里为什么是严格小于而非小于等于
mid = left + (right - left) // 2#思考2:为什么这里写成此种格式而非更简洁的mid = (left + right) // 2
if nums[mid] > target:
right = mid
elif nums[mid] < target:
left = mid + 1#思考4:为什么上面right不需要+1而left需要+1
else:
return mid
return -1
思考题解答:
首先二分法的核心要义在于搞清楚区间,并保持一致:即究竟是在哪一个区间内进行的搜索,左右端点的开闭情况尤其需要注意,一旦确定端点开闭情况,整个代码中都应保持一致。上面的代码搜索去间就是左闭右开,即搜索区间包括左端点而不含右端点。有了此等认知基础,我们可以进行接下来的讨论。
思考题1:
left, right = 0, len(nums)#思考1:为什么这里是len(nums)而不是len(nums)-1
**解答:**因为是左闭右开区间,所以搜索区间不包含右端点。如果写为len(nums) - 1则搜索不到列表最后一个元素。
思考题2:
mid = left + (right - left) // 2#思考2:为什么这里写成此种格式而非更简洁的mid = (left + right) // 2
**解答:**如果该列表容量巨大,则left + right运算后可能会出现溢出风险
思考题3:
while left < right:#思考3:这里为什么是严格小于而非小于等于
**解答:**因为是左闭右开区间,因而一个开区间端点与一个闭区间端点永远无法相等,因而写等号纯属做无用功。
- 如果写了等号会导致什么错误?
写了等号就会导致当left = right时会继续执行一次循环
思考题4:
while left < right:
if nums[mid] > target:
right = mid
elif nums[mid] < target:
left = mid + 1#思考4:为什么上面right不需要+1而left需要+1
else:
return mid
return -1
'''解答:
主要的作用在于避免重复搜索
if nums[mid] > target:说明目标值在left~mid中,因为本来mid就不等于target,自然可以将它当右端点,因为右端点是开区间,不会有重复搜索【left,mid)
elif nums[mid] < target:说明目标值在mid~right中,因为本来mid就不等于target,自然可以将它当左端点,但因为左端点是闭区间,因而可以将左端点写为mid+1,避免重复搜索mid【mid+1,right)
'''