1984年图灵奖颁给了尼古拉斯·沃斯(Niklaus Wirth)。
他有一句名言:
Algorithm+Data Structures=Programs
程序=算法+数据结构
在这个算法和数据结构被忽视的时代,程序员的日常工作,真的离算法和数据结构很远吗?
我先很肤浅地理解计算机,计算机由软件和硬件组成,接收来自键盘,摄像头,网卡,麦克风等硬件的输入,转换为数据,这些数据经过操作系统和各种程序的计算后,得出结果再输出到硬盘、显示器、网卡等各种硬件。所以毕加索(Pablo Picasso)有句名言:
Computers are useless. They can only give you answers.
计算机毫无用处,除了答案,什么也给不了。
我们是程序员,重点关注的不是硬件设备如何把输入的信号转为数据,也不必过多关注输出的数据如何转换为硬件设备需要的信号。程序员最重要的工作就是编写程序,让程序正确、高效、稳定地处理输入的数据,计算出结果输出。所以从这个角度讲,输入不仅仅是程序中函数的入参,还包括从数据库提取的数据、文件中读取的内容,输入也不仅仅是函数的返回,还包括写入磁盘的内容,存入数据库的数据等等。
程序处理输入数据,转换为输出数据的过程叫做算法,这些数据之间的关系叫做数据结构。编写程序第一步肯定是仔细研究数据之间的关系,设计出数据结构,才能去设计算法。算法严重依赖数据结构。所以第一步是数据结构,第二步才是算法。
再看看程序员们日常头痛的问题:
1. bug似乎无穷无尽;
2. 代码难以维护;
3. 需求不断变更,开发跟不上产品的想法;
4. 性能问题简直是折磨;
5. 安全问题一不小心就给企业造成巨大损失;
要解决这五个问题,要养成良好的编程习惯及工作流程:
1. 无论如何,第一步也是最重要的步骤是认真研究、深刻理解需求;
2. 根据需求理清数据之间的关系,设计出数据库表结构以及程序中的数据结构;
3. 根据设计好的数据结构设计出程序的处理逻辑也就是算法,画出流程图、时序图;
4. 对输入进行数据校验和场景判断,这里所说的输入不仅是用户输入,还包括文件、数据库等;
5. 根据设计好的算法编写程序;
6. 对程序中各种可能的异常场景,比如网络、文件异常进行处理;
7. 仔细对程序进行测试;
8. 最后一步,编写规范的文档,和程序一起交付给需求方。
本专栏不讨论如何研究与理解需求,重点在第二步根据需求设计数据结构。程序员都是实用主义者,所有的数据结构都是建立在现实需求的基础上。
提起数据结构,大部分人都会觉得高深、困难,实际不然,第一:学习数据结构不需要太高的数学基础,不需要懂微积分、概率论、线性代数等高等数学知识;其次:学习数据结构不需要昂贵的硬件设备,普通的电脑完全可以胜任;第三:学习数据结构只需要掌握一门编程语言最基础的语法和极少数的API,不需要太深的编程语言基础。
数据结构,因为研究的是数据之间的关系,而不是数据本身。为了更清晰地理解,本专栏在数据结构中,会使用两种图形抽象:
数据抽象为一个点,数据之间的关系会抽象为一条线;
数据之间的关系抽象为一张表,数据抽象为单元格。
数据结构中最重要的数据结构是树,最难的是图。因为树有个特性,树的任意两个节点之间只有一条唯一的路径,所以使得很多算法问题都使用树来解决。图算法难则难在NP完全问题上,成为千禧年数学难题,也成为数据结构这门学科发展的最大障碍。所以数据结构近几年发展特别缓慢,也许你们可以解决这个数学难题,成为数据结构真正的大神。
对于常见的数据结构,我整理了以下表格:
分类 | 数据结构 | 需求/用途 |
1 数组 | 1.1 一维数组 | 存储数据、数组列表、二叉堆 |
1.2 多维数组 | 线性代数、动态规划 | |
2 线性结构 | 2.1 链表 | 存储数据、队列、栈 |
2.2 数组列表 | 存储数据 | |
2.3 循环队列 | 广度搜索 | |
2.4 栈 | 实现递归、宽度搜索、表达式解析 | |
2.5 Floyd链表查环算法 | Floyd’s Cycle-Finding Algorithm | |
2.6 Brent链表查环算法 | Brent’s Cycle-Finding Algorithm | |
3 堆 | 3.1 二叉堆 | 优先队列 |
3.2 二项堆 | 可合并优先队列 | |
3.3 斐波那契堆 | 可合并优先队列 | |
4 表 | 4.1 跳表 | 有序集合、有序映射 |
4.2 哈希表 | 存储数据快速检索、无序集合 | |
5 位图 | 5.1 位集 | 用于压缩存储数据 |
6 树 | 6.1 搜索算法 | 存储树状关系数据的检索方法 |
6.2 二叉树遍历 | ||
6.3 AVL树 | 快速查找 | |
6.4 红黑树 | 快速查找、有序映射 | |
6.5 赫夫曼树 | 数据压缩 | |
6.6 van Emde Boas树 | 快速查找 | |
6.7 并查集 | ||
6.8 B-树 | 磁盘存储,快速查找,m=4的B-树就是2-3-4树 | |
6.9 B+树 | 磁盘存储,快速查找 | |
6.10 Fenwick树 | 快速求前几项和 | |
6.11 2-3-4树 | B-树和红黑树的桥梁 | |
6.12 段树 |
7 图基础 | 7.1 图的介绍 | 点、边、路径、权重等概念 |
7.2 图的几种分类 | 加权图、无权图、有向无环图 | |
7.3 图的几种存储方式 | 邻接矩阵、邻接数组等方式 | |
8 图基本定理 | 8.1 握手定理 | |
8.2 欧拉定理(图论) | ||
8.3 Dirac’s Theorem (1952) | ||
8.4 Ore’s Theorem (1962) | 哈密尔顿图的一个充分条件,证明过程类似Dirac定理 | |
8.5 Whitney’s Inequality | ||
8.6 欧拉图论公式 | ||
8.7 Kuratowi’s Theorem | ||
8.8 4-color Theorem | ||
8.9 5-color Theorem (Heawood 1890) | ||
8.10 Max-Flow Min-Cut Theorem | ||
8.11 拉姆齐定理(1929) | Ramsey's Theorem | |
9 搜索算法 | 9.1 三色标记BFS | |
9.2 三色标记DFS | ||
9.3 DFS应用:检测环的存在 | 递归实现 | |
10 最短路径 | 10.1 单源无权BFS | 适用于无权图 |
10.2 单源加权Dijkstra | 适用于加权图 | |
10.3 单源加权D’Esopo-Pape | 适用于加权图,比Dijkstra更先进 | |
10.4 单源负权Bellman-Ford | 适用于负权图 | |
10.5 全源负权Floyd-Warshall | 适用于负权图 | |
10.6 全源负权Johnson | 适用于负权图 | |
11 最大流问题 | 11.1 Ford-Fulkerson及Edmonds-Karp算法 | 基于增广路径定理 |
11.2 Dinitz算法 | 基于增广路径定理 | |
11.3 Push-relabel algorithm | 基于溢出流算法 | |
12 最小生成树 | 12.1 prim算法 | |
12.2 Kruskal算法 | ||
12.3 Reverse-Delete算法 | ||
12.4 Borüvka Algorithm | ||
13 强连接组件 | 13.1 Kosaraju’s Algorithm | 两次DFS实现 |
13.2 Tarjan算法 | 一次DFS实现 | |
14 拓扑排序 | 14.1 Kahn算法 | |
14.2 DFS算法 | 三色标记加上路径数组实现 |