Hello 算法搜索篇总结:四大类搜索算法的效率对比、选型策略与源码实现
本文基于《Hello 算法》(hello-algo)英文版搜索篇的章末总结(en/docs/chapter_searching/summary.md)展开,系统梳理暴力搜索、二分查找、哈希查找与树搜索四类方法的原理、时间复杂度与适用场景,并结合仓库中 en/codes/python/chapter_searching/ 目录下的 Python 参考实现,帮助读者掌握"根据数据规模、查询/更新频率选择搜索方法"的决策框架,以及"用哈希替换线性查找优化时间复杂度"这一核心实战技巧。
一、总结的核心结论:两类搜索路线
搜索篇的总结首先给出了一个关键分类:所有搜索算法可按实现思路划分为两条路线。
第一条路线:通过遍历数据结构定位目标(暴力搜索 / Brute-force Search)
- 线性查找适用于数组、链表等线性结构,从一端逐元素访问,直到命中目标或遍历完毕;
- **广度优先搜索(BFS)与深度优先搜索(DFS)**适用于图与树:BFS 从初始节点出发逐层搜索、由近及远;DFS 沿路径深入到底再回溯尝试其他路径。
这类算法的优点是简单、通用性好,不需要任何数据预处理或额外数据结构;缺点是时间复杂度为 ( 为元素个数),数据量大时性能较差。
第二条路线:利用数据的组织方式或先验信息高效定位(自适应搜索 / Adaptive Search)
- 二分查找利用数据的有序性,仅适用于数组;
- 哈希查找用哈希表以键值对形式存储待查数据;
- 树搜索作用于二叉搜索树等特定树结构,通过比较节点值快速剪枝。
这类算法效率很高,时间复杂度可达 甚至 ,但通常需要数据预处理:二分查找要求数组预先排序,哈希查找与树搜索则需要额外数据结构来维护,附带额外的时间与空间开销。在书中它们也常被称为"查找算法(lookup)",主要用于在特定数据结构中快速检索目标元素。这一分类的详细阐述见 searching_algorithm_revisited.md。
二、线性查找:O(n) 的通用解法
总结指出线性查找"适用于小规模数据或更新频繁的数据"。仓库中的 linear_search.py 给出了数组与链表两种载体上的实现:
def linear_search_array(nums: list[int], target: int) -> int:
"""Linear search (array)"""
# Traverse the array
for i in range(len(nums)):
if nums[i] == target: # Found the target element, return its index
return i
return -1 # Target element not found, return -1
对链表则沿 next 指针逐节点推进,找到目标节点后返回节点对象(而非下标),体现"链表查找结果通常是节点引用"这一与数组的本质差异。从源码结构看,两种实现都只有一次循环,无额外空间开销(空间复杂度 ),最差情况需要比较 次——这正是总结中"无需预处理但时间复杂度 "这一判断的直接来源。
三、二分查找:有序数组上的 O(log n) 解法
总结第一条要点即:二分查找依赖有序数据,通过不断将搜索区间减半来定位目标,要求输入已排序且仅适用于数组或基于数组的结构。仓库中的 binary_search.py 同时给出了闭区间与左闭右开区间两种写法:
def binary_search(nums: list[int], target: int) -> int:
"""Binary search (closed interval)"""
# Initialize closed interval [0, n-1]
i, j = 0, len(nums) - 1
while i <= j:
m = (i + j) // 2 # Calculate midpoint index m
if nums[m] < target:
i = m + 1 # target is in the interval [m+1, j]
elif nums[m] > target:
j = m - 1 # target is in the interval [i, m-1]
else:
return m
return -1
关键细节有两点,均可从源码注释与文档中确认:
- 中点计算防溢出:在 C/C++ 等语言中 可能超出
int范围,规范做法是使用 (详见 binary_search.md);Python 整数无固定位宽,因此注释中特别说明"无需考虑大数溢出"; - 区间表示的一致性:闭区间写法下循环条件为
i <= j,收窄操作i = m + 1/j = m - 1左右对称;而左闭右开区间[i, j)的写法(binary_search_lcro)循环条件为i < j,右边界收窄写作j = m。书中推荐闭区间写法,因其更不易出错。
二分查找的代价与收益在总结与正文中对应:
- 收益:每轮迭代区间减半,时间复杂度 。举例来说, 时线性查找需约 次迭代,而二分查找只需 20 次;且无需额外空间,空间复杂度 。
- 代价:仅适用有序数据——若为排序付出 的预处理成本,且插入频繁时维持有序数组每次需 移动元素,得不偿失;另外二分查找依赖"跳跃式"随机访问,链表上这种访问方式低效,因此不适合链表。
四、哈希查找:O(1) 的查询利器
总结指出哈希查找"适合高查询效率且不需要范围查询的场景"。hashing_search.py 展示了其工作方式:先用一趟遍历把"元素 → 下标(或节点对象)"写入哈希表,之后每次查询都是直接的键查找:
def hashing_search_array(hmap: dict[int, int], target: int) -> int:
"""Hash search (array)"""
# If this key does not exist in the hash table, return -1
return hmap.get(target, -1)
从源码结构看,查询函数本身只是一次 dict.get 调用,这正是 平均查询复杂度的体现。其代价在总结中同样明确:哈希表需要 额外空间,无法维护数据有序性,因此不适合需要有序数据或范围查询的场景,且性能依赖于哈希函数与冲突处理策略。
五、树搜索与效率总表
总结中"树搜索适合必须保持有序并支持范围查询的大规模动态数据集"这一论断,依据可在 searching_algorithm_revisited.md 的效率对比表中找到完整数据:
| 操作 | 线性查找 | 二分查找 | 树搜索 | 哈希查找 |
|---|---|---|---|---|
| 查找元素 | ||||
| 插入元素 | ||||
| 删除元素 | ||||
| 额外空间 | ||||
| 数据预处理 | 无 | 排序 | 建树 | 建哈希表 |
| 数据有序性 | 无序 | 有序 | 有序 | 无序 |
树搜索的两个关键特性值得注意:
- 树节点在内存中非连续存储,因此适合超大规模数据集,且天然支持有序数据与范围查询;
- 连续插入/删除可能使二叉搜索树退化(偏斜),时间复杂度退化为 ;若采用 AVL 树或红黑树,所有操作可稳定保持在 ,代价是维护树平衡的额外开销。
六、如何选择搜索方法:总结给出的决策框架
总结的核心实战结论是:实际选型需要综合分析数据规模、搜索性能要求、查询与更新频率三个因素。整理为场景对照如下(均出自 summary.md 与 searching_algorithm_revisited.md):
线性查找
- 通用性好,无需任何预处理;若只查询一次,其他三种方法的前置成本(排序、建表、建树)可能超过一次线性查找本身;
- 适合数据量小的场景,时间复杂度的影响有限;
- 适合数据更新频率高的场景,因为它不需要维护任何附加结构。
二分查找
- 适合大规模、已排序的数据集,最坏情况性能稳定在 ;
- 数据量不宜"过大到无法装入连续内存",因为数组需要连续存储空间;
- 不适合频繁的插入/删除场景——维护有序数组的开销高。
哈希查找
- 适合查询性能要求高的场景,平均时间复杂度 ;
- 不适合需要有序数据或范围搜索的场景;
- 对哈希函数与冲突处理策略敏感,存在性能退化风险;
- 不适合过大的数据量,因为哈希表需要预留额外空间来降低冲突率。
树搜索
- 适合海量数据集(节点非连续存储);
- 适合需要维持有序或执行范围查询的场景;
- 需注意二叉搜索树退化为 的风险,必要时改用平衡树。
七、实战技巧:用哈希替换线性查找
总结最后一条要点是本篇最值得记住的优化策略:用哈希查找替换线性查找,把 的查找降为 。书中以"两数之和"问题为例,two_sum.py 给出了两种解法的对照:
暴力法(双重循环枚举, 时间 / 空间)
def two_sum_brute_force(nums: list[int], target: int) -> list[int]:
"""Method 1: Brute force enumeration"""
for i in range(len(nums) - 1):
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] == target:
return [i, j]
return []
哈希表法(单循环, 时间 / 空间)
def two_sum_hash_table(nums: list[int], target: int) -> list[int]:
"""Method 2: Auxiliary hash table"""
dic = {}
for i in range(len(nums)):
if target - nums[i] in dic:
return [dic[target - nums[i]], i]
dic[nums[i]] = i
return []
哈希法的精髓在于"查插同步":对每个 nums[i],先在表中查询互补值 target - nums[i] 是否已出现,命中即返回;未命中再把当前元素写入表。由此把内层循环的线性扫描(每次 ,累计 )压缩为一次哈希查询(平均 ),整体从 降到 。虽然需要 额外空间,但书中文档 replace_linear_by_hashing.md 的结论是:这是"时空更均衡的总体最优解"——以可控的空间换数量级的时间提升。
八、要点回顾
- 二分查找依赖有序数据、仅适用于数组类结构,通过不断对半收缩区间实现 查找,空间复杂度 ;
- 暴力搜索(线性查找、BFS、DFS)通用且免预处理,但时间复杂度 ,适合小数据或高频更新场景;
- 哈希查找与树搜索将效率提升到 / ,代价是额外数据结构与维护成本;哈希查找不能支持范围查询,树搜索需注意偏斜退化;
- 选型时综合权衡数据规模、查询性能要求、查询与更新频率,没有"总是最优"的方案;
- 工程上最常用的高性价比优化是以哈希替换线性查找,典型如"两数之和"从 优化到 。
相关实现均可在 en/codes/python/chapter_searching/ 目录中直接运行验证;二分查找的区间写法对比、插入位置查找(binary_search_insertion.md)与左右边界查找(binary_search_edge.md)等变体问题,可作为掌握本章节后的进阶练习。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00