基本的图算法

图的简单概念

    我们通常使用的图为有向图和无向图。有向图G是一个二元组(V, E),其中V是有限集,而E是V上的二元关系。集合V称为图G的顶点集,其元素称为顶点。集合E是G的边集,其元素称为边。在无向图G=(V, E)中,边集E是由无序的顶点对组成,而不是有序对。也就是说,一条边是一个集合{u, v},其中u, v∈V且u≠v。通常用符号(u, v)表示边,而不用集合符号{u, v},但(u, v)和(v, u)被视为同一条边。无向图中不允许出现自环。下图中a)为有向图,b)为无向图。
图1
    无向图和有向图的许多定义大体上是相同的。如果(u, v)是有向图G=(V, E)中的一条边,则称(u,v)射出或离开顶点u,且(u,v)射入或进入顶底v。如果(u,v)是无向图G=(V,E)中的一条边,则称(u,v)与顶点u和v关联。
    如果(u,v)是图G=(V,E)中的一条边,则称顶点v邻接于顶点u。当图是无向图时,邻接关系是对称的。当图是有向图时,邻接关系不一定是对称的。如果在有向图中,v邻接于u,写作u→v。
    无向图中顶点的度是指关联于该顶点的边的数目。如果一个顶点的度是0,则它是孤立的。有向图中,顶点的出度是指离开该顶点的边的数目,顶点的入度是指进入该顶点的边的数目。有向图中顶点的度是该顶点的入度和出度之和。
    图G=(V,E)中从顶点u到顶点u’的一条长度为k的路径是一个顶点序列(v0,v1,v2,•••,vk),其中u=v0,u’=vk,且(v(i-1), vi)∈E,i=1,2,•••,k。路径的长度是路径中边的数目。如果从顶点u到顶点u’存在一条路径p,则称u’是从u经过p可达的。如果路径中所有顶点互不相同,则称路径是简单的。
    如果一个无向图中每个顶点从所有其他顶点都是可达的,则称该图是连通的。图的连通分量是顶点在“从••••••可达”关系下的等价类。若一个无向图只有一个连通分量,则该无向图连通。
    如果一个有向图中任意两个顶点互相可达,则该有向图是强连通的。有向图的强连通分量是“相互可达”关系下顶点的等价类。如果一个有向图只有一个强连通分量,则它是强连通的。

图的表示

    对于图G=(V,E),可以用两种标准表示方法表示。一种表示法将图作为邻接链表的组合,另一种表示法则将图作为邻接矩阵来看待。邻接链表因为在表示稀疏图(边的条数|E|远远小于|V|²的图)时非常紧凑而成为通常的选择。在稠密图(|E|接近|V|²的图)情况下,可能更倾向于使用邻接矩阵表示法。
    有向图和无向图的两种表示如下图所示:
图2
图3
    对于图G=(V,E)来说,其邻接链表表示由一个包含|V|条链表的数组Adj所构成,每个结点有一条链表。对于每个结点u∈V,邻接链表Adj[u]包含所有与结点u之间有边相连的结点v,即Adj[u]包含图G中所有与u邻接的结点。由于邻接链表代表的是图的边,在伪代码里,将数组Adj看作是图的一个属性。
    如果图是一个有向图,则对于边(u,v)来说,结点v将出现在链表Adj[u]中。如果G是一个无向图,则对于边(u,v)来说,结点v将出现在链表Adj[u]中,结点u将出现在链表Adj[v]中。
    对邻接链表稍加修改,既可以用来表示权重图。权重图是图中的每条边都带有一个相关的权重的图。该权重值通常由一个w:E→R的权重函数给出。
    对于邻接矩阵表示来说,通常会将图G中的结点编为1,2,•••,|V|,这种编号可以是任意的,在进行此种编号之后,图G的邻接矩阵表示由一个|V|×|V|的矩阵A=(aij)予以表示,该矩阵满足下述条件:
    aij = 1 , 若(i,j)∈E
    aij = 0 , 其他
    与邻接链表表示法一样,邻接矩阵也可以用来表示权重图,直接将边(u,v)∈E的权重w(u,v)存放在邻接矩阵中的第u行第v列记录上。对于不存在的边,则在相应的行列记录上存放值NIL或者0、∞。

