图算法对于计算机学科至关重要。成百上千的计算问题最后都可以归约为图论问题。本文主要是对《算法导论》图算法的学习笔记进行整理。
首先,我们先对图算法中的一些表达方式进行统一:给定图G=(V,E),当对该图上的一个算法的运行时间进行表述时,我们通常以图的结点数|V|和边的条数|E|作为输入的规模。另外,我们用G.V来表示图G的结点集,用G.E表示图G的边集合,也就是说,我们将结点和边看作是图的属性。
图的表示
对于图G=(V,E),可以用两种标准表示方法表示。一种表示法是将图作为邻接链表的组合,另一种是将图作为邻接矩阵来看待。
邻接链表
邻接链表表示由一个包含|V|条链表的数组Adj所构成,每个结点有一条链表。对于每个结点u,邻接链表Adj[u]包含所有与结点u之间有边相连的结点v。邻接链表在表示稀疏图上很有优势。邻接链表的一个潜在缺陷是无法快速判断一条边(u,v)是否是图中的一条边,唯一的方法是在邻接链表Adj[u]里面搜索结点v。邻接矩阵克服了这个缺陷,但付出的代价是更大的存储空间消耗。
邻接矩阵
对邻接矩阵表示来说,我们通常会将图G中的结点编为1、2、……、|V|,这种编号可以任意。在进行编号之后,图G的邻接矩阵表示一个|V|*|V|的矩阵A=(aij)予以表示,该矩阵满足下述条件:
aij=1 if (i,j)属于E;否则 aij=0.
关于图的两种表示的一个例子如下所示,其中(b)为链表,(c)为矩阵表示。

广度优先搜索
广度优先搜索是最简单的图搜索算法之一,也是许多重要的图算法的原型。Prim的最小生成树算法和Dijkstra的单源最短路径算法都使用了类似广度优先搜索的思想。< http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+PHN0cm9uZz64xcr2PC9zdHJvbmc+o7o8L3A+CjxwPrj4tqjNvEc9KFYsRSm6zdK7uPa/ycq2sfC1xNS0veG143OjrLnjtsjTxc/Iy9HL97bUzbxH1tC1xLHfvfjQ0M+1zbPQ1LXEzL3L98C0t6LP1r/J0tS009S0veG143O1vbTvtcTL+dPQveG146GjuMPL47eoxNy5u7zGy+O009S0veG143O1vcO/uPa/ybW9tO+94bXjtcS+4MDro6jX7snZtcSx38r9o6mjrM2syrHJ+rPJ0ru/w6GwueO2yNPFz8jL0cv3yvehsaGjuMPK99LU1LS94bXjc86quPm94bXjo6yw/Lqsy/nT0L/JtNNztb2077XEveG146GjPC9wPgo8cD64w8vjt6jKvNbVyse9q9LRt6LP1r3hteO6zc60t6LP1r3htePWrrzktcSx373no6zR2MbkueO2yLe9z/LAqdW5o6zSsry0ysfLtaOsy+O3qNDo0qrU2reiz9bL+dPQvuDA69S0veG143POqmu1xMv509C94bXj1q6686OsssW74beiz9a+4MDr1LS94bXjc86qayYjNDM7MbXExuTL+73hteOhozwvcD4KPHA+1Nq547bI08XPyMvRy/e5/bPMQkZT1tCjrLzZtqhHtcTB2r3TwbSx7bHtyr6jrHUuY29sb3LOqtHVyavK9NDUo6x1LmZyb250tOa3xXW1xMewx/294bXjo6x1LmS8x8K8tcTKx7njtsjTxc/Iy9HL98vjt6jL+bzGy+Oz9rXEtNNztb2073XWrrzktcS+4MDroaM8L3A+CjxwPjxicj4KPC9wPgo8cD48c3Ryb25nPs6xtPrC6zwvc3Ryb25nPsjnz8KjujwvcD4KPHA+PHByZSBjbGFzcz0="brush:java;">BFS(G,s) for each vertex u ∈ G.V-{s} u.color = WHITE u.d = inf u.front = NIL s.color = Gray s.d = 0 s.front = NIL Q = 空集 ENQUEUE(Q,s) while Q != 空集 u = DEQUEUE(Q) for each v ∈ G.Adj[u] if v.color == WHITE v.color = Gray v.d = u.d +1 v.front = u ENQUEUE(Q,v) u.color = BLACK
深度优先搜索
深度优先搜索总是对最近才发现的结点v的出发边进行探索,知道该结点的所有出发边都被发现为止。一旦结点v的所有出发边都被发现,搜索则回溯到v的前驱结点,来搜索该前驱结点的出发边。该过程一直持续到从源结点可以达到的所有的结点都被发现为止。若尚存在未发现结点,则深度优先搜索将从这些未被发现的结点中任选一个作为新的源结点,并重复同样的搜索过程。
与广度优先搜索不同的是,广度优先搜索的前驱子图形成一棵树,而深度优先搜索的前驱子图可能由多棵树组成,因为搜索可能从多个源结点重复进行。这些前驱子图形成深度优先森林。
深度优先算法在每个结点盖上一个时间戳。每个结点v有两个时间戳,第一个时间戳v.d记录结点v第一次被发现的时间,第二个v.f记录的是搜索完成对v的邻接链表扫描的时间。
伪代码如下:
DFS(G) for each vertex u∈ G.V u.color = WHITE u.front = NIL time = 0 for each vertex u∈ G.V if u.color == WHITE DFS-VISIT(G,u) DFS-VISIT(G,u) time = time+1 u.d = time u.color = Gray for each vertex v∈G:Adj[u] if v.color == WHITE v.front = u DFS-VISIT(G,V) u.color = BLACK time = time+1 u.f = time