Hello 算法「算法无处不在」导读:从查字典、理扑克到找零钱,认识生活中的算法雏形
【免费下载链接】hello-algo《Hello 算法》:动画图解、一键运行的数据结构与算法教程。支持简中、繁中、English、日本語,提供 Python, Java, C++, C, C#, JS, Go, Swift, Rust, Ruby, Kotlin, TS, Dart 等代码实现项目地址: https://gitcode.com/GitHub_Trending/he/hello-algo
《Hello 算法》(hello-algo) 是一本动画图解、支持一键运行的算法入门书。本文对应日文版序章 ja/docs/chapter_introduction/algorithms_are_everywhere.md,从三个最不起眼的生活习惯——查字典、整理扑克牌、超市找零——出发,揭示它们背后其实分别是二分查找、插入排序与贪心算法。读完全文,你将能完成"生活经验 → 抽象算法 → 仓库源码实现"的第一次思维切换,为后续正式学习数据结构与算法建立直觉基础。
一提到"算法",很多人本能地联想到复杂的数学公式。但本书开篇给出了一个反转认知的事实:许多算法并不依赖高深的数学,只依赖基础逻辑;而这种逻辑早已遍布你的日常生活——你其实已经在不知不觉中"运行"过很多算法了。下面就是正文给出的三个经典例证。
例一:查字典 = 二分查找(binary search)
字典里每个汉字都对应一个拼音,整本字典按照拼音的字母顺序排列。假设要查找一个拼音首字母为 $r$ 的字,我们通常会这样做:
- 把字典翻到大约一半的页数,先看该页汉字的首字母,例如翻到的是 $m$;
- 由于在拼音字母表中 $r$ 位于 $m$ 之后,于是排除字典前半部分,把查找范围缩小到后半部分;
- 不断重复步骤 1 与步骤 2,每次都将搜索区间减半,直到翻到首字母为 $r$ 的那一页。
这个"小学生必备技能",本质上就是著名的二分查找(二分探索)算法。从数据结构视角看,字典是一份按拼音排好序的"数组";从算法视角看,上述一连串"翻中页、比字母、砍一半"的操作就是二分查找——它的核心特征是在有序数据上每次排除约一半的候选区间,从而把查找规模以对数级速度收缩。
这一抽象结论在仓库里被落实成了可运行的代码。以 Python 实现为例,ja/codes/python/chapter_searching/binary_search.py 给出了双闭区间版本:
def binary_search(nums: list[int], target: int) -> int: """二分查找(双闭区间)""" i, j = 0, len(nums) - 1 # 初始化双闭区间 [0, n-1] while i <= j: # 区间为空(i > j)时退出 m = (i + j) // 2 # 计算中点索引 m if nums[m] < target: i = m + 1 # target 在区间 [m+1, j] 中 elif nums[m] > target: j = m - 1 # target 在区间 [i, m-1] 中 else: return m # 找到目标元素,返回索引 return -1 # 未找到目标元素,返回 -1对照阅读不难发现:代码里的i = m + 1/j = m - 1,正是查字典时"丢弃含 $m$ 的那一半"的动作;而while i <= j的退出条件,则对应着"整个字典范围都已翻完、目标字不在其中"的情形。仓库中该章节还提供了二分查找的左闭右开区间变体binary_search_lcro(见同一文件),方便后续在 二分查找插入点、二分查找边界 等主题中切换区间定义。同样的实现还存在于根目录与各语言分目录的codes下(如 C、C++、Java、Go、Rust 等),它们共享同一套算法骨架。
例二:整理扑克 = 插入排序(insertion sort)
打牌时,每局开始前我们都会把手中的牌按从小到大重新排一遍,这个过程是:
- 把牌堆划分为"已排序"与"未排序"两部分,初始时假设最左边的 1 张牌已有序;
- 从未排序部分抽出 1 张牌,插入到已排序部分的正确位置,完成后最左 2 张牌有序;
- 循环执行第 2 步,每轮从无序区取 1 张牌插入有序区,直到整副牌全部有序。
这种整理方法就是插入排序。正文特别指出:插入排序在处理小型数据集时非常高效,因此许多编程语言标准库的排序函数在小规模场景下都会选用它(例如与快速排序等混合使用的内省式排序)。它的关键优势在于:对于基本有序的小数组,元素移动次数少、常数开销低。
仓库对应的 Python 实现位于 ja/codes/python/chapter_sorting/insertion_sort.py:
def insertion_sort(nums: list[int]): """插入排序""" for i in range(1, len(nums)): # 外循环:已排序区间为 [0, i-1] base = nums[i] j = i - 1 while j >= 0 and nums[j] > base: # 内循环:将 base 插入已排序区间的正确位置 nums[j + 1] = nums[j] # 将 nums[j] 向右移动一位 j -= 1 nums[j + 1] = base # 将 base 放到正确位置代码中"外循环维护已排序区间边界、内循环从右向左腾挪空位"的结构,与理牌时"左手持已有序牌、右手抽新牌向前比大小再插入"完全同构。插入排序的更完整理论(最好/最坏/平均时间复杂度分析、与选择排序的对比)见 插入排序章节,完整排序算法家族则收录在 排序章节索引。
例三:超市找零 = 贪心算法(greedy algorithm)
假设在超市购买 $69$ 元的商品,付给收银员 $100$ 元,需要找零 $31$ 元。在币值包含 $1$、$5$、$10$、$20$ 元的前提下,收银员的自然思路是:
- 列出所有小于 $31$ 元的可选币值:$1$、$5$、$10$、$20$ 元;
- 从可选项中取出面额最大的 $20$ 元,剩余 $31 - 20 = 11$ 元;
- 再从剩余选项中取出最大的 $10$ 元,剩余 $11 - 10 = 1$ 元;
- 取出最大的 $1$ 元,剩余 $1 - 1 = 0$ 元;
- 找零完成,方案为 $20 + 10 + 1 = 31$ 元。
每一步都"在当前状态取看起来最好的选择"(尽量用大面额),最终得到一个可行方案——这就是贪心算法最朴素的原型:局部最优选择的累积,期望逼近全局最优解。
仓库将上述思想实现为通用函数 ja/codes/python/chapter_greedy/coin_change_greedy.py:
def coin_change_greedy(coins: list[int], amt: int) -> int: """零钱兑换:贪心""" i = len(coins) - 1 # 假设 coins 已按面额升序排列 count = 0 while amt > 0: # 找到面额不超过剩余金额的最大硬币 while i > 0 and coins[i] > amt: i -= 1 amt -= coins[i] # 选择 coins[i] count += 1 return count if amt == 0 else -1 # 若金额无法凑出则返回 -1这个文件很值得细读:它的driver代码在同一函数下做了三组对照实验——在币值 $[1,5,10,20,50,100]$ 下,贪心能找到全局最优;而当币值改为 $[1,20,50]$、金额 $60$ 时,贪心会得到 $50+1\times10$ 共 $11$ 枚的错误结果,真正的最优解却是 $20+20+20$ 共 $3$ 枚;币值 $[1,49,50]$、金额 $98$ 时同理(贪心给出 $50+1\times48=49$ 枚,最优为 $49+49=2$ 枚)。这组实验直接揭示了贪心算法的重要局限:贪心只保证每步局部最优,不保证整体最优,能否得到最优解取决于问题与币值结构。对应理论详见 贪心算法章节 与 零钱兑换问题的精确解(动态规划)。
三个例子的共性:算法是"生活问题"的抽象
把三个例子并排看,一条清晰的脉络浮现出来:
| 生活场景 | 关键操作特征 | 对应算法 | 仓库代码位置 |
|---|---|---|---|
| 按拼音查字典 | 有序数据上每次排除一半 | 二分查找 | binary_search.py |
| 逐张整理扑克 | 维护有序区、逐张插入 | 插入排序 | insertion_sort.py |
| 大面额优先找零 | 每步取当前最优 | 贪心算法 | coin_change_greedy.py |
从中可以提炼出本小节最重要的两个认知:
- 数据结构的眼光:字典是一份有序"数组",扑克牌堆是一份待维护的序列,币值是一组可枚举的"候选集合"——生活对象都可以用数据结构的抽象去审视;
- 算法的眼光:砍半查找、原地插入、局部最优递推——这些操作模式一旦被命名、被抽象,就能脱离具体生活场景,迁移到海量的计算机问题中。
正文用一句话收束了这个递进关系:小到烹饪一道菜、大到星际航行,几乎所有问题的解决都离不开算法。而计算机的出现,让"通过编程把数据结构存入内存、再编写代码驱动 CPU/GPU 执行算法"成为可能——于是原本靠人力完成的生活问题得以转移到计算机上,以远高于人力的效率求解。
阅读提示与下一步
如果你对"数据结构、算法、数组、二分查找"这些词仍感到一知半解,请不要担心——这正是本节的预期效果。本节的目标不是让你立刻掌握定义,而是让你先感知到自己早已具备算法直觉,从而以更从容的心态进入后续章节。
在仓库中按阅读顺序推进,你的下一站是 what_is_dsa.md(数据结构和算法是什么),它会把本节的经验直觉上升为学科定义;随后进入 计算复杂度章节,学习如何量化"砍半查找到底有多快"这类问题。全文的动画图解与可交互演示可在本地构建后查看,运行方式参见仓库根目录 README.md;各算法示例文件(如上述 Python 文件)多为自包含脚本,在安装对应语言环境后可直接运行查看输出,例如:
# 以 Python 为例,运行二分查找示例(其余语言位于对应 codes 子目录) python3 codes/python/chapter_searching/binary_search.py从"你早就会的算法"出发,这本《Hello 算法》将一步步带你把这些生活经验翻译成系统的数据结构与算法知识。
【免费下载链接】hello-algo《Hello 算法》:动画图解、一键运行的数据结构与算法教程。支持简中、繁中、English、日本語,提供 Python, Java, C++, C, C#, JS, Go, Swift, Rust, Ruby, Kotlin, TS, Dart 等代码实现项目地址: https://gitcode.com/GitHub_Trending/he/hello-algo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考