第1章 基础
本书的目的是研究多种重要而实用的算法,即适合用计算机实现的解决问题的方法。和算法关系最紧密的是数据结构,即便于算法操作的组织数据的方法。本章介绍的就是学习算法和数据结构所需要的基本工具。
首先要介绍的是我们的基础编程模型。本书中的程序只用到了Java语言的一小部分,以及我们自己编写的用于封装输入输出以及统计的一些库。1.1节总结了相关的语法、语言特性和书中将会用到的库。
接下来我们的重点是数据抽象并定义抽象数据类型(ADT)以进行模块化编程。在1.2节中我们介绍了用Java实现抽象数据类型的过程,包括定义它的应用程序编程接口(API)然后通过Java的类机制来实现它以供各种用例使用。
之后,作为重要而实用的例子,我们将学习三种基础的抽象数据类型:背包、队列和栈。1.3节用数组、变长数组和链表实现了背包、队列和栈的API,它们是全书算法实现的起点和样板。
性能是算法研究的一个核心问题。1.4节描述了分析算法性能的方法。我们的基本做法是科学式的,即先对性能提出假设,建立数学模型,然后用多种实验验证它们,必要时重复这个过程。
我们用一个连通性问题作为例子结束本章,它的解法所用到的算法和数据结构可以实现经典的union-find抽象数据结构。
算法
编写一段计算机程序一般都是实现一种已有的方法来解决某个问题。这种方法大多和使用的编程语言无关——它适用于各种计算机以及编程语言。是这种方法而非计算机程序本身描述了解决问题的步骤。在计算机科学领域,我们用算法这个词来描述一种有限、确定、有效的并适合用计算机程序来实现的解决问题的方法。算法是计算机科学的基础,是这个领域研究的核心。
要定义一个算法,我们可以用自然语言描述解决某个问题的过程或是编写一段程序来实现这个过程。如发明于2300多年前的欧几里得算法所示,其目的是找到两个数的最大公约数:
自然语言描述
计算两个非负整数p和q的最大公约数:若q是0,则最大公约数为p。否则,将p除以q得到余数r, p和q的最大公约数即为q和r的最大公约数。
Java语言描述
public static int gcd(int p, int q) { if (q == 0) return p; int r = p % q; return gcd(q, r); }
欧几里得算法
如果你不熟悉欧几里得算法,那么你应该在学习了1.1节之后完成练习1.1.24和练习1.1.25。在本书中,我们将用计算机程序来描述算法。这样做的重要原因之一是可以更容易地验证它们是否如所要求的那样有限、确定和有效。但你还应该意识到用某种特定语言写出一段程序只是表达一个算法的一种方法。数十年来本书中许多算法都曾被表达为多种编程语言的程序,这正说明每种算法都是适合于在任何计算机上用任何编程语言实现的方法。
我们关注的大多数算法都需要适当地组织数据,而为了组织数据就产生了数据结构,数据结构也是计算机科学研究的核心对象,它和算法的关系非常密切。在本书中,我们的观点是数据结构是算法的副产品或是结果,因此要理解算法必须学习数据结构。简单的算法也会产生复杂的数据结构,相应地,复杂的算法也许只需要简单的数据结构。本书中我们将会研讨许多数据结构的性质,也许本书就应该叫《算法与数据结构》。
当用计算机解决一个问题时,一般都存在多种不同的方法。对于小型问题,只要管用,方法的不同并没有什么关系。但是对于大型问题(或者是需要解决大量小型问题的应用),我们就需要设计能够有效利用时间和空间的方法了。
学习算法的主要原因是它们能节约非常多的资源,甚至能够让我们完成一些本不可能完成的任务。在某些需要处理上百万个对象的应用程序中,设计优良的算法甚至可以将程序运行的速度提高数百万倍。在本书中我们将在多个场景中看到这样的例子。与此相反,花费金钱和时间去购置新的硬件可能只能将速度提高十倍或是百倍。无论在任何应用领域,精心设计的算法都是解决大型问题最有效的方法。
在编写庞大或者复杂的程序时,理解和定义问题、控制问题的复杂度和将其分解为更容易解决的子问题需要大量的工作。很多时候,分解后的子问题所需的算法实现起来都比较简单。但是在大多数情况下,某些算法的选择是非常关键的,因为大多数系统资源都会消耗在它们身上。本书的焦点就是这类算法。我们所研究的基础算法在许多应用领域都是解决困难问题的有效方法。
计算机程序的共享已经变得越来越广泛,尽管书中涉及了许多算法,我们也只实现了其中的一小部分。例如,Java库包含了许多重要算法的实现。但是,实现这些基础算法的简化版本有助于我们更好地理解、使用和优化它们在库中的高级版本。更重要的是,我们经常需要重新实现这些基础算法,因为在全新的环境中(无论是硬件的还是软件的),原有的实现无法将新环境的优势完全发挥出来。在本书中,我们的重点是用最简洁的方式实现优秀的算法。我们会仔细地实现算法的关键部分,并尽最大努力揭示如何进行有效的底层优化工作。
为一项任务选择最合适的算法是困难的,这可能会需要复杂的数学分析。计算机科学中研究这种问题的分支叫做算法分析。通过分析,我们将要学习的许多算法都有着优秀的理论性能;而另一些我们则只是根据经验知道它们是可用的。我们的主要目标是学习典型问题的各种有效算法,但也会注意比较不同算法之间的性能差异。不应该使用资源消耗情况未知的算法,因此我们会时刻关注算法的期望性能。
本书框架
接下来概述一下全书的主要内容,给出涉及的主题以及本书大致的组织结构。这组主题触及了尽可能多的基础算法,其中的某些领域是计算机科学的核心内容,通过对这些领域的深入研究,我们找出了应用广泛的基本算法,而另一些算法则来自计算机科学和相关领域比较前沿的研究成果。总之,本书讨论的算法都是数十年来研发的重要成果,它们将继续在快速发展的计算机应用中扮演重要角色。
第1章 基础
它讲解了在随后的章节中用来实现、分析和比较算法的基本原则和方法,包括Java编程模型、数据抽象、基本数据结构、集合类的抽象数据类型、算法性能分析的方法和一个案例分析。
第2章 排序
有序地重新排列数组中的元素是非常重要的基础算法。我们会深入研究各种排序算法,包括插入排序、选择排序、希尔排序、快速排序、归并排序和堆排序。同时我们还会讨论另外一些算法,它们用于解决几个与排序相关的问题,例如优先队列、选举以及归并。其中许多算法会成为后续章节中其他算法的基础。
第3章 查找
从庞大的数据集中找到指定的条目也是非常重要的。我们将会讨论基本的和高级的查找算法,包括二叉查找树、平衡查找树和散列表。我们会梳理这些方法之间的关系并比较它们的性能。
第4章 图
图的主要内容是对象和它们的连接,连接可能有权重和方向。利用图可以为大量重要而困难的问题建模,因此图算法的设计也是本书的一个主要研究领域。我们会研究深度优先搜索、广度优先搜索、连通性问题以及若干其他算法和应用,包括Kruskal和Prim的最小生成树算法、Dijkstra和Bellman-Ford的最短路径算法。
第5章 字符串
字符串是现代应用程序中的重要数据类型。我们将会研究一系列处理字符串的算法,首先是对字符串键的排序和查找的快速算法,然后是子字符串查找、正则表达式模式匹配和数据压缩算法。此外,在分析一些本身就十分重要的基础问题之后,这一章对相关领域的前沿话题也作了介绍。
第6章 背景
这一章将讨论与本书内容有关的若干其他前沿研究领域,包括科学计算、运筹学和计算理论。我们会介绍性地讲一下基于事件的模拟、B树、后缀数组、最大流量问题以及其他高级主题,以帮助读者理解算法在许多有趣的前沿研究领域中所起到的巨大作用。最后,我们会讲一讲搜索问题、问题转化和NP完全性等算法研究的支柱理论,以及它们和本书内容的联系。
学习算法是非常有趣和令人激动的,因为这是一个历久弥新的领域(我们学习的绝大多数算法都还不到“五十岁”,有些还是最近才发明的,但也有一些算法已经有数百年的历史)。这个领域不断有新的发现,但研究透彻的算法仍然是少数。本书中既有精巧、复杂和高难度的算法,也有优雅、朴素和简单的算法。在科学和商业应用中,我们的目标是理解前者并熟悉后者,这样才能掌握这些有用的工具并学会算法式思考,以迎接未来计算任务的挑战。