Python算法实战精讲: 讲个二分查找怎么水了1000多字?

动机

Python上的二分查找方案主要包括bisect和sortedcontainers.

  • bisect提供了在一个有序的indexable对象上进行查找和插入的接口
  • sorted container提供了SortedList、SortedKeyList、SortedSet、SortedDict这几种有序数据容器对象,支持创建、增删查等操作和一些通用操作,底层实现是类B+树的结构。

然而,我们面对以下场景:在indexable对象nums上,我们给出一个条件cond,并保证该条件满足

for i in range(lo, m):
	  assert cond(nums[i])
for i in range(m, hi):
    assert not cond(nums[i])

我们想要找到这个m.

这是一个典型的二分查找问题,例如以下应用场景:

通过二分查找,求f(x)=x^6+x-3[1,2]上的零点。

但是该问题无法通过直接封装这两种解决方案提供的接口来解决。直觉上,一个有序的indexable对象是一个严格全序有限集,于是对于满足上述条件的cond,集合中取满足cond的子集,这个子集一定有唯一最小元。于是,如果我们想要封装bisect.bisect来实现上述需求,需要把cond表示成“序关系+最小元”的形式。然而,获知这个最小元的过程是non-trivial的,甚至在很多时候,我们的场景本身就是要求这个最小元,例如求解函数在某个单调区间上的零点。因此,无法通过封装类似bisect.bisect的方式来实现上述需求。

其实根本原因在于之前的bisect和sorted list都只是侧重于维护有序结构,二分查找只是基于有序结构的插入来顺便实现一下。所以没有办法适用于二分查找在其他地方的广泛应用。

解决

即使说了这么多,这件事也不复杂,于是我们写一个特定于该场景的二分查找就解决了

def binary_search(nums, cond, lo=0, hi=len(nums)):
  	if hi is None: hi = len(nums)
    while lo < hi:
        m = (lo+hi) // 2
        if cond(nums[m]): lo = m + 1
        else: hi = m
    return lo
  
binary_search(nums=[0,1,2,3,4],
             cond=lambda i: i <= 2) # out: 3

考虑到nums只是和cond配合使用,其实可以完全不用传入,使其作为cond的函数题的一部分即可。

def binary_search(cond, lo, hi):
    while lo < hi:
        m = (lo+hi) // 2
        if cond(m): lo = m + 1
        else: hi = m
    return lo
  
binary_search(cond=lambda i: i <= 2
             lo=0,
             hi=len(nums)) # out: 3

这样相当于在[lo:hi]这个整数区间上二分查找。

希望这篇文章让大家感受到二分查找的应用之广泛,从而体会到开发一个通用的二分查找接口是困难的。

本文灵感来自我之前的一篇水文 [link]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值