算法第四版(谢路云译)
官方网站:http://algs4.cs.princeton.edu/home/有部分源代码和部分课后习题答案。
个人练习代码:https://github.com/morefans/AlgorithmsFourthEdition
第4章 图
4.1 无向图
l 图是由一组顶点和一组能够将两个顶点相连的边组成的。
l 自环:即一条连接一个顶点和其他自身的边。平行边:连接同一对顶点的两条边。多重图:含有平行边的图。简单图:没有平行边或自环的图。
l 路径,简单路径,简单环,路径或环的长度,连通,连通图。树是无环连通图。森林,连通图的生成树,生成树森林。
l 度数:某个顶点的度数就是依附于它的边的总数。图的密度,稀疏图,稠密图,二分图。
l 图的表示应该满足:它必须为可能在应用中碰到的各种类型的图预留出足够的空间;实例方法的实现一定要快。待选:邻接矩阵(空间不能满足且不能表示平行边),边的数组(简单但是不快),邻接表数组(满足条件)。
l 深度优先搜索:深度优先搜索标记与起点连通的所有顶点所需的时间和顶点的度数之和成正比,和路径的长度成正比。(小巧而又强大的算法)
l Tremaux搜索(走迷宫)。
l 广度优先遍历:解决单点最短路径问题。(所需的时间在最坏情况下和V+E成正比)
l 对于从s可达的任意顶点v,广度优先搜索都能找到一条从s到v的最短路径(没有其他从s到v的路径所含的边比这条路径更少)。
l 深度优先搜索的与预处理使用的时间和空间与V+E成正比且可以在常数时间内处理关于图的连通性查询。
l 解决图连通性问题时union-find算法其实更快,因为不需要对图进行预处理,因此在完成只需要判断连通性或是需要完成有大量连通性查询和插入操作混合等类似的任务时,更倾向使用union-find算法。
l 检测环:给定的图是否含有环。
l 双色问题:给定图是否是二分图。
l 以下为“答疑”和“练习”中的知识点:
l 不把所有的算法都实现在Graph.Java中是避免宽接口。
l 顶点v的离心率是它和离它最远的顶点的最短距离。图的直径即所有顶点的最大离心率,半径为所有顶点的最小离心率,中心为离心率和半径相等的顶点。图的周长是图中最短环的长度,如果是无环图,则它的周长为无穷大。
l 欧拉环:图中经过每条边一次且仅一次的环。哈密尔顿环是指不重复地走过所有的点,并且最后还能回到起点的回路。
4.2 有向图
l 有向图:由一组顶点和一组有方向的边组成的,每条有方向的边都连接着有序的一对顶点(顶点分别称为头和尾)。
l 单点可达性:是否存在一条从s到给定顶点v的有向路径。多点可达性:是否存在一条从集合中的任意顶点到达给定顶点v的有向路径。在有向图中,深度优先搜索标记由一个集合的顶点可达的所有顶点所需的时间与被标记的所有顶点的出度之和成正比。
l 单点有向路径,单点最短有向路径。优先级限制下的调度问题。拓扑排序:给定一幅有向图,将所有的顶点排序,使得所有的有向边均从排在前面的元素指向排在后面的元素(或者说明无法做到这一点)。拓扑排序典型应有:任务调度,课程安排,继承,电子表格,符号链接。
l 如果一个有优先级限制的问题中存在有向环,那么这个问题一定是无解的。有向无环图(DAG):就是一幅不含有向环的有向图。
l 当且仅当一幅有向图是无环图时它才能进行拓扑排序。一幅有向无环图的拓扑排序即为所有顶点的逆后序排列。使用深度优先搜索对有向无环图进行拓扑排序所需的时间和V+E成正比。
l 解决任务调度类应用通常需要以下三步:指明任务和优先级条件;不断检测并去除有向图中的所有环以确保存在可行方案;使用拓扑排序解决调度问题。
l 强连通:两个顶点互相可达。强连通图:一幅有向图中任意两个顶点都是强连通的。强连通性具有:自反性,对称性,传递性。
l Kosaraju算法:高效计算强连通分量。使用深度优先查找给定有向图G的方向图GR,根据由此得到的所有顶点的逆后序再次深度优先搜索处理有向图G(Kosaraju算法),其构造函数中的每一次递归调用所标记的顶点都在同一个强连通分量之中。Kosaraju算法的预处理所需的时间和空间与V+E成正比且支持常数时间的有向图强连通性的查询。
4.3 最小生成树
l 加权图:是一种每条边关联一个权值或是成本的图模型。
l 图的生成树:是它的一棵含有其所有顶点的无环连通子图。
l 一幅加权图的最小生成树(MST)是它的一棵权值(树中所有边的权值之和)最小的生成树。
l 一些约定:只考虑连通图(非连通图不存在最小生成树,只有最小生成森林);边的权重不一定表示距离;边的权重也可能是0或者负数(边权重都为正数的话最小生成树定义为连接所有顶点且总权重最小的子图就足够了);所有边的权重都各不相同(若权重可以相同那么最小生成树就不一定是唯一的了,但这个假设并不会限制算法的适用范围)。
l 图的一种切分是将图的所有顶点分为两个非空且不重叠的两个集合。横切边是一条连接两个属于不同集合的顶点和边。
l 切分定理:在一幅加权图中,给定任意的切分,它的横切边中的权重最小者必然属于图的最小生成树。
l 最小生成树的贪心算法:下面这种方法会将含有V个顶点的任意加权连通图中属于最小生成树的边标记为黑色:初始状态下所有边均为灰色,找到一种切分它产生的横切边均不为黑色,将它权重最小的横切边标记为黑色,反复,知道标记了V-1条黑色边为止。
l Prim算法:计算最小生成树的方法,它的每一步都会为一棵生长中的树添加一条边,一开始这棵树只有一个顶点,然后会向它添加V-1条边,每次总是将下一条连接树中的顶点与不在树中的顶点且权重最小的边(黑色表示)加入树中(即由树中的顶点所定义的切分中的一条横切边)。能够计算任意加权连通图的最小生成树。
l Prim算法的延时实现计算一幅含有V个顶点和E条边的连通加权无向图的最小生成树所需的空间与E成正比,所需的时间与ElogE成正比(最坏情况)。
l Prim算法的即时实现计算一幅含有V个顶点和E条边的连通加权无向图的最小生成树所需空间和V成正比,所需的时间和ElogV成正比(最坏情况)。
l Kruskal算法:按照边的权重顺序(从小到大)处理它们,将边加入最小生成树中(图中的黑色边),加入的边不会与已经加入的边构成环,直到树中含有V-1条边为止。这些黑色的边逐渐由一片森林合并为一棵树,也就是图的最小生成树。Kruskal算法能能够计算任意加权连通图的最小生成树。
l Kruskal算法的计算一幅含有V个顶点和E条边的连通加权无向图的最小生成树所需的空间和E成正比,所需的时间和ElogE成正比(最坏情况)。
l Prim算法和Kruskal算法不能处理有向图处理问题。有向图更难,称为最小树形图问题。
4.4 最短路径
l 在一幅加权有向图中,从顶点s到顶点t的最短路径是所有从s到t的路径中的权重最小者。
l 最短路径的性质:路径是有向的;权重不一定等价于距离;并不是所有顶点就是可达的;负权重会使问题更复杂;最短路径一般都是简单的;最短路径不一定是唯一的;可能存在平行边和自环。
l 最短路径树:给定一幅加权有向图和一个顶点s,以s为起点的一棵最短路径树是图的一幅子图,它包含s和从s可达的所有顶点。这棵有向树的根结点为s,树的每条路径都是有向图中的一条最短路径。
l 放松操作(relax):放松边v->w意味着检查从s到w的最短路径是否是先从s到v,然后再由v到w。如果是,则根据这个情况更新数据结构的内容。由v到达w的最短路径是distTo[v]与e.weight()之和,如果这个值不小于distTo[w],称这条边失效了并将它忽略,如果这个值更小就更新数据。(松弛术语来源于橡皮筋比喻:放松一条边就像将橡皮筋转移到一条更短的路径上,从而缓解了橡皮筋的压力)
l 最短路径的最优性条件:令G为一幅加权有向图,顶点s是G中的起点,distTo[]是一个由顶点索引的数组,保存的是G中路径的长度,对于从s可达的所有顶点v,distTo[v]的值是从s到v的某条路径的长度,对于从s不可达的所有顶点v,该值为无穷大。当且仅当对于从v到w的任意一条边e,这些值都是满足distTo[w]<=distTo[v]+e.weight()时(换句话说,不存在有效边时),它们是最短路径的长度。
l 通用最短路径算法:将distTo[s]初始化为0,其他distTo[]元素初始化为无穷大,继续如下操作:放松G中的任意边,直到不存在有效边为止。对于任意从s可达的顶点w,在进行这些操作之后,distTo[w]的值即为从s到w的最短路径的长度(且edgeTo[w]的值即为该路径上的最后一条边)。
l Dijkstra算法:首先将distTo[s]初始化为0,distTo[]中的其他元素初始化为正无穷,然后将distTo[]最小的非树顶点放松并加入树中,如此这般,直到所有的顶点都在树中或者所有的非树顶点的distTo[]值均为无穷大。Dijkstra算法能够解决边权重非负的加权有向图的单起点最短路径问题。
l 在一幅含有V个顶点和E条边的加权有向图中,使用Dijkstra算法计算根结点为给定起点的最短路径树所需的空间与V成正比,时间与ElogV成正比(最坏情况下)。
l 无环加权有向图中的最短路径算法:能够在线性时间内解决单点最短路径问题;能够处理负权重的边;能够解决相关的问题,例如找出最长的路径。
l 按照拓扑排序放松顶点,就能在和E+V成正比的时间内解决无环加权有向图的单点最短路径问题。
l 解决无环加权有向图中的最长路径问题所需的时间与E+V成正比。
l 解决并行任务调度问题的关键路径方法的步骤如下:创建一幅无环加权有向图,其中包含一个起点s和一个重点t且每个任务都对应着两个顶点(一个起始顶点和一个结束顶点)。对于每个任务都有一条从它的其实顶点指向结束顶点的边,边的权重为任务所需的时间。对于每条优先级限制v->w添加一条从v的结束顶点指向w的起始顶点的权重为0的边。我们还需要为每个任务添加一条从起点指向该任务的其实顶点的权重为0的边以及一条从该任务的结束顶点到终点的权重为0的边。这样每个任务预计的开始时间即为从起点到它的起始顶点的最长距离。
l 解决优先级限制下的并行任务调度问题的关键路径算法所需的时间为线性级别。
l 相对最后期限限制下的并行任务调度问题是一个加权有向图中的最短路径问题(可能存在环和负权重边)。
l 负权重环:加权有向图中的负权重环是一个总权重(环上的所有边的权重之和)为负的有向环。(绕着这个环一直转圈子就能将总权重降到任意小的权重值)
l 当且仅当加权有向图中至少存在一条从s到v的有向路径且所有从s到v的有向路径上的任意顶点都不存在与任何负权重环中时,s到v的最短路径才是存在的。
l Bellman-Ford算法:在任意含有V个顶点的加权有向图中给定起点s,从s无法到达任何负权重环,以下算法能够解决其中的单点最短路径问题:将distTo[s]初始化为0,其他distTo[]元素初始化为无穷大,以任意顺序放松有向图的所有边,重复V轮。Bellman-Ford算法所需的时间和EV成正比,空间和V成正比;
l 套汇问题等价于加权有向图中的负权重环的检测问题。
第5章 字符串
l 字符,不可变性,索引,长度,子字符串,字符串的连接,字符数组,字符索引数组,数字。
5.1 字符串排序
l 低位优先(Least-Significant-Digit-First):从右到左检查键中的字符,最适合用于键长度都相同的字符串排序应用。
l 高位优先(Most-Significant-Digit-First):从左到右检查键中的字符。
l 键索引计数法:适用于小整数键的简单排序方法。键索引计数法排序N个键为0到R-1之间的整数的元素需要访问数组11N+4R+1次。
l 低位优先的字符串排序算法能够稳定地将定长字符串排序。
l 对于基于R个字符的字母表的N个以长为W的字符串为键的元素,低位优先的字符串排序需要访问~7WN+3WR次数组,使用的额外空间与N+R成正比。
l 高位优先的字符串排序对字符串末尾处理需要特别注意,小型子数组切换到插入排序是必要的,含有大量等值键的子数组排序会比较慢。
l 要将基于大小为R的字母表的N个字符串排序,高位优先的字符串排序算法平均需要检查NlogRN个字符;访问数组的次数在8N+3R到~7wN+3wR之间,其中w是字符串的平均长度;最坏情况下高位优先的字符串排序算法所需的空间与R乘以最长的字符串的长度之积成正比(再加上N)。
l 三向字符串快速排序:根据高位优先的字符串排序算法改进快速排序,根据键的首字母进行三向切分,仅在中间子数组中的下一个字符(因为键的首字母都与切分字符相等)继续递归排序。要将含有N个随机字符串的数组排序,三向字符串快速排序平均需要比较字符~2NlnN次。
5.2 单词查找树
l 值为空的结点在符号表中没有对应的键,它们的存在是为了简化单词查找树中的查找操作。
l R向单词查找树:基于含有R个字符的字母表的单词查找树。结点保存一个value值为从根结点到该结点合起来的字符串,保存一个大小为R的子链接数组。
l 单词查找树的链表结构(形状)和键的插入或删除顺序无关:对于任意给定的一组键,其单词查找树都是唯一的。
l 在单词查找树中查找一个键或者插入一个键时,访问数组的次数最多为键的长度加1。
l 字母表的大小为R,在一棵由N个随机键构造的单词查找树中,未命中查找平均所需检查的结点数量为~logRN。
l 一棵单词查找树中的链接总数在RN到RNw之间,其中w为键的平均长度。(当所有键均较短时链接总数接近于RN;当所有键均较长时链接总数接近于RNw;缩小R能节省大量的空间)
l 单向分支:解决长尾巴问题。
l 三向单词查找树:每个结点含有一个字符、三条链接和一个值,三条链接对应当前字母小于等于大于结点字母的所有键。
l 由N个平均长度为w的字符串构造的三向单词查找树中的链接总数在3N到3Nw之间。
l 在一棵由N个随机字符串构造的三向单词查找树中,查找未命中平均需要比较字符~lnN次。除~lnN次外,一个插入或命中的查找会比较一次被查找的键中的每个字符。
l 三向单词查找树最大好处是它能够很好地适应实际应用中可能出现的被查找键的不规则性。(实际应用中的键来自大型字母表而各字符使用非常不均衡,且实际应用程序中的键常常有着类似的结构)
l 由N个随机字符串构造的根结点进行了Rt向分支且不含有外部单向分支的三向单词查找树中,一个插入或查找操作平均需要进行约lnN-tlnR次字符比较。
5.3 子字符串查找
l 暴力子字符串查找算法:用一个指针i跟踪文本,一个指针j跟踪模式。最坏情况下,其在长度为N的文本中查找长度为M的模式需要~NM次字符比较。
l Knuth-Morris-Pratt子字符串查找算法(KMP算法):主要思想是提前判断如何重新开始查找,这种判断只取决于模式本身。
l 对于长度为M的模式字符串和长度为N的文本,KMP字符串查找算法访问的字符不会超过M+N个。
5.4 正则表达式
l
5.5 数据压缩
l 数据压缩的基础模型(两个能够读写比特流的黑盒子):1、压缩盒,能够将一个比特流B转化为压缩后的版本C(B)。2、展开盒,能够将C(B)转化回B。(无损压缩模型)
l 不存在能压缩任意比特流的算法。
l 游程编码:一长串重复的比特,用前4位表示长度,然后是0或者1。典型应用为位图的无损压缩。随着分辨率的提高游程编码的效果会大大提高。
l 霍夫曼压缩(Huffman)(变长前缀码):所有字符编码都不会称为其他字符编码的前缀,便是前缀码(不需要分隔符)。霍夫曼压缩不仅对于自然语言而且对各种来诶性的文件都有效果。
l 对于任意前缀码,编码后的比特字符串的长度等于相应单词查找树的加权外部路径。加权外部路径长度是所有叶子的权重(频率)和深度之积的和。
l 给定一个含有r个字符的集合和它们的频率,霍夫曼算法所构造的前缀码是最优的。
l LZW压缩算法:
第6章 背景
6.2 B-树
l B-树的成本模型:我们使用页的访问次数(无论读写)作为外部查找算法的成本模型。这里用来泛指基于固定页大小的多向平衡查找树的数据结构。
l 一棵M阶B-树(M为正偶数)或者仅是一个外部k-结点(含有k个键和相关信息的树),或者由若干内部k-结点(每个结点都含有k个键和k条链接,链接指向的子树表示了键之间的间隔区域)组成,它的结构性质如下:从根结点到每个外部结点的路径长度均相同(完美平衡);对于根结点,k在2到M-1之间,对于其他结点k在M/2到M-1之间。
l 含有N个元素的M阶B-树中的一次查找或插入操作需要logMN~logM/2N次探查,在实际情况下这基本是一个常数。
6.4 网络流算法
l 一个流量网络是一张边的权重(这里称为容量)为正的加权有向图。一个st-流量网络有两个已知的顶点,即起点s和终点t。
l st-流量网络中的st流量配置是由一组和每条边相关联的值组成的集合,这个值被称为边的流量。如果所有边的流量均小于边的容量且满足每个顶点的局部平衡(即流入量等于流出量,净流量为0,s和t除外),那么久称这种流量配置方案是可行的。
l 最大st-流量:给定一个st-流量网络,找到一种st-流量配置,使得从s到t的流量最大化。
l Ford-Fulkerson最大流量算法(增广路径算法):网络中的初始流量为0,沿着任意从起点到终点(且不含有饱和的正向边或是空逆向边)的增广路径增大流量,直到网络中不存在这样的路径为止。
l 未完。