搜索算法(Python)

本文的最新版本位于:https://github.com/iwhales/algorithms_notes
转载请注明出处:https://www.jianshu.com/u/5e6f798c903a

参考:《数据结构(Python 语言描述)》 - 3.3 搜索算法

学习思路按照以下三个层次推进:学习算法设计思路 >> 实现算法 >> 复杂度分析
Tips:为了保持简洁,每个函数都只处理一个整数列表,并且假设列表不为空。

1. 搜索最小值

Search for the Minimum

下面这个函数会返回列表中最小项的索引,该算法的复杂度是 O(n)

def index_of_min(lyst):
    """返回最小项的索引"""
    min_index = 0
    current_index = 1
    while current_index < len(lyst):
        if lyst[current_index] < lyst[min_index]:
            min_index = current_index
        current_index += 1
    return min_index

2. 顺序搜索

顺序搜索(Sequential Search),也称线性搜索(linear search)。

如果需要在对象上使用 in 进行成员测试,则需要为该对象实现 __contains__() 方法。如果对象中包含指定项,返回 True;否则返回 False 。下面这个顺序搜索函数,实现了与列表中 __contains__() 方法类的功能。

def sequential_search(target, lyst):
    """如果找到目标项,返回其索引;否则返回-1"""
    position = 0
    while position < len(lyst):
        if target == lyst[position]:
            return position
        position += 1
    return -1

2.1 最好情况、最坏情况、平均情况

Best-Case, Worst-Case, and Average-Case Performance Revisited

有些算法的性能与数据的排列方式有关,比如顺序搜索算法。此时,我们可以分三种情况考虑考虑该算法的性能:

  1. 最坏情况:对于"顺序搜索"而言,在最坏情况下,目标项位于列表末尾,或根本不在列表之中。此时必须访问列表中的每一项,对大小为 n 的列表要执行 n 次迭代。因此,顺序搜索在最坏的情况下复杂度为 O(n)
  2. 最好情况:"顺序搜索"只进行一次迭代就在第一个位置找到了目标项,复杂度为 O(1)
  3. 平均情况:需要把从"最好情况"到"最坏情况"间可能出现的所有情况的迭代次数相加,并除以 n。因此,算法平均执行了 (1+2+3+...+n-1+n)/n 次迭代,化简后等于 (n+1)/2 。对于很大的 n 而言,常数因子 2 的作用并不大。因此,平均情况的复杂度仍然为 O(n)

3. 二叉搜索

Binary Search

二叉搜索也称二分查找,该算法针对有序列表,其时间复杂度是 O(\log_{2}n)

假设我们需要利用二叉搜索查找某个按照升序排列的列表。首先,二叉算法会找出位于列表中间位置的中间项,并将该项与目标项进行比较:如果两者一致,便返回中间项的索引;如果目标项小于中间项,则继续搜索列表的前半部分;反之,则搜索后半部分。当找到目标,或当索引的起点值大于终点值时停止搜索。

注意,使用二叉搜索时有一个额外的整体性代价,就是必须保持列表的有序性。

下面分别使用"循环方式"和"递归方式"来实现二叉搜索。

3.1 循环方式

# -*- coding: utf-8 -*-
def binary_search_loop(sorted_list: list, target: int):
    """
    二分查找,循环方式
    :param sorted_list: 按升序排列的列表
    :param target: 被查找的目标项
    :return: 如果sorted_list中包含target,返回target的索引值,否则返回None
    """
    low = 0
    high = len(sorted_list) - 1
    while low <= high:
        mid_point = (low + high) // 2
        if sorted_list[mid_point] == target:
            return mid_point
        elif sorted_list[mid_point] > target:
            high = mid_point - 1
        else:
            low = mid_point + 1
    return None

if __name__ == '__main__':
    my_list = [1, 2, 3, 4]
    assert binary_search_loop(my_list, 1) == 1
    assert binary_search_loop(my_list, 2) == 1
    assert binary_search_loop(my_list, 3) == 2
    assert binary_search_loop(my_list, 4) == 3
    assert binary_search_loop(my_list, 10) is None

3.2 递归方式

  • 基线条件:数组只包含一个元素,如果目标值与该元素相同,便在最终的基线条件下找到了目标值,否则目标值不在数组中。
  • 递归条件:每次把数组分成两半,并丢弃其中的一半,对剩下的一半再次执行二分查找。
# -*- coding: utf-8 -*-
def binary_search_recursive(sorted_list: list, target: int):
    """
    二分查找,递归方式
    :param sorted_list: 按升序排列的列表
    :param target: 被查找的目标项
    :return: 如果sorted_list中包含target,返回target的索引值,否则返回None
    """
    mid = (len(sorted_list) - 1) // 2
    if len(sorted_list) == 1:  # 基线条件
        if target == sorted_list[0]:
            return 0
        else:
            return None
    elif target == sorted_list[mid]:  # 基线条件
        return mid
    # 下面的代码,通过递归逐步缩减问题规模
    if target > sorted_list[mid]:
        index = binary_search_recursive(sorted_list[mid + 1:], target)
        if index is None:
            return None
        # 减半后的新列表sorted_list[mid + 1:]会重新以索引0开头,
        # 这里需要保证返回的索引值包含当前sorted_list列表的索引信息,
        # 因此需要重新调整index中的索引值。
        # 要将index中的索引值调整到sorted_list列表中的对应位置,
        # 需要为index加上(mid + 1)
        return index + (mid + 1)
    else:
        index = binary_search_recursive(sorted_list[:mid], target)
        return index

if __name__ == '__main__':
    assert binary_search_recursive(my_list, 1) == 0
    assert binary_search_recursive(my_list, 2) == 1
    assert binary_search_recursive(my_list, 3) == 2
    assert binary_search_recursive(my_list, 4) == 3
    assert binary_search_recursive(my_list, 10) is None

3.3 最坏情况

当目标项不在列表中的时候,便会出现最坏情况。在最坏情况下,对于大小为 n 的列表,会持续将列表长度除以 2,直至商为 1 时停止(如, n//2//...//2 = 1 )——其中除法执行的次数便是循环执行的总次数。假设除法执行的次数为 k ,那么便有 n/2^k=1 ,可得 k=log_2n 。因此,二叉搜索最坏情况的复杂度为 O(log_2n)

下图展示了在仅含 1~9 的整数列表中,用二叉搜索查找整数 10 的过程。灰色项表示中间项,用于和目标项进行比较,也就是会被访问的项。另外,位于初始列表前半部分的项,实际上并不会被访问。


二叉搜索.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 一些概念 数据结构就是研究数据的逻辑结构和物理结构以及它们之间相互关系,并对这种结构定义相应的运算,而且确保经过这...
    Winterfell_Z阅读 5,654评论 0 13
  • 流模块单独分出来讲是因为内容相对比较多,而且也有一定难度。流模块可以对应数据的生产者/消费者模型,生产者可以向流里...
    vsf_simon阅读 622评论 0 0
  • 前言:Universal Links是苹果在iOS9上开始支持的外部跳转App的功能,正如它的名字Universa...
    马修王阅读 2,099评论 0 4
  • 我的家乡是平原,土壤很肥沃,自古以来就是粮食生产大县,因此很多家庭都是农民。农民一辈子最大的愿望就是粮食大丰收,这...
    刘大胜阅读 295评论 0 1