当前位置:   article > 正文

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

python 内置二分

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/177468
推荐阅读
相关标签
  

闽ICP备14008679号