python3实现二分法查找并返回元素位置

二分法作为一种比较经典的查找算法,经常在面试中被提及。网上有递归和非递归两种方式,同时还有返回元素下标和不返回两种版本,这篇文章我们就分别用递归和非递归方式来实现返回元素下标的版本。

二分法查找

先来了解下二分法的思路。

二分法的特点是查找速度快,但处理对象必须是有序列表。如下图所示,整个列表是升序排列的,要查找48所在的位置(如果该元素存在的话)。首先找到居中的元素36,发现48比36大,于是在36右边的子列表中继续查找。又找到居中元素52,发现36比52小,于是在52左边的子列表中继续查找。此时的子列表只剩下了一个元素,刚好等于48。一共迭代3次,比从头遍历的5次要少。

1-binary.png

代码实现

输入只有两个,有序列表和待查找的值,输出要么是找到的元素下标,要么是返回None表示没找到。

非迭代实现

非迭代版本的好处就是可以一直对同一个列表进行操作,所以只需要两个游标分别指向子列表的左右俩边界即可。

直接给出实现代码

def binary_search(list_: list, item):
    '''non-recursive version'''
    n = len(list_)
    left = 0
    right = n - 1
    while left <= right:
        mid = (left + right) // 2
        if list_[mid] == item:
            return mid
        elif item < list_[mid]:
            left = left
            right = mid - 1
        elif item > list_[mid]:
            left = mid + 1
            right = right
    return None

这里的left是最左边元素的下标,right是最右边元素的下标。要注意这里的边界,最左边是从0开始。通过不停对leftright赋值,从而实现在循环中不停对新的子列表进行操作。当最后两个游标重合并且所指向的元素不是要寻找的值时,此时新的left会比新的right大1,跳出循环,返回None。

下面来验证下

if __name__ == '__main__':
    list_ = [9, 11, 33, 36, 48, 52, 74, 87]
    for item in [7,9,11,33,36,37,48,52,74,87,88,100]:
        result = binary_search(list_, item)
        print(result)

返回的结果如下,符合预期

None
0
1
2
3
None
4
5
6
7
None
None

迭代实现

迭代的缺点就是不能直接对原列表进行操作,这也是为什么在《python3实现快速排序算法图文详解》中,要传递两个游标的位置作为参数的原因。不过我们这里并不需要返回完整列表,只需要返回下标即可,所以新列表也无所谓。

直接看代码。

def binary_search(list_: list, item):
    '''recursive version'''
    n = len(list_)
    if n < 1:
        return None
    left = 0
    right = n - 1
    mid = (left + right) // 2
    if list_[mid] == item:
        return mid
    elif item < list_[mid]:
        newList = list_[0:mid]
        newResult=binary_search(newList,item)
        if newResult is None:
            return None
        else:
            return binary_search(newList, item)
    elif item > list_[mid]:
        newList = list_[mid+1:]
        newResult=binary_search(newList,item)
        if newResult is None:
            return None
        else:
            return mid + binary_search(newList, item) + 1

主要逻辑就是,当中间值比查找值大的时候,直接返回子列表的查询结果。而当中间值比查找值小的时候,要注意用mid加上新的查找值还要再加1。当子列表长度为0时退出迭代。

这里要注意边界条件,左边子列表是list_[0:mid],这里的最后mid对应的值是不包括在子列表中的。

同时要注意None的处理,当最后一层迭代返回None时,再与前面的数值进行计算会报错。所以要进行额外的判断,当下一层迭代返回了None,也就是最后没找到时,本层也会返回None。

验证结果也同意没问题

None
0
1
2
3
None
4
5
6
7
None
None

时间复杂度

最好的情况是直接第一步就在中点找到,而最坏的情况是一直二分到了最后,一共二分了logn次,所以时间复杂度为O(logn)

完整代码

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time: 2020-Jul-21
# @Author: xiaofu


# def binary_search(list_: list, item):
#     '''non-recursive version'''
#     n = len(list_)
#     left = 0
#     right = n - 1
#     while left <= right:
#         mid = (left + right) // 2
#         if list_[mid] == item:
#             return mid
#         elif item < list_[mid]:
#             left = left
#             right = mid - 1
#         elif item > list_[mid]:
#             left = mid + 1
#             right = right
#     return None


def binary_search(list_: list, item):
    '''recursive version'''
    n = len(list_)
    if n < 1:
        return None
    left = 0
    right = n - 1
    mid = (left + right) // 2
    if list_[mid] == item:
        return mid
    elif item < list_[mid]:
        newList = list_[0:mid]
        newResult=binary_search(newList,item)
        if newResult is None:
            return None
        else:
            return binary_search(newList, item)
    elif item > list_[mid]:
        newList = list_[mid+1:]
        newResult=binary_search(newList,item)
        if newResult is None:
            return None
        else:
            return mid + binary_search(newList, item) + 1


if __name__ == '__main__':
    list_ = [9, 11, 33, 36, 48, 52, 74, 87]
    for item in [7,9,11,33,36,37,48,52,74,87,88,100]:
        result = binary_search(list_, item)
        print(result)

我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值