学径 LearnPath

CAICP-E · Python 编程基础 / Wiki

查找

在一堆东西里找目标。可以一个一个找,排好队也能从中间折半找。

它是什么

查找 就是找某个目标在不在。 在哪一个位置也可以算查找。

书包里找橡皮。 名单里找同学。 字典里找一个字。 这些都是查找。

电脑最常在 列表 里找。 办法不同,快慢不同。 前提也不同。

两种入门算法:

  • 顺序查找:从左到右一个一个问
  • 二分查找:在已经排好序的一排里,反复看中间、丢掉一半

它们都属于 算法

名字从哪来

查找的英文是 search。 日常就是「搜寻」。

顺序查找也叫线性查找。 线性的意思是:沿着一条线往前走,不跳。

二分查找的英文是 binary search。 binary 表示「二」。 每一次把范围切成两半,留下可能的那一半。

图书馆把书按编号排上架,才能快速定位。 字典按拼音或部首排列,才能少翻很多页。 「先排序,再折半」不是电脑独有的发明,是人早就在用的办法。

它怎么工作

顺序查找

从左到右一个一个比。 最朴素,也好懂。 列表不必先排序。

python
# 顺序查找
nums = [3, 8, 2, 5]
target = 5
found = False
for n in nums:
    if n == target:
        found = True
        break
print(found)

找到可以用 break 提前停。 找不到就保持 False

最倒霉时,目标在最后,或根本不在。 那时几乎每个格子都要问一遍。

二分查找

前提:已经按顺序排好。 每次看中间那个。 太大去左边。 太小去右边。 范围越缩越小。

python
# 二分查找:列表已排序
nums = [1, 3, 5, 7, 9]
target = 7
left = 0
right = len(nums) - 1
found = False
while left <= right:
    mid = (left + right) // 2
    if nums[mid] == target:
        found = True
        break
    elif nums[mid] < target:
        left = mid + 1
    else:
        right = mid - 1
print(found)

mid 是中间下标。 // 2 保证是整数位置。 这是 while 的典型用法。 也用到 整除

没排序时,不要直接二分。 可以先 sorted,或改用顺序查找。 排序本身也要花时间。 东西很少时,一个一个找往往更省事。

in 运算符在列表里,通常也是顺序问过去。 写法短,思路仍是「一个一个比」。

生活里在哪里

  • 书架:如果书是乱堆的,你只能一本本翻。那是顺序查找。
  • 图书馆:书按号排好,你可以先走到大概的架,再缩小。有点像二分的精神。
  • 猜数字:人家说「更大」「更小」,你每次从中间猜,就是在折半。
  • 点名册:没按座位排时,只能从上往下扫名字。
  • 字典 App:背后往往先排序,再快速定位。

CS Unplugged 里有一个经典游戏:在已经排好的天平或卡片中找目标,体会「每次丢掉一半」。

和相近概念的差别

查找是在已有的一堆里问「在不在 / 在哪」。 枚举常常是把可能的答案都生成一遍再检查。 二者都能用 循环分支,问题不同。

排序是把队伍排整齐。 二分查找要求队伍已经整齐。 先排队,再折半。 不要把排序和查找当成同一件事。

list.index(x) 也能找位置。 找不到会报错。 自己写循环时,常常用 found 标记,更方便处理「没有」。

常见误会

误会 1:没排序就二分。

中间那个不能代表左右两边的大小关系。 办法会错。

误会 2:right 写成 len(nums)

最后一个下标是长度减 1。 取到长度本身,会 IndexError

误会 3:中间位置用普通除法 /

得到小数下标,列表不认。 要用 //

误会 4:找到以后既不 break,也不记录。

循环白跑。 结果也可能被后面的比较冲掉。

误会 5:二分一定总是更好。

东西很少,或完全没排序,顺序查找更直接。 工具要配场合。

想知道更多

  • 书:阿迪特亚·巴尔加瓦《算法图解》。第一章就用电话簿、猜数字讲二分,可以和家长一起看图画。
  • 书:潘洪波《小学生 Python 趣味编程》(用名单、猜数字体会「一个一个找」和「缩小范围」)。
  • 电影:《隐藏人物》(Hidden Figures)。和家长一起看:把大问题拆成清楚步骤,才能找得又准又稳。

未登录时不会保存学习进度