如何利用Python实现二分查找(迭代和递归)

“Although the basic idea of binary search is comparatively straightforward, the details can be surprisingly tricky, and many good programmers have done it wrong the first few times they tried.”
— Donald Knuth
翻译:虽然二分查找的基本思想相对简单,但细节可能会非常棘手,许多优秀的程序员在最开始的尝试中都做错了。

二分查找 Binary Search

算法思想:二分查找用于在一个含有n个元素的有序序列中有效地定位目标值
考虑一下三种情况:

  • 如果目标值 value == [middle]的数据,查找成功并且终止
  • 如果目标值 value > [middle]的数据,对后半部分序列重复这一过程,即索引的范围从mid + 1right
  • 如果目标值 value < [middle]的数据,对前半部分序列重复这一过程,即索引的范围从leftmiddle - 1

迭代定义 - Iteratively

# binary_search.py
def binary_search_iterative(elements, value):

    left, right = 0, len(elements)-1

    while left <= right:
        middle = (left + right) // 2

        if elements[middle] == value:
            return middle
        elif elements[middle] < value:
            left = middle + 1
        else:
            right = middle - 1
    return None


if __name__ == '__main__':
    nums = [1, 2, 3, 4, 5]
    print(binary_search_iterative(nums, 2))	 # Output: 1,值为2的元素出现在索引1的位置(索引从0开始)
    print(binary_search_iterative(nums, 10))  # Output: None,表示空,没有找到指定的元素

递归定义 - Recursively

第一版

类似二分查找的迭代版本,使用切片运算符:将列表切碎:

def binary_search_recursive(elements, value):

    if len(elements) == 0:
        return False

    left, right = 0, len(elements) - 1

    if left <= right:
        middle = (left + right) // 2

        if elements[middle] == value:
            return True
        elif elements[middle] < value:
            return binary_search_recursive(elements[middle+1:], value)
        else:
            return binary_search_recursive(elements[:middle], value)

    return False

if __name__ == '__main__':
    nums = [1, 2, 3, 4, 5]
    print(binary_search_recursive(nums, 2))  # True
    print(binary_search_recursive(nums, 10))  # False

不用像迭代那样使用while循环,只需检查一下条件。在对应的分片列表中调用相同的函数。

使用分片会有什么问题?好吧,事实证明,切片会生成元素引用的副本,这些副本可能具有显着的内存和计算开销。

第2版

为避免复制操作,您可以重复使用相同的列表,但在必要时将不同的边界传递给函数

def binary_search(elements, value, left, right):

    if left <= right:
        middle = (left + right) // 2

        if elements[middle] == value:
            return True

        elif elements[middle] < value:
            return binary_search(elements, value, middle+1, right)
        else:
            return binary_search(elements, value, left, middle-1)
    return False

if __name__ == '__main__':
    nums = [1, 2, 3, 4, 5]
    print(binary_search(nums, 2, 0, len(nums)-1))	# True
    print(binary_search(nums, 10, 0, len(nums)-1))	# False

缺点是每次您要调用该函数时,都必须传递初始边界,以确保它们正确无误:binary_search(nums, 10, 0, len(nums)-1)

第3版

更进一步,您可能希望将一个函数嵌套在另一个函数中以隐藏技术细节并利用外部作用域中的变量重用:

def contains(elements, value):
    def recursive(left, right):
        if left <= right:
            midddle = (left + right) // 2
            if elements[midddle] == value:
                return True
            elif elements[midddle] < value:
                return recursive(midddle+1, right)
            else:
                return recursive(left, midddle-1)
        return False
    return recursive(0, len(elements) - 1)

if __name__ == '__main__':
    nums = [1, 2, 3, 4, 5]
	print(contains(nums, 2))  # True
    print(contains(nums, 10))  # False

即使在封闭范围内定义了recursive()内部函数,也可以访问元素和值参数。

迭代和递归实现之间的选择通常是性能考虑,便利性以及个人喜好的最终结果。

总结

本文中介绍了首先二分查找的基本思想,然后用迭代和递归两种方法实现了简易版的二分查找,其实Python实现了功能更强大的二分查找的库 bisect,感兴趣的同学,可以在本文的基础上进行学习。

最后:二分查找的时间复杂度:O(log(n))

推荐阅读:
How to Do a Binary Search in Python

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值