二分法作为一种比较经典的查找算法,经常在面试中被提及。网上有递归和非递归两种方式,同时还有返回元素下标和不返回两种版本,这篇文章我们就分别用递归和非递归方式来实现返回元素下标的版本。
二分法查找
先来了解下二分法的思路。
二分法的特点是查找速度快,但处理对象必须是有序列表。如下图所示,整个列表是升序排列的,要查找48所在的位置(如果该元素存在的话)。首先找到居中的元素36,发现48比36大,于是在36右边的子列表中继续查找。又找到居中元素52,发现36比52小,于是在52左边的子列表中继续查找。此时的子列表只剩下了一个元素,刚好等于48。一共迭代3次,比从头遍历的5次要少。
代码实现
输入只有两个,有序列表和待查找的值,输出要么是找到的元素下标,要么是返回None表示没找到。
非迭代实现
非迭代版本的好处就是可以一直对同一个列表进行操作,所以只需要两个游标分别指向子列表的左右俩边界即可。
直接给出实现代码
def binary_search(list_: list, item):
'''non-recursive version'''
n = len(list_)
left = 0
right = n - 1
while left <= right:
mid = (left + right) // 2
if list_[mid] == item:
return mid
elif item < list_[mid]:
left = left
right = mid - 1
elif item > list_[mid]:
left = mid + 1
right = right
return None
这里的left
是最左边元素的下标,right
是最右边元素的下标。要注意这里的边界,最左边是从0开始。通过不停对left
和right
赋值,从而实现在循环中不停对新的子列表进行操作。当最后两个游标重合并且所指向的元素不是要寻找的值时,此时新的left
会比新的right
大1,跳出循环,返回None。
下面来验证下
if __name__ == '__main__':
list_ = [9, 11, 33, 36, 48, 52, 74, 87]
for item in [7,9,11,33,36,37,48,52,74,87,88,100]:
result = binary_search(list_, item)
print(result)
返回的结果如下,符合预期
None
0
1
2
3
None
4
5
6
7
None
None
迭代实现
迭代的缺点就是不能直接对原列表进行操作,这也是为什么在《python3实现快速排序算法图文详解》中,要传递两个游标的位置作为参数的原因。不过我们这里并不需要返回完整列表,只需要返回下标即可,所以新列表也无所谓。
直接看代码。
def binary_search(list_: list, item):
'''recursive version'''
n = len(list_)
if n < 1:
return None
left = 0
right = n - 1
mid = (left + right) // 2
if list_[mid] == item:
return mid
elif item < list_[mid]:
newList = list_[0:mid]
newResult=binary_search(newList,item)
if newResult is None:
return None
else:
return binary_search(newList, item)
elif item > list_[mid]:
newList = list_[mid+1:]
newResult=binary_search(newList,item)
if newResult is None:
return None
else:
return mid + binary_search(newList, item) + 1
主要逻辑就是,当中间值比查找值大的时候,直接返回子列表的查询结果。而当中间值比查找值小的时候,要注意用mid
加上新的查找值还要再加1。当子列表长度为0时退出迭代。
这里要注意边界条件,左边子列表是list_[0:mid]
,这里的最后mid
对应的值是不包括在子列表中的。
同时要注意None的处理,当最后一层迭代返回None时,再与前面的数值进行计算会报错。所以要进行额外的判断,当下一层迭代返回了None,也就是最后没找到时,本层也会返回None。
验证结果也同意没问题
None
0
1
2
3
None
4
5
6
7
None
None
时间复杂度
最好的情况是直接第一步就在中点找到,而最坏的情况是一直二分到了最后,一共二分了logn
次,所以时间复杂度为O(logn)
。
完整代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time: 2020-Jul-21
# @Author: xiaofu
# def binary_search(list_: list, item):
# '''non-recursive version'''
# n = len(list_)
# left = 0
# right = n - 1
# while left <= right:
# mid = (left + right) // 2
# if list_[mid] == item:
# return mid
# elif item < list_[mid]:
# left = left
# right = mid - 1
# elif item > list_[mid]:
# left = mid + 1
# right = right
# return None
def binary_search(list_: list, item):
'''recursive version'''
n = len(list_)
if n < 1:
return None
left = 0
right = n - 1
mid = (left + right) // 2
if list_[mid] == item:
return mid
elif item < list_[mid]:
newList = list_[0:mid]
newResult=binary_search(newList,item)
if newResult is None:
return None
else:
return binary_search(newList, item)
elif item > list_[mid]:
newList = list_[mid+1:]
newResult=binary_search(newList,item)
if newResult is None:
return None
else:
return mid + binary_search(newList, item) + 1
if __name__ == '__main__':
list_ = [9, 11, 33, 36, 48, 52, 74, 87]
for item in [7,9,11,33,36,37,48,52,74,87,88,100]:
result = binary_search(list_, item)
print(result)
我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。