广度优先搜索

    广度优先搜索是最简单的图搜索算法之一,也是许多重要的图算法的原型。
    给定图G=(V,E)和一个可以识别的源结点s,广度优先搜索对图G中的边进行系统性的探索发现可以从源结点s到达的所有结点。该算法能够计算从源结点s到每个可到达的结点的距离(最少的边数),同时生成一棵“广度优先搜索树”。该树以源结点s为根结点,包含所有可以从s到达的结点。对于每个从源结点s可以到达的结点v,在广度优先搜索树里从结点s到结点v的简单路径所对应的就是图G中从结点s到结点v的“最短路径”,即包含最少变数的路径。该算法可以用于无向图,也可以用于有向图。
    该算法始终是将已有发现结点和未发现结点之间的边界,沿其广度方向向外扩展。也就是说,算法需要在发现所有距离源结点s为k的所有结点之后,才会发现距离源结点s为k+1的其他结点。
    为了跟踪算法的进展,广度优先搜索在概念上将每个结点涂上白色、灰色或黑色。所有结点在一开始的时候均涂上白色。在算法推进过程中,这些结点可能会变成灰色或者黑色。在搜索过程中,第一次遇到一个结点就称该结点被“发现”,此时该结点的颜色将发生改变。
    在执行广度优先搜索的过程中将构造出一颗广度优先树。一开始,该树仅含有根结点,就是源结点s。在扫描已发现结点u的邻接链表是,每当发现一个白色结点v,就将结点v和边(u,v)同时加入该棵树中。在广度优先树中,称结点u是结点v的前驱或者父结点。广度优先树中的祖先和后代的关系皆以相对于根结点s的位置来定义:如果结点u是从根结点s到结点v的简单路径上的一个结点,则结点u是结点v的祖先,结点v是结点u的后代。
    广度优先搜索过程FBS中,假定输入图G=(V,E)是以邻接链表所表示的。该算法为图中每个结点赋予了一些额外的属性:我们将每个结点u的颜色存放在属性u.color里,将u的前驱结点存放在属性u.π里。如果u没有前驱结点,则u.π=NIL。属性u.d记录的是广度优先搜索算法所计算出的源结点s到结点u之间的距离。

BFS(G, s)
for each vertex u ∈ G.V – {s}
  u.color = WHITE
  u.d = ∞
  u.π = NIL
s.color = GRAY
s.d = 0
s.π = NIL
Q = null
ENQUEUE(Q, s)
while Q ≠ null
  u = DEQUEUE(Q)
  for each v ∈ G.Adj[u]
    if v.color == WHITE
      v.color = GRAY
      v.d = u.d + 1
      v.π = u
      ENQUEUE(Q, v)
  u.color = BLACK

最短路径

    定义从源结点s到结点v的最短路径距离δ(s,v)为从结点s到结点v之间所有路径里面最少的边数。如果从结点s到结点v之间没有路径,则δ(s,v)=∞。称从结点s到结点v的长度δ(s,v)为路径s到v的最短路径。

广度优先树

    过程BFS在对图进行搜索的过程中将创建一棵广度优先树,定义图G的前驱子图为Gπ=(Vπ,Eπ),其中Vπ={v∈V:v.π≠NIL}∪{s},Eπ={(v.π,v):v∈Vπ-{s}}。
    如果Vπ由源结点s可以到达的结点组成,并且对于所有的v∈Vπ,子图Gπ包含一条从源结点到结点v的唯一简单路径,且该路径也是图G里面从源结点s到结点v之间的一条最短路径,则前驱子图Gπ是一棵广度优先树。广度优先树实际上就是一棵树,因为它是连通的,我们称Eπ中的边为树边。
    下面的伪代码将打印出从源结点s到结点v的一条最短路径上的所有结点。

PRINT_PATH(G, s, v)
if v == s
  print s
else if v.π == NIL
  printno path from” s “to” v “existselse
  PRINT_PATH(G, s, v.π)
  print v

