上一篇讲的是「你在学科的哪一格」。这一篇往下钻一层,讲那句人人都背过的话。

程序 = 数据结构 + 算法 是 Wirth 在 1976 年提出来的。它到今天仍然是对的,但它被当成了另一句话在用。


一、先看两本书的写法

有意思的是,同一个等式,不同教材的写法不一样:

出处原文的写法
《数据结构——从概念到C++实现(第 4 版)》第 4 页算法 + 数据结构 = 程序
《数据结构——从概念到C实现(第 2 版)》第 2 页数据结构 + 算法 = 程序

同一句话,加号两边调了个位置。

这不是哪本印错了。两本书都在讲同一件事,只是强调的重点不同:一本想先说算法,一本想先说结构。

但这里正好可以问一句:顺序重要吗?

加号本身是可交换的,但**「先想哪个」非常重要**。

两本书紧接着都说了同一句:一个「好」程序,首先要把问题抽象出一个适当的数据模型(结构),然后基于它设计算法。

结构在前,算法在后。这是设计顺序,不是加法交换律。


二、误读在哪

绝大多数人把这句话理解成:

程序由两个部件组成,像「桌子 = 桌面 + 桌腿」——一块是数据结构,一块是算法,拼起来就是程序。

Wirth 说的不是部件构成,是设计过程。

差别在哪?听起来很虚,但它直接影响你怎么写程序:

如果理解成「部件」如果理解成「顺序」
先想算法,再挑个数据结构装数据先看数据的形状,再想怎么处理它
数据结构只是个容器数据结构的形状决定了算法能长什么样
换语言换库无所谓换存储方式等于换问题难度

同一个需求,两种理解会导致两种完全不同的代码。

举个最常见的:查一万人里有没有某个人。

  • 「部件」理解:先写一个循环去比——算法定了,数据结构用数组。
  • 「顺序」理解:先问这些人怎么组织——如果按身份证号能建索引,查找就是一次的事,根本不需要循环。

第二种想法不是「优化」,是在设计阶段就选了另一条路。 等写完循环再优化,已经晚了。


三、加号是误导性的:两半不是相加,是相乘

这是我认为这句话里最容易漏掉的一层。

同一个算法,换个存储方式,复杂度会差一个量级:

操作用数组用链表
取第 k 个元素O(1)O(n)
在头部插入O(n)O(1)
查找某个值O(n)O(n)

算法没有变一个字,只是数据换了个放法,取第 k 个元素就从「一步」变成「走 k 步」。

所以「数据结构 + 算法」这个加号,会让人以为是两块独立的成本相加。实际是乘积关系:结构选错,算法再好也白搭;结构选对,笨算法也能跑得动。

一句实操的经验:

性能问题,八成的根子在结构,不在算法。


四、这个等式漏了什么

1976 年的程序和 2026 年的程序,不是同一种东西。

Wirth 那个年代,程序基本是:读入数据 → 计算 → 输出结果。单线程、独占机器、跑完就结束。

今天的程序里有五样东西,等式里没有:

1. 并发

多个执行流同时改同一份数据。这不是「算法」也不是「数据结构」,这是顺序的不确定性。你要处理的是「谁先谁后」,而不是「算得快不快」。

2. I/O 与阻塞

程序大部分时间在等——等磁盘、等网络、等用户。一个「算法上 O(n)」的程序,可能 99% 的时间卡在等磁盘。这时候优化算法是白费劲。

3. 持久化状态

程序死了,数据还在。这意味着你要考虑「写到一半断电」之后是什么状态。这是事务、日志、幂等的问题,不是数据结构能解决的。

4. 网络对端

对面是另一个程序,它可能慢、可能超时、可能返回垃圾、可能永远不回。分布式系统里有个残酷的事实:你永远无法确认对面收到了。 这类问题叫「两军问题」,有严格的证明说明它无解(不是不会做,是做不了)。

5. 人

界面、错误提示、可恢复性、可理解性。程序是给人用的,这一条决定了它能不能被用起来。

所以:等式没错,是不完整。

一句判据,我用它判断过很多次:

你觉得程序「写完了」,但它还不能用——缺的通常不是算法,也不是数据结构,是这五样里的一个。


五、那这句话还有什么用

有人可能会说:既然它不完整,是不是就别讲了?

恰恰相反。它仍然是最好的入门框架,因为它是所有框架里最简洁的一个。

它的价值不在「完整」,在「可操作」:

  1. 拿到一个问题,先问:数据长什么样? (数组?树?图?还是根本没结构?)
  2. 再问:在它上面做什么? (查?排?最短路径?)
  3. 最后才问:用什么语言写?

顺序颠倒,代价很大。 先写代码再想结构,等于先盖墙再画图纸。


六、一个反例,说明顺序错了会怎样

经典的「最大子段和」问题:给定一列数(有正有负),求和最大的连续一段。

按「先算法后结构」的思路,你会写三重循环:枚举起点、枚举终点、算这段的和。复杂度 O(n³)。数据一大就跑不动。

按「先看结构」的思路,会先注意到一件事:「这一段的和」是可以从前一段推出来的——不需要每次重算。

于是三重循环塌成两重(O(n²)),再往下想,还能塌成一重(O(n))。

同一份数据,同一个需求,四种写法:O(n³) → O(n²) → O(n log n) → O(n)。

这不是「优化」,这是从看问题的角度不同长出来的不同解法。

下一篇讲怎么判断这些复杂度,下下篇讲这四种写法背后的四种设计范式——以及为什么最快的那个解法可能是错的。


参考

  • 《数据结构——从概念到C++实现(第 4 版)》第 4 页;《数据结构——从概念到C实现(第 2 版)》第 2 页(等式的两种写法)。
  • 「五样漏掉的东西」「加号是乘积」「八成根子在结构」是我自己的归纳,不是原书内容。
  • 两军问题的严格证明见《计算机学科概论(第 3 版)》第 41 页。