数据结构与算法_part_7_二分查找(搜索)

1. 搜索

搜索是在一个项目集合中找到一个特定项目的算法过程。搜索通常的答案是真的或假的,因为该项目是否存在。

搜索的几种常见方法:

  1. 顺序查找
  2. 二分法查找
  3. 二叉树查找
  4. 哈希查找

2. 二分法查找 (Binary Search)

2.1 二分法查找的优缺点

二分查找又称折半查找,优点是:

  • 比较次数少
  • ,查找速度快,
  • 平均性能好﹔

其缺点是:

  • 要求待查表为有序表
  • 插入删除困难

因此,折半查找方法适用于不经常变动而查找频繁的有序列表

2.2 二分法查找的步骤

首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表希成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。

在这里插入图片描述

2.3 二分法查找的例子

这里的二分法查找就和人查字典类似,首先翻到字典的一半位置( s t a r t + e n d 2 = 0 + 8 2 = 4 \frac{start+end}{2} = \frac{0+8}{2} = 4 2start+end=20+8=4),看看此时的字母是多少:

  • 如果我们要找的在该字母的前面,我们就将前面的对半分( s t a r t + e n d 2 = 0 + 3 2 = 1 \frac{start+end}{2} = \frac{0 + 3}{2} = 1 2start+end=20+3=1);
  • 如果我们要找的在该字母的后面,我们就将后面的对半分( s t a r t + e n d 2 = 2 + 3 2 = 2 \frac{start+end}{2} = \frac{2 + 3}{2} = 2 2start+end=22+3=2) -> 已经找到了!
  • 依次执行,直到找到目标位置…

在二分的时候一定要明确起始的index和结束的index!

看图说话:

在这里插入图片描述

2.4 二分法查找的注意事项

在二分的时候一定要明确起始的index和结束的index!

在这里插入图片描述

在二分的时候一定要明确起始的index和结束的index!

2.5 二分法查找的代码实现

2.5.1 递归实现

# coding: utf-8


def binary_search(alist, target):
    """二分法查找
        对于二分法查找的代码实现,最好的实现方式是使用递归
    """

    n = len(alist)

    if n > 0:  # 递归的终止条件
        mid = n // 2
        if alist[mid] == target:
            return True
        elif target < alist[mid]:  # 往左半部分查找
            return binary_search(alist[:mid], target)
        else:  # 往右半部分查找
            return binary_search(alist[mid + 1:],
                                 target)  # 注意alist[mid]这个数我们就不要了
    else:  # 说明没有该元素,则返回False
        return False


if __name__ == "__main__":
    ls = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    print(ls)
    res_1 = binary_search(ls, target=26)
    res_2 = binary_search(ls, target=100)
    print(ls)
    print(res_1)
    print(res_2)
"""
    [54, 26, 93, 17, 77, 31, 44, 55, 20]
    [54, 26, 93, 17, 77, 31, 44, 55, 20]
    True
    False
"""

2.5.2 非递归的实现

# coding: utf-8


def binary_search(alist, target):
    """二分法查找(非递归版本)
        对于非递归版本,只能在原有list上进行操作,必须涉及:
            1. 起始位置下标
            2. 结束位置下标 
    """
    n = len(alist)
    first_idx = 0
    last_idx = n - 1

    while first_idx <= last_idx:  # 一旦first_idx > last_idx时,表示已经越界,没有该数值,返回False

        mid = (first_idx + last_idx) // 2

        if alist[mid] == target:
            return True
        elif alist[mid] > target:  # ←
            # 改变起始位置和结束位置就可以改变搜索范围
            last_idx = mid - 1  # 不要之前的mid了
        else:  # →
            first_idx = mid + 1  # 不要之前的mid了

    return False


if __name__ == "__main__":
    ls = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    print(ls)
    res_1 = binary_search(ls, target=26)
    res_2 = binary_search(ls, target=100)
    print(ls)
    print(res_1)
    print(res_2)
"""
[54, 26, 93, 17, 77, 31, 44, 55, 20]
[54, 26, 93, 17, 77, 31, 44, 55, 20]
True
False
"""

2.5.3 非递归版本和递归版本的区别

两种代码实现之间最大的区别就在于:

  • 在写递归算法时,我们就用递归的思想去重新调用函数,但是使用递归在重新调用函数时会产生一个新的列表;
  • 在非递归的实现中,我们不能调用函数自身了,因此需要重新划分查找的范围

2.6 二分查找简洁代码

2.6.1 递归实现

def binary_search(alist, target):
    if len(alist) == 0:
        return False
    else:
        mid = len(alist) // 2
        if alist[mid] == target:
            return True
        elif alist[mid] > target:
            return binary_search(alist[:mid], target=target)  # [: mid-1]
        else:
            return binary_search(alist[mid + 1:], target=target) # [mid+1: n]

2.6.2 非递归实现

def binary_search(alist, target):
    first = 0
    last = len(alist) - 1
    while first <= last:
        mid = (first + last) // 2
        if alist[mid] == target:
            return True
        elif alist[mid] > target:
            last = mid - 1  # 此时first不变
        else:
            first = mid + 1  # 此时last不变
    return False

2.7 二分查找的时间复杂度

首先我们先考虑最坏时间复杂度。我们之前学习的希尔排序、归并排序和快排都会涉及到二分法,所以我们假设它一直需要二分,也就是 2 t = n 2^t = n 2t=n,即二分t次将整个数组分完,所以次数 t = log ⁡ 2 n t = \log_2^n t=log2n

那么我们考虑一下它的最优时间复杂度,即第一次求得中间值就是目标值target,就不需要二分了,所以我们认为此时的时间复杂度为 O ( 1 ) O(1) O(1)


  • 最优时间复杂度: O ( 1 ) O(1) O(1)
  • 最坏时间复杂度: O ( log ⁡ 2 n ) O(\log_2^n) O(log2n)

我们与之前从头到尾那样遍历去找元素的方法对比一下:

算法最优时间复杂度最坏时间复杂度最优说明
遍历 O ( 1 ) O(1) O(1) O ( n ) O(n) O(n)第一个元素即为target
二分查找 O ( 1 ) O(1) O(1) O ( log ⁡ 2 n ) O(\log_2^n) O(log2n)第一个中间的值就是target

在这里插入图片描述

很明显, O ( log ⁡ 2 n ) O(\log_2^n) O(log2n)的时间复杂度明显比 O ( n ) O(n) O(n)要低的多。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值