本书研究的是算法和数据结构,本章介绍的是学习算法和数据结构所需要的基本工具。它讲解了在随后的章节中用来实现、分析和比较算法的基本原则和方法,包括Java编程模型、数据抽象、基本数据结构、集合类的抽象数据类型、算法性能分析的方法和一个案例分析。
介绍了相关的语法、语言特性和库。
本书使用Java编程语言编写的程序实现算法。我们把描述和实现算法所用到的语言特性、软件库和操作系统特性总称为基础编程模型。另一本入门书籍An Introduction to Programming in Java: An Interdisciplinary Approach也使用了这个模型。
一段Java程序(类)要么是一个静态方法(函数)库,要么定义了一个数据结构。要创建静态方法库和定义数据类型,会用到如下几种语法:原始数据类型、语句(声明、赋值、条件、循环、调用和返回)、数组、静态方法、字符串、标准输入/输出、数据抽象。
int、double、boolean、char、long、short、byte、float。
声明、赋值、条件、循环、break和continue。
声明并初始化、隐式赋值、单语句代码段、for语句。
创建、初始化、二维数组。
static、递归。
自定义标准库。
String。
标准输入输出、标准绘图库。
以二分查找作为例子简单回顾。
介绍了用Java实现抽象数据类型的过程,包括定义它的应用编程接口(API)然后通过Java的类机制来实现它以供各种用例使用——自定义类。
为了编写一个使用Counter(计数器)的简单数据类型的程序,需要写一份API,并重写toString()方法。导包,创建对象,使用对象,将对象返回。
对象的数组。
介绍了很多Java内置的抽象数据类型:画图的、信息处理的、字符串、输入输出。
介绍了类的相关知识,如构造函数、实例方法等。
举例解释上一节:日期、累加器、可视化累加器。
封装、算法与抽象数据类型、接口、继承、字符串特点、等价性、内存管理、不可变形、异常、断言。
用数组、变长数组和链表实现了背包、队列和栈的API,它们是全书算法实现的起点和样板。
无论做什么,先写API。每份API都含有一个无参数的构造函数、一个向集合中添加单个元素的方法、一个测试集合是否为空的方法和一个返回集合大小的方法。队列和栈都含有一个能够删除特定元素的方法。
泛型:我们定义一个集合,但不知道集合中储存数据的具体类型是什么,用 < T>表示。
自动装箱:int —> Integer
可迭代的集合类型:就是可以访问集合中的每一个元素的意思。
背包:一种元素之间没有相对顺序的集合,可以理解成一个背包中的弹球,每次迭代都是没有顺序的。我认为正是因为没有顺序,所以没有明确的办法区分出某一个元素,因此背包才不支持删除元素。
先进先出队列:好像没啥说的,就是队列呗。
下压栈:压栈弹栈,先进后出,基础概念。
算术表达式求值:用两个栈(一个用于保存运算符,一个用于保存操作数)实现表达式的运算。在不同的地方看过两次,真正用代码实现过一次,现在看来还跟陌生的东西一样。跟程序的机器级表示有神似之处。
定容栈:作为热身,先来看一种表示容量固定的字符串栈的抽象数据类型,它只处理String值,要求用例指定容量且不支持迭代。在其构造函数中指定指定容量,用push()添加一个字符串,用pop()弹出一个字符串,用N的大小记录字符串数量,用N是否等于0判断栈是否为空。
public class FixedCapacityStackOfStrings {
private String[] a; // stack entries
private int N; // size
public FixedCapacityStackOfStrings(int cap) {
a = new String[cap];
}
public boolean isEmpty() {
return N == 0;
}
public int size() {
return N;
}
public void push(String item) {
a[N++] = item;
}
public String pop() {
return a[--N];
}
}
泛型:使用Item代替String,理论上就可以处理认识类型的数据了。
调整数组的大小:因为选择了用数组表示栈的容量,如果需要调整栈的大小,就需要调整数组的大小。说白了就是创建一个大小符合要求的新数组,然后把老数组的内容复制到新数组中,具体的代码比较简单就不贴了。
对象游离:说白了就是内存泄漏。
迭代:集合类数据类型的基本操作之一就是,能够使用Java的foreach语句通过迭代遍历并处理集合中的每个元素。我们决定使用Java的接口机制来实现迭代,即在类的声明中加入implements Iterable< Item>。当然,具体的方法实现在不同的需求中需要做不同的改动。
public interface Iterator- {
boolean hasNext();
Item next();
void remove();
}
结点记录:我们先用一个嵌套类来定义结点的抽象数据类型:
private class Node {
Item item;
Node next;
}
构造链表:要构造一条含有元素to、be和or的链表,我们首先为每个元素创造一个结点:
Node first = new Node();
Node second = new Node();
Node third = new Node();
并将每个结点的item域设为所需的值:
first.item = "to";
second.item = "be";
third.item = "or";
然后设置next域来构造链表:
first.next = second;
second.next = third;
链表表示的是一列元素,可以用以下数组表示同一列字符串:
String[] s = { "to", "be", "or" };
在表头插入结点:先创建一个结点oldfirst,把first保存在oldfirst中,再将一个新的结点赋给first,把它的item设为自己需要的内容,next指向oldfirst。
从表头删除结点:只需将first指向first.next。
在表尾插入节点:虽然操作简单,但不能草率的决定维护一个链接,如果不仔细斟酌,可能会出现问题。
其他位置的插入和删除操作:最好的解决方法是使用双向链表,没详讲。
遍历:
for (Node x = first; x != null; x = x.next)
{
// Process x.item.
}
栈的实现:先进后出,将栈保存为一条链表,栈的顶部即为表头,实例变量first指向栈顶,当使用push()压入一个元素时,即把一个元素添加到表头;当使用pop()删除一个元素时,即将该元素从表头删除。
public class Stack- implements Iterable
- {
private Node first; // 栈顶(最近添加的元素)
private int N; // 元素数量
private class Node { // 定义了结点的嵌套类
Item item;
Node next;
}
public boolean isEmpty() {
return first == null;
} // 或:N == 0
public int size() {
return N;
}
public void push(Item item) { // 向栈顶添加元素
Node oldfirst = first;
first = new Node();
first.item = item;
first.next = oldfirst;
N++;
}
public Item pop() { // 从栈顶删除元素
Item item = first.item;
first = first.next;
N--;
return item;
}
}
队列的实现:先进先出,将队列表示为一条从最早插入的元素到最近插入的元素的链表,实例变量first指向队列的开头,实例变量last指向队列的结尾。这样,要将一个元素入列,就把它添加到表尾;要将一个元素出列,就删除表头的结点。
public class Queue- implements Iterable
- {
private Node first; // 指向最早添加的结点的链接
private Node last; // 指向最近添加的结点的链接
private int N; // 队列中的元素数量
private class Node { // 定义了结点的嵌套类
Item item;
Node next;
}
public boolean isEmpty() {
return first == null; // 或:N == 0
}
public int size() {
return N;
}
public void enqueue(Item item) { // 向表尾添加元素
Node oldlast = last;
last = new Node();
last.item = item;
last.next = null;
if (isEmpty())
first = last;
else
oldlast.next = last;
N++;
}
public Item dequeue() { // 从表头删除元素
Item item = first.item;
first = first.next;
if (isEmpty())
last = null;
N--;
return item;
}
}
背包的实现:先进先出,不过不重要,要实现只需将栈中的push()改名为add()就行了,去掉多余的pop()。
public class Bag<Item> implements Iterable<Item> {
private Node first; // 链表的首结点
private class Node {
Item item;
Node next;
}
public void add(Item item) { // 和Stack的push()方法完全相同
Node oldfirst = first;
first = new Node();
first.item = item;
first.next = oldfirst;
}
public Iterator- iterator() {
return new ListIterator();
}
private class ListIterator implements Iterator<Item> {
private Node current = first;
public boolean hasNext() {
return current != null;
}
public void remove() {
}
public Item next() {
Item item = current.item;
current = current.next;
return item;
}
} // 为什么只有背包具体实现了迭代,之前的章节说在1.4里实现啊,存疑
}
我们在本节中研究背包、队列和栈时描述数据结构和算法的方式是全书的原型(本书中的数据结构如图)。在研究一个新的应用领域时,我们将会按照以下步骤识别目标并使用数据抽象解决问题:
性能是算法研究的一个核心问题。1.4节描述了分析算法性能的方法。
用一个连通性问题作为例子结束本章。
有序地重新排列数组中的元素是非常重要的基础算法。我们会深入研究各种排序算法,包括插入排序、选择排序、希尔排序、快速排序、归并排序和堆排序。同时我们还会讨论另外一些算法,它们用于解决几个与排序相关的问题,例如优先队列、选举以及归并。其中许多算法会成为后续章节中其他算法的基础。
从庞大的数据集中找到指定的条目也是非常重要的。我们将会讨论基本的和高级的查找算法,包括二叉查找树,平衡查找树和散列表。我们会梳理这些方法之间的关系并比较它们的性能。
图的主要内容是对象和它们的连接,连接可能有权重和方向。利用图可以为大量重要而困难的问题建模,因此图算法的设计也是本书的一个主要研究领域。我们会研究深度优先搜索、广度优先搜索、连通性问题以及若干其他算法和应用,包括Kruskal和Prim的最小生成树算法、Dijkstra和Bellman-Ford的最短路径算法。
字符串是现代应用程序中的重要数据类型。我们将会研究一系列处理字符串的算法,首先是对字符串键的排序和查找的快速算法,然后是子字符串查找、正则表达式模式匹配和数据压缩算法。此外,在分析一些本身就十分重要的基础问题之后,这一章对相关领域的前沿话题也作了介绍。
这一章将讨论与本书内容有关的若干其他前沿研究领域,包括科学计算、运筹学和计算理论。我们会介绍性地讲一下基于事件的模拟、B树、后缀数组、最大流量问题以及其他高级主题,以帮助读者理解算法在许多有趣的前沿研究领域中所起到的巨大作用。最后,我们会讲一讲搜索问题、问题转化和NP完全性等算法研究的支柱理论,以及它们和本书内容的联系。