03-算法类型与描述形式
算法类型与描述形式
算法不只是「一段逻辑」。不同类型的算法,表达重点完全不同:有的重点在执行步骤,有的在状态变化,有的在递归关系,有的在数学关系。表达形式必须匹配表达重点——流程图、状态图、递归树、公式推导各有适用场景。「一个算法必须有流程图」是错的,形式由类型决定。
一、从两个算法开始
同样是「一个函数实现的算法」,二分查找和快速排序画成流程图,观感完全不同。
二分查找——重点是步骤:
1 | 开始 |
流程图画出来清清楚楚:初始化 → 判断 → 执行 → 循环。
快速排序——重点是递归关系:
1 | void quickSort(int a[], int l, int r) { |
硬画流程图会变成一堆乱箭头。它真正的表达重点是分治结构——递归树更合适:
1 | quickSort(0,7) |
同样是一个函数,二分查找该画流程图,快速排序该画递归树。差别不在函数,在算法的类型。
二、为什么类型决定形式
算法的类型,本质是表达重点不同:
1 | 表达重点 → 该用什么形式 |
判断标准一句话:
算法描述的是「步骤」就画流程图;描述的是「状态」就画状态图;描述的是「递归」就画递归树;描述的是「数学」就写公式。
不是「一个函数 = 必须有流程图」,而是「算法的表达重点 = 它该用的形式」。
三、常见算法类型 → 最佳描述形式对照
| 算法类型 | 表达重点 | 推荐描述 |
|---|---|---|
| 顺序处理 | 执行步骤 | 流程图 |
| if/else 判断 | 分支路径 | 流程图 |
| 循环算法 | 反复执行 | 流程图 |
| 搜索算法 | 步骤 + 状态变化 | 流程图 + 状态变化 |
| 排序算法 | 数据的动态变化过程 | 动态过程图 |
| 递归算法 | 递归展开关系 | 递归树 |
| 动态规划 | 子问题之间的转移 | 状态转移表 |
| 图算法 | 节点/边的结构变化 | 图结构变化 |
| 贪心算法 | 每步的选择策略 | 决策过程图 |
| 字符串算法 | 匹配状态 | 状态机 |
| 数学算法 | 输入输出间的数学关系 | 公式推导 |
| 数据结构操作 | 数据结构的形态变化 | 数据结构变化图 |
举例说明几个差异最大的:
动态规划——状态转移表。 画流程图没法表达「子问题怎么复用」。状态转移表一行一状态:
1 | 金额 0 1 2 3 4 5 |
表本身就是算法——每个格子怎么由前面格子算出,比任何流程图都清楚。
字符串匹配——状态机。 KMP 的核心不是步骤,而是「当前匹配到哪个状态、失配时跳到哪」:
1 | 匹配'A' 匹配'B' 匹配'A' |
数学算法——公式推导。 比如求最大公约数,欧几里得的表达重点是数学关系,不是执行步骤:
1 | gcd(a, b) = gcd(b, a mod b) |
公式两行说完的事,流程图反而要画一堆框。
四、公式也是算法的一种表达,不是另一类东西
「算法 = 代码」是误解。同一个算法,在不同抽象层级有不同的表达:
1 | 数学公式 → 算法思想(最抽象) |
比如二分查找:
1 | 公式/伪代码: |
同一种算法,多种表达形式。 选择哪种,取决于当时要讲清楚什么——讲步骤用流程图,讲证明用公式,讲实现用代码。
五、描述算法的最短完整骨架
实际工程里分析一个算法函数,通常不是只画一张图,而是走一个完整骨架:
1 | 算法名称 ← 它是什么 |
以 groupAnagrams 为例:
1 | 问题定义:把字母相同、排列不同的字符串分到一组 |
不同算法在这个骨架上,唯一会换的就是「执行过程」那一格——按类型换成对应形式。
六、在工程中的位置
这条线不是孤立的:
1 | 模块三要素(主线) 数据 + 算法 + 接口 |
- 主线讲「模块里有算法」——本线讲「算法怎么被分析、怎么表达」;
- 七条流讲「程序运行时的流」——算法图是设计时的表达,流是运行时的观察,同一结构的两面;
- 文档写作时,「算法类型 → 描述形式」直接决定一章怎么画图、怎么写。
收束
1 | 算法 ≠ 必须有流程图 |
流程图只是算法的形式之一。 形式由类型决定,类型由表达重点决定——这才是算法描述的第一原则。
