Python 标准库之 bisect 二分查找
一、二分查找法🎏
二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法
二分查找法的时间复杂度为:O(㏒n)
特点
- 仅当 数组或列表 是 有序 的时候,二分查找法才管用。列如:通讯录(微信、QQ等)里的联系人是按字母顺序排序的,因此可以使用二分查找法来查找联系人
- 二分查找『 O(㏒n) 』的速度比 简单查找『 O(n) 』快,搜寻的元素越多,二分法就越快
- 另一方面,简单查找虽然慢,但代码编写起来更容易,且出现bug的几率会更小
简单查找
就是将 数组一个一个查找
def simple_search(array: list):
"""
简单查找示例
:param array: 数组列表
"""
target = input("数组范围 1~1000,请输入需要查找的值:")
if target.isdigit():
target = int(target)
else:
raise TypeError("请输入整数")
for i in array:
if i == target:
print(f"查找了{i}次找到目标值 {target}")
break
else:
print("需要查找的值不存在")
arr = list(range(1, 1001))
simple_search(arr)
二分查找
二分原理:将 数组分为 大致相等的两份,取中间值(中位数)进行比较,如果中间值等于 目标值,条件达成、查找成功,如果 目标值 小于 中间值,则在 数组左半部分 继续以这种方式搜索,如果 目标值 大于 中间值,则在数组右边部分搜索。
def binary_search(array: list):
"""
二分查找 while
:param array: 数组列表
"""
target = input("数组范围 1~1000,请输入需要查找的值:")
if target.isdigit():
target = int(target)
else:
raise TypeError("请输入整数")
low, high = 0, len(array) - 1 # 数组开头索引(默认为 0), 数组结尾索引
count = 0 # 寻找次数计数器
while low <= high: # 如果结尾索引大于等于开头索引 则不断循环
median = (low + high) // 2
count += 1
if array[median] > target: # 如果目标值比中间数小,位于中间数左边,更改high索引
high = median - 1
elif array[median] < target: # 如果目标值比中间数大,位于中间数右边,更改high索引
low = median + 1
else:
print(f"查找了{count}次找到目标值{target}")
break
arr = list(range(1, 1001))
binary_search(arr)
二、bisect 下函数
bisect 下的函数都有四个参数,『 a,x,lo=0,hi=len(a) 』
其中 a 和 x 为位置参数,a 传入的是 需要搜寻的数组,x 传入的是 目标值
lo 和 hi 为位置参数,lo 是 low 的缩写,代表数组搜寻范围中最小数的索引(查询范围开头索引),默认值为 0,即从第一个值开始查找;hi 是 high 的缩写,代表数组搜寻范围中最大数的索引(查询范围结尾索引),默认值为数组的长度
默认情况下整个 列表/数组 都会被使用
1) bisect.bisect(a, x, lo=0, hi=len(a))
返回 x 在 a 中 合适的插入点索引 以维持数组的有序, 如果 x 在 a 中已存在,那么返回的插入点是 a 中所有 已存在元素 x 的右侧。
注意:此函数是返回 插入点索引,并不是真的把数值 插入到列表数组中
arr = [1, 2, 3, 5]
arr_1 = [1, 2, 3, 4, 4, 4, 5]
print(bisect.bisect(arr, 4)) # 无重复值列表数组
print(bisect.bisect(arr_1, 4)) # 插入点索引 在所有已存在值的 右侧
print(bisect.bisect_right(arr_1, 4)) # 插入点索引 在所有已存在值的 右侧
3
6
6
2) bisect.bisect_right(a, x, lo=0, hi=len(a))
源码👇
可以看到,调用 bisect 实质上就是在调用 bisect_right,使用方法是相同的
3) bisect.bisect_left(a, x, lo=0, hi=len(a))
bisect_left 类似于 bisect_right / bisect ,但是不同在于 如果 x 在 a 中已存在,那么返回的插入点是 a 中所有 已存在元素 x 的左侧。
arr = [1, 2, 3, 5]
arr_1 = [1, 2, 3, 4, 4, 4, 5]
print(bisect.bisect(arr, 4)) # 无重复值列表数组
print(bisect.bisect(arr_1, 4)) # 插入点索引 在所有已存在值的 右侧
print(bisect.bisect_right(arr_1, 4)) # 插入点索引 在所有已存在值的 右侧
print(bisect.bisect_left(arr_1, 4)) # 插入点索引 在所有已存在值的 左侧
3
6
6
3
4) bisect.insort(a, x, lo=0, hi=len(a))
将 x 插入到一个 有序数组 a 里,并维持其有序, 如果 x 在 a 中已存在,那么将插入到 a 中所有 已存在元素 x 的右侧。
arr = [1, 2, 3, 4, 4, 4, 5]
bisect.insort(arr, 6) # 插入数值,若已存在则在所有已存在值的右侧插入
print(arr)
bisect.insort_right(arr, 5) # 插入数值在 所有已存在值的 右侧
print(arr)
[1, 2, 3, 4, 4, 4, 5, 6]
[1, 2, 3, 4, 4, 4, 5, 5, 6]
注意:搜索查询时间复杂度为:O(log n) ,而插入却是 O(n)
没有返回值
5) bisect.insort_right(a, x, lo=0, hi=len(a))
源码👇
可以看到,调用 insort 实质上就是在调用 insort_right,使用方法是相同的
6) bisect.insort_left(a, x, lo=0, hi=len(a))
将 x 插入到一个有序序列 a 里,并维持其有序。如果 a 有序的话,这相当于 a.insert(bisect.bisect_left(a, x, lo, hi), x)。要注意搜索是 O(log n) 的,插入却是 O(n) 的。
arr = [1, 2, 3, 4, 4, 4, 5]
bisect.insort(arr, 6) # 插入数值,若已存在则在所有已存在值的右侧插入
print(arr)
bisect.insort_right(arr, 4) # 插入数值在 所有已存在值的 右侧
print(arr)
bisect.insort_left(arr, 5) # 插入数值在 所有已存在值的 左侧
print(arr)
[1, 2, 3, 4, 4, 4, 5, 6]
[1, 2, 3, 4, 4, 4, 4, 5, 6]
[1, 2, 3, 4, 4, 4, 4, 5, 5, 6]
注意:搜索查询时间复杂度为:O(log n) ,而插入却是 O(n)
没有返回值
参考资料💌
- Python 官方文档