该用 bisect 而非手写 while 循环:查插入位置或有序插入时,bisect 更快、更安全;其返回合法索引(0 到 len(lst)),不抛异常,但需额外比较判断存在性。

什么时候该用 bisect 而不是手写 while 循环
如果你只是查一个值在已排序列表里的插入位置,或者想保持列表有序地插入新元素,bisect 就是标准解法——它底层是 C 实现的,比纯 Python 的 while 循环快,而且逻辑无死角。手写 while 容易漏掉边界条件,尤其在找左边界/右边界时。
常见错误现象:IndexError、死循环、返回位置比预期大 1 或小 1;本质是没统一好「是否包含等于」和「搜索区间开闭」。
-
bisect.bisect_left(lst, x)返回第一个 ≥ x 的索引,适合找左边界 -
bisect.bisect_right(lst, x)返回第一个 > x 的索引,等价于bisect.bisect(lst, x) - 插入用
bisect.insort_left()或bisect.insort_right(),别自己lst.insert(pos, x)—— 后者是 O(n),前者也是 O(n) 但语义清晰、不易错
bisect 找不到值时返回什么
它不关心“找没找到”,只管“该插在哪”。哪怕 x 不在列表里,bisect_left 和 bisect_right 都会返回合法索引(0 到 len(lst) 之间),不会抛异常。
所以判断“是否存在”得额外比较:pos 。漏掉 <code>pos 就会触发 <code>IndexError: list index out of range。
立即学习“Python免费学习笔记(深入)”;
- 错误写法:
if lst[bisect.bisect_left(lst, x)] == x:—— 没检查越界 - 正确写法:
pos = bisect.bisect_left(lst, x); if pos != len(lst) and lst[pos] == x: -
bisect对空列表也安全,返回 0
手写 while 二分必须守住的三个边界细节
手动实现唯一合理场景:需要定制比较逻辑(比如按对象某个字段比)、或做范围扩展(如找最近邻、满足某条件的最远位置)。但每一步都容易翻车。
核心陷阱在 while 条件和更新方式不匹配。用闭区间 [left, right] 最不容易错,但很多人混用开闭。
- 闭区间写法:初始化
left, right = 0, len(lst) - 1,while 条件是left ,更新必须是 <code>left = mid + 1和right = mid - 1 - 开区间写法:初始化
left, right = 0, len(lst),while 条件是left ,更新是 <code>left = mid + 1和right = mid—— 注意右边不减 1 - mid 计算一律用
left + (right - left) // 2,避免整数溢出(虽然 Python 一般不溢出,但习惯要养)
示例(闭区间找左边界):
while left <= right:<br> mid = left + (right - left) // 2<br> if lst[mid] < x:<br> left = mid + 1<br> else:<br> right = mid - 1<br>return left
性能与兼容性:为什么别为了“练手”在生产代码里手写
bisect 在 CPython 里是纯 C 实现,实测比等效 while 快 3–5 倍;PyPy 下差距更大。更重要的是它经过几十年验证,处理浮点精度、NaN、自定义类的 __lt__ 等边缘 case 更稳。
兼容性方面:bisect 自 Python 2.4 就存在,所有现代版本行为一致;而手写二分在不同人笔下可能对相等值的处理逻辑不一致(比如遇到重复值时停不停),导致后期维护成本陡增。
真正容易被忽略的一点:如果你的列表不是全局静态的,而是频繁插入删除,考虑用 sortedcontainers.SortedList,而不是反复调用 bisect + insert —— 后者每次插入都是 O(n),前者是 O(log n)。










