G=(V,E)由顶点(vertex)集合V和边(edge)集合E组成。
边有两个顶点构成,还有可能有权,路径是一个顶点序列w1,w2,…wn,路径的长是路径上的边数,等于n-1。
包括有向图和无向图,有向无圈图简称DAG(Directed Acyclic Graph) 。
如果一个无向图中从每一个顶点到每个其他顶点都存在一条路径,称该无向图是连通的。
具有这样性质的有向图称强连通图,如果一个有向图不是强连通的,但是它的基础图(即去掉方向变成无向图)是连通的,那么称该有向图是弱连通图。
完全图是其每一对顶点间都存在一条边的图。
1.图的表示
邻接矩阵,用二维数组,对于每条边(u,v),设置A[u][v]=1或者权重,否则等于0。
该表示法空间需求是O(|v|^2),如果图的边较少(稀疏),则数组大部分都是0,代价很大。
如果是稀疏图,可以用邻接表,即对每一个顶点,用一个链表存放所有邻接的顶点。空间需求是O(|E|+|V|)。
如果需求中顶点名字不是数字,一般将名字散列成数字(维护一个map或者用struct保存数据),然后再用数字进行操作。
2.拓扑排序(针对DAG)
针对有向无圈图(DAG)的顶点的一种排序,它使得如果存在一条从Vi到Vj的路径,那么在排序中Vj出现在Vi的后面。
一个简单的求拓扑排序的算法是先找出任意一个没有入度的顶点,然后显示出该顶点,并将它和它的边一起从图中删除。然后,对图的其余部分应用同样的办法处理。
可以使用一个队列,初始状态把图中入度为0的点全部入队,然后每次pop一个顶点,并且减少该顶点的邻接顶点的入度,若减少后入度为0则入队,直到队列为空。最后判断输出点数是否等于顶点数,若不是则表示该图有环。
3.最短路径
针对有向图图,求两个顶点间权重和最小的路径,不考虑存在负值权重的图,因为如果存在负值,路径可以无限长(一直在负值的路径上循环走)。
3.1 无权最短路径
只对包含在路径中的边数有关系,所以可以设置所有的边权1。
可以用广度优先搜索(Breadth-First Search)的方法:按层处理顶点,距离开始点最近的顶点首先被赋值,最远的那些顶点最后被赋值。(树的层次遍历)
同样使用一个队列,首先起点入队,起点距离=0。然后每次pop一个顶点,遍历该顶点的邻接点,如果邻接点中的距离没有没更新(依然是初始化值Infinity),则更新该邻接点的距离=当前顶点距离+1,记录path值=当前顶点,然后该邻接点入队。如果已经更新了不做任何操作。
3.2 带权图最短路径 Dijkstra算法
每个阶段选择一个顶点v,它在所有未知顶点中具有最小的dv,同时算法声明s到v的最短路径是已知的,阶段的其余部分由dw值的更新工作组成。
假设顶点开始节点s是v1,第一个选择v1,路径长为0,标记为已知。然后调整邻接到v1的顶点的距离。
下一步选v4并标记为已知,更新v4的邻接点v3,v5,v6,v7(图9-23)。
接着选v2,v4是已知的所以不更新。v5当前值<经过v2的值(2+10=12),所以不更新。(图9-24)
下一个选v5,v7邻接但是不更新(3+6>5)。然后选v3,更新v6.(图9-25)
接下来选v7,更新v6(5+1=6)。(图9-26)
最后选择v6。(图9-27)
每次查找需要O(|V|),整个算法需要O(|V|^2),每次更新dw常数时间,每条边最多有一次更新,总计O(|E|),因此总得运行时间是O(|E|+|V|^2)=O(|V|^2)。如果图是稠密的,边数|E|约=|V|^2,算法基本上最优,运行时间与边数成线性关系。
如果图稀疏的,边数|E|约=|V|,算法就比较慢,这种情况下距离需要存储在优先队列中。
但是使用优先队列需要更新d的值,需要使用decreaseKey,这个至少std自带的优先队列中不提供,所以很难办。
一个解决方法是插入的时候把旧值和新值同时插入优先队列中,这样队列中每个顶点就可能有多于一个的代表,并且DeleteMin的时候要循环知道一个未知的顶点合并为止。
除了优先队列,还有配对堆的实现和斐波那契堆的实现。(不太清楚,或许以后了解了会补上)
3.3 无圈图
如果图是无圈的,顶点选取法则可以用来改进Dijkstra算法,新法则以拓扑顺序选择顶点。由于选择和更新可以在拓扑顺序执行的时候进行,所以算法能够一趟完成。
因为一个顶点v被选择以后,按照拓扑排序的法则,它没有从未知顶点发出的进入边,所以它的距离d可以不再被降低。这种选取法则不需要优先队列。
无圈图一个重要的用途是关键路径分析法,下图中每个节点表示一个必须执行的动作以及完成动作所花费的时间,称为动作节点图。
这种类型的图用来模拟方案的构建。比如方案最早完成时间是何时?从图中可以看出,沿路径A、C、F、H需要10个时间单位。另一个问题是哪些动作可以延迟,延迟多长而不至于影响最少完成时间。A、C、F、H中的任一个延迟都影响最少完成时间,而B可以被延迟2个时间单位而不至于影响最后完成时间。
4.网络流问题
给定边容量,求发点s到收点t的最大流量。下图最大流是5。
5.最小生成树
一个无向图的最小生成树就是由该图的那些连接G的所有顶点的边构成的树,且权重之和最低。
5.1 Prim算法
算法的每个阶段,都把顶点集合分成已经添加到树上的顶点集和未加到树上的顶点集,然后选择边(u,v),使得u在树上而v不在树上且(u,v)的权重最小,然后把v和边(u,v)加到树上。
5.2 Kruskal算法
每个阶段都按照最小的权选择边,并且当所选的边不产生圈的时候就把它作为取定的边。
起初每个顶点是在它自己的集合中,如果u和v在同一个集合中,那么连接它们的边就要放弃,因为他们已经连通了。如果这两个顶点不在集合中,加入该边,并合并两个顶点的集合。
6. 深度优先搜索
DFS可以生成深度优先生成树。
遍历过程中处理(v,w)时,如果w是未被标记的,就用树的一条边表示。如果处理(v,w)时w已经被标记了,就用虚线表示,称之为背向边(虚线其实不是树的一部分)。
6.1.欧拉回路
给出一个无向图,在图中找出一条路径,使得该路径对图的每条边恰好访问一次。(附加问题:结束的顶点要与开始的顶点一样)
充分必要条件:
图中每一个顶点的度(边的条数)必须是偶数。
思路:
从起点开始用DFS,每次回到起点时结束。然后把路径上的边从原图删掉,然后再对剩下图选之前路径上的有尚未访问的边的第一个顶点,进行另外一次DFS。并且把第二次DFS路径拼接到第一次DFS上。继续该过程直到所有的边都被遍历。