文章目录
1. 简介
二分算法是编程中非常常见和常用的算法,即便如此我之前也没有想到 Python 官方竟然有一个专门的模块实现了该算法的大部分操作,该模块为 bisect
。
实际上,该模块提供的函数主要用于这样的场景,即在每次使用其中的函数向列表中插入一个元素后,无需对列表进行排序的同时可以保证该列表一直都是有序的。这对于列表元素很多,且元素之间的比较非常耗时的场景能很好的提高代码的效率。
2. 源码
"""Bisection algorithms."""
def insort_right(a, x, lo=0, hi=None):
"""Insert item x in list a, and keep it sorted assuming a is sorted.
If x is already in a, insert it to the right of the rightmost x.
Optional args lo (default 0) and hi (default len(a)) bound the
slice of a to be searched.
"""
lo = bisect_right(a, x, lo, hi)
a.insert(lo, x)
def bisect_right(a, x, lo=0, hi=None):
"""Return the index where to insert item x in list a, assuming a is sorted.
The return value i is such that all e in a[:i] have e <= x, and all e in
a[i:] have e > x. So if x already appears in the list, a.insert(x) will
insert just after the rightmost x already there.
Optional args lo (default 0) and hi (default len(a)) bound the
slice of a to be searched.
"""
if lo < 0:
raise ValueError('lo must be non-negative')
if hi is None:
hi = len(a)
while lo < hi:
mid = (lo+hi)//2
if x < a[mid]: hi = mid
else: lo = mid+1
return lo
def insort_left(a, x, lo=0, hi=None):
"""Insert item x in list a, and keep it sorted assuming a is sorted.
If x is already in a, insert it to the left of the leftmost x.
Optional args lo (default 0) and hi (default len(a)) bound the
slice of a to be searched.
"""
lo = bisect_left(a, x, lo, hi)
a.insert(lo, x)
def bisect_left(a, x, lo=0, hi=None):
"""Return the index where to insert item x in list a, assuming a is sorted.
The return value i is such that all e in a[:i] have e < x, and all e in
a[i:] have e >= x. So if x already appears in the list, a.insert(x) will
insert just before the leftmost x already there.
Optional args lo (default 0) and hi (default len(a)) bound the
slice of a to be searched.
"""
if lo < 0:
raise ValueError('lo must be non-negative')
if hi is None:
hi = len(a)
while lo < hi:
mid = (lo+hi)//2
if a[mid] < x: lo = mid+1
else: hi = mid
return lo
# Overwrite above definitions with a fast C implementation
try:
from _bisect import *
except ImportError:
pass
# Create aliases
bisect = bisect_right
insort = insort_right
3. 分析
实际上,该模块非常简单,只是针对二分算法,通过 4 4 4 个函数实现了 二分算法的 4 4 4 个常用操作:
3.1 bisect_left(a, x, lo=0, hi=None)
该函数在假定 a
为有序序列的前提下,返回一个索引 i
,该索引确定了应该在何处插入元素 x
后可以确保 a
仍是有序序列。
该函数的返回值 i
满足如下性质:
- 对于
a[:i]
中任意元素e
,都有e < x
; - 对于
a[i:]
中任意元素e
,都有e >= x
。
因此,如果 x
已经存在于 a
中,那么 a.insert(i, x)
会在 a
中已有的 x
中,在最左边 x
的前一个位置处插入 x
。
其中可选的参数 lo
(默认为 0 0 0)以及 hi
(默认为 len(a)
)用于确定二分查找的边界。
3.2 bisect_right(a, x, lo=0, hi=None)
该函数在假定 a
为有序序列的前提下,返回一个索引 i
,该索引确定了应该在何处插入元素 x 后可以确保 a
仍是有序序列。
该函数的返回值 i
满足如下性质:
- 对于
a[:i]
中任意元素e
,都有e <= x
; - 对于
a[i:]
中任意元素e
,都有e > x
。
因此,如果 x
已经存在于 a
中,那么 a.insert(i, x)
会在 a
中已有的 x
中,在最右边 x
的后一个位置处插入 x
。
其中可选的参数 lo
(默认为 0 0 0)以及 hi
(默认为 len(a)
)用于确定二分查找的边界。
3.3 insort_left(a, x, lo=0, hi=None)
该函数将元素 x
插入 a
中,确保 插入前 a
是有序的前提下,插入后 a
也是有序的。
如果 x
已经存在于 a
中,那么元素 x
将会被插在最左侧那个 x
的前一个位置。
其中可选的参数 lo
(默认为 0 0 0)以及 hi
(默认为 len(a)
)用于确定二分查找的边界。
3.4 insort_right(a, x, lo=0, hi=None)
该函数将元素 x
插入 a
中,确保 插入前 a
是有序的前提下,插入后 a
也是有序的。
如果 x
已经存在于 a
中,那么元素 x
将会被插在最右侧那个 x
的后一个位置。
其中可选的参数 lo
(默认为 0 0 0)以及 hi
(默认为 len(a)
)用于确定二分查找的边界。
4. 实现
下面是自定义实现:
def binary_search_left(self, nums: List[int], target: int) -> int:
low, high = 0, len(nums) - 1
while low <= high:
mid = low + (high - low) // 2
if nums[mid] == target:
high = mid - 1 # 缩小搜索的上界
elif nums[mid] < target:
low = mid + 1
else:
high = mid - 1
return low