二分查找:
又叫折半查找,从有序列表的初始候选区li[0:n]开始,通过对待查找的值与候选区中间值的比较,可以使候选区减少一半。
第一种简单写法:
逻辑:通过取中间位置的值来对比,不断调整候选区的范围,匹配到则范围该直到如果最后匹配成功。
由于left和right的值一直跟随mid一同变化,所以最后得到的mid值,是真正的位置
def binary_search2(li, val):
'''假设输入的是个升序的列表,获取中间位置的值跟要查找的值val作对比,
如果比val大,则取中间位置左边部分继续二分查找'''
left = 0
right = len(li) - 1
while left <= right: # 使用left和right变量控制候选区的范围,并没有改变传进来的列表
mid = (left + right) // 2
if li[mid] == val:
return mid
elif li[mid] > val: # 待查找的值在mid左侧
right = mid - 1
else: # 待查找的值在mid右侧
left = mid + 1
return None
li = [i for i in range(0, 10000000, 2)]
print(binary_search2(li, 39898))
第二种:递归写法1
这种写法的逻辑同样是选取中间位置的值出来做对比,不同的是这种会不断裁剪列表,确认不在匹配范围内的数据则会被舍弃,只保留可能存在该值的区域。
缺点:此种写法只能确认待查找的值是否在列表中,无法返回该值处在列表的位置。
def binary_search(li, val):
'''假设输入的是个升序的列表,获取中间位置的值跟要查找的值val作对比,
如果比val大,则取中间位置左边部分继续二分查找'''
'''递归会由于过程中会修改列表,所以如果直接返回mid的值,
并不是我们想要的索引值'''
if len(li) == 0:
return False
mid = len(li)//2
if li[mid] == val:
return True
if li[mid] > val:
li = li[:mid]
return binary_search(li, val)
else:
li = li[mid+1:]
return binary_search(li, val)
li = [i for i in range(10)]
print(li)
print(binary_search(li, 7))
第三种:递归写法2
上一个写法无法返回待查找值在列表中的位置,但可以稍微改进一下的,让它能准确返回待查找值的位置
修改逻辑:
上一个写法无法返回待查找值的位置主要是因为列表在查找的过程中被我们不断修剪,最后一次即使找到了,该位置也不是最初的位置。
所以如果我们能在修剪列表的过程中,把裁剪的长度记录下来,一直传递下去,最后一次查到时把它加上去,就是它最初在列表中的位置了。
需要注意的是,并不是每一次裁剪都需要把裁剪长度记录下来。只有当你取列表的右半部分时,才需要加上,当你取列表的左半部分时,不需要加上。
下图圈出部分为新增代码:
完整代码:
def binary_search(li, val, idx=0):
'''假设输入的是个升序的列表,获取中间位置的值跟要查找的值val作对比,
如果比val大,则取中间位置左边部分继续二分查找'''
if len(li) == 0:
return False
mid = len(li)//2
if li[mid] == val:
return idx + mid
if li[mid] > val:
li = li[:mid]
return binary_search(li, val, idx)
else:
idx += len(li[:mid+1])
li = li[mid+1:]
return binary_search(li, val, idx)
li = [i for i in range(60)]
print(li)
print(binary_search(li, 57))