深度优先搜索

    深度优先搜索总是对最近才发现的结点v的出发边进行搜索,直到该结点的所有出发边都被发现为止。一旦结点v的所有出发边都被发现,搜索则“回溯”到v的前驱结点,来搜索该前驱结点的出发边。该过程一直持续到从源结点可以达到的所有结点都被发现为止。如果还存在尚未发现的结点,则深度优先搜索将从这些未被发现的结点中任选出一个作为新的源结点,并重复同样的搜索过程。该算法会重复整个过程,知道图中的所有结点都被发现为止。
    与广度优先搜索不同的是,广度优先搜索的前驱子图形成一棵树,而深度优先搜索的前驱子图可能由多棵树组成,因为搜索可能从多个源结点重复进行。深度优先搜索的前驱子图的定义为:设图Gπ=(V,Eπ),其中Eπ={(v.π,v):v∈V且v.π≠NIL}。深度优先搜索的前驱子图形成一个有多棵深度优先树构成的深度优先森林。森林Eπ中的边仍然称为树边。
    像广度优先搜索算法一样,深度优先搜索算法在搜索过程中也是对结点进行涂色来指明结点的状态。
    除了创建一个深度优先搜索森林外,深度优先搜索算法还会在每个结点盖上一个时间戳。每个结点v有两个时间戳:第一个时间戳v.d记录结点v第一次被发现的时间,第二个时间戳v.f记录的是搜索完成对v的邻接链表扫描的时间。
    下面的伪代码给出的是基本的深度优先搜索算法。变量time是一个全局变量,用来计算时间戳。

DFS(G)
for each vertex u ∈ G.V
  u.color = WHITE
  u.π = 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 v ∈ G:Adj[u]
  if v.color == WHITE
    v.π = u
    DFS_VISIT(G, v)
u.color = BLACK
time = time + 1
u.f = time

深度优先搜索性质

    深度优先搜索提供的是关于图结构的价值很高的信息。深度优先搜索最基本的性质是,其生成的前驱子图Gπ形成一个由多棵树所构成的森林。深度优先搜索的另一个重要性质是,结点的发现时间和完成时间具有所谓的括号化结构。
    括号化定理:在对有向或无向图G=(V,E)进行的任意深度优先搜索中,对于任意两个节点u和v来说,下面三种情况只有一种成立:

  • 区间[u.d, u.f]和区间[v.d, v.f]完全分离,在深度优先森林中,结点u不是结点v的后代,结点v也不是结点u的后代。
  • 区间[u.d, u.f]完全包含在区间[v.d, v.f]内,在深度优先树中,结点u是结点v的后代。
  • 区间[v.d, v.f]完全包含在区间[u.d, u.f]内,在深度优先树中,结点v是结点u的后代。

    深度优先搜索的另一个性质是,可以通过搜索来对输入图G=(V,E)的边进行分类。每条边的类型可以提供关于图的重要信息。对于在图G上运行深度优先搜索算法生成的深度优先森林Gπ,定义4种边的类型:

  • 树边:为深度优森林Gπ中的边。如果结点v是因算法对边(u,v)的搜索而首先被发现,则(u,v)是一条树边。
  • 后向边:后向边(u,v)是将结点u连接到其在深度优先树中祖先结点v的边。
  • 前向边:是将结点u连接到期深度优先树中一个后代结点v的边(u,v)。
  • 横向边:指其他所有的边。这些边可以连接同一棵深度优先树中的结点,只要其中一个结点不是另外一个结点的祖先,也可以连接不同深度优先树的两个结点。

    当第一次搜索边(u,v)时,结点v的颜色能够告诉我们关于该条边的一些信息。

  • 结点v为白色时表明该条边(u,v)是一条树边。
  • 结点v为灰色时表明该条边(u,v)是一条后向边。
  • 结点v为黑色时表明该条边(u,v)是一条前向边或横向边。

拓扑排序

    对于一个有向无环图G=(V,E)来说,其拓扑排序是G中所有结点的一种线性次序,该次序满足如下条件:如果图G包含边(u,v),则结点u在拓扑排序中处于结点v的前面。可以将图的拓扑排序看作是将图的所有结点在一条水平线上排开,图的所有有向边都从左指向右。
    下面的简单算法可以对一个有向无环图进行拓扑排序。

TOPOLOGICAL_SORT(G)
call DFS(G) to compute finishing times v.f for each vertex v as each vertex is finished, insert it onto the front of a linked list
return the linked list of vertices
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值