【源码共读】Python 内置二分算法(折半算法)模块 bisect 详解

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

猜你喜欢

转载自blog.csdn.net/weixin_37780776/article/details/120254087
今日推荐