图
什么是图
- 图的定义
“图”G可以表示为顶点和边的两个集合: G = ( V , E ) G=(V,E) G=(V,E)。每条边是一个顶点对 ( v , w ) ∈ E (v,w) \in E (v,w)∈E,并且 v , w ∈ V v,w \in V v,w∈V。
- 通常:
用 ∣ V ∣ |V| ∣V∣表示顶点的数量 ( ∣ V ∣ ≥ 1 ) (|V| \geq 1) (∣V∣≥1)
用 ∣ E ∣ |E| ∣E∣表示边的数量 ( ∣ E ∣ ≥ 0 ) (|E| \geq 0) (∣E∣≥0)
关于图的术语
- 无向图(Undirected Graphs):边
(
v
,
w
)
(v,w)
(v,w)等同于边
(
w
,
v
)
(w,v)
(w,v)。用圆括号“
(
)
()
()”表示无向边。
- 有向图(Directed Graphs):边
<
v
,
w
>
<v,w>
<v,w>不同于边
<
w
,
v
>
<w,v>
<w,v>。用尖括号“
<
>
<>
<>”表示有向边。有向边也称“弧(Arc)”。
- 邻接点:如果 ( v , w ) (v,w) (v,w)或 < v , w > <v,w> <v,w>是图中任意一条边,那么称 v v v和 w w w互为“邻接点(Adjacent Vertices)”。
- 路径:图 G G G中从 v p v_p vp到 v q v_q vq的路径为 { v p , v i 1 , v i 2 , ⋯ , v i n , v q } \{ v_p,v_{i1},v_{i2}, \cdots ,v_{in},v_q \} {vp,vi1,vi2,⋯,vin,vq},使得 ( v p , v i 1 ) , ( v i 1 , v i 2 ) , ⋯ , ( v i n , v q ) (v_p,v_{i1}),(v_{i1},v_{i2}), \cdots ,(v_{in},v_q) (vp,vi1),(vi1,vi2),⋯,(vin,vq)或 < v p , v i 1 > , < v i 1 , v i 2 > , ⋯ , < v i n , v q > <v_p,v_{i1}>,<v_{i1},v_{i2}>, \cdots ,<v_{in},v_q> <vp,vi1>,<vi1,vi2>,⋯,<vin,vq>都属于 E ( G ) E(G) E(G)。
- 路径长度:路径中边的数量。
- 简单路径: v p , v q , v i 1 , v i 2 , ⋯ , v i n v_p,v_q,v_{i1},v_{i2}, \cdots ,v_{in} vp,vq,vi1,vi2,⋯,vin都是不同顶点的路径。
- 回路:起点和终点相同 ( v p = v q ) (v_p=v_q) (vp=vq)的路径。
- 无环图:不存在任何回路的图。
- 有向无环图:不存在回路的有向图,也称DAG(Directed Acyclic Graph)。
- 简单图(Simple Graphs):没有重边和自回路的图。
- 无向完全图:在顶点数在给定为
n
n
n的情况下,边数达到最大的
n
(
n
−
1
)
/
2
n(n-1)/2
n(n−1)/2条边的无向简单图。
- 有向完全图:在顶点数在给定为
n
n
n的情况下,边数达到最大的
n
(
n
−
1
)
n(n-1)
n(n−1)条边的有向简单图。
- 顶点的度(Degree):与顶点 v v v相关的边数。
- 顶点的入度(In-Degree)和出度(Out-Degree):在有向图中,顶点
v
v
v的入度为以顶点
v
v
v为终点的路径的条数;顶点
v
v
v的出度为以顶点
v
v
v为起点的路径的条数。
- 稠密图和稀疏图:是否满足 ∣ E ∣ > ∣ V ∣ l o g 2 ∣ V ∣ |E|>|V|log_2{|V|} ∣E∣>∣V∣log2∣V∣,作为稠密图和稀疏图的分界条件。
- 图 G G G的子图 G ′ G^{'} G′:满足 V ( G ′ ) ⊆ V ( G ) V(G^{'}) \subseteq V(G) V(G′)⊆V(G)并且 E ( G ′ ) ⊆ E ( G ) E(G^{'}) \subseteq E(G) E(G′)⊆E(G)的图 G ′ G^{'} G′称为图G的子图。
- 无向图的连通性
- 连通(Connected):如果无向图从一个顶点 v i v_i vi到另一个顶点 v j ( i ≠ j ) v_j(i≠j) vj(i=j)有路径,则称顶点 v i v_i vi和 v j v_j vj是连通的。
- 连通图(Connect Graphs):无向图中任意两顶点都是连通的,则称该图是连通图。
- 连通分量(Connected Component):无向图的极大连通子图(子图、连通、极大顶点数、极大边数)。
- 有向图的连通性:
- 强连通图(Strongly Connected Graph):有向图中任意一对顶点 v i v_i vi和 v j ( i ≠ j ) v_j(i≠j) vj(i=j)均既有从 v i v_i vi到 v j v_j vj的路径,也有从 v j v_j vj到 v i v_i vi的路径,则称该有向图是强连通图。
- 强连通分量(Strongly Connected Component):有向图的极大强连通子图称为强连通分量(子图、连通、极大顶点数、极大边数)。
- 树(Tree):树是图的特例(无环的无向图)。
- 生成树(Spanning Tree):所谓连通图G的生成树,是G的包含其全部n个顶点的一个极小连通子图。
图的表示
- 邻接矩阵(Adjacency Matrix)
-
定义
图的逻辑结构分为两部分:V和E集合,其中,V是顶点,E是边。因此,用一个一维数组存放图中所有顶点数据;用一个二维数组存放顶点间关系(边或弧)的数据,这个二维数组称为邻接矩阵。
邻接矩阵表示:
A [ i ] [ j ] = { 1 ( v i , v j ) 或 < v i , v j > 为 图 G 的 边 0 或 ∞ ( v i , v j ) 或 < v i , v j > 不 为 图 G 的 边 A[i][j]= \begin{cases} 1 & (v_i,v_j)或<v_i,v_j>为图G的边\\ 0或\infty & (v_i,v_j)或<v_i,v_j>不为图G的边 \end{cases} A[i][j]={10或∞(vi,vj)或<vi,vj>为图G的边(vi,vj)或<vi,vj>不为图G的边 -
例:
邻接矩阵表示为:
A [ i ] [ j ] = [ 0 0 1 1 0 1 0 1 0 0 0 0 0 0 1 0 1 0 0 1 0 0 0 1 0 ] A[i][j]=\left[ \begin{matrix} 0 & 0 & 1 & 1 & 0\\ 1 & 0 & 1 & 0 & 0\\ 0 & 0 & 0 & 0 & 1\\ 0 & 1 & 0 & 0 & 1\\ 0 & 0 & 0 & 1 & 0 \end{matrix} \right] A[i][j]=⎣⎢⎢⎢⎢⎡0100000010110001000100110⎦⎥⎥⎥⎥⎤ -
适用情况(就空间利用率而言):稠密图
-
- 邻接表(Adjacency List)
-
定义
对于图G中的每个顶点 v i v_i vi,将所有邻接于 v i v_i vi的顶点 v j v_j vj链成一个单链表,这个单链表就称为顶点 v i v_i vi的邻接表,再将所有点的邻接表表头放到一个数组中,就构成了图的邻接表。
-
例:
邻接表表示为:
-
适用情况(就空间利用率而言):稀疏图
-
抽象数据类型描述
- 类型名称:图(Graph)
- 数据对象集:一非空的顶点集合Vertex和一个边集合Edge,每条边用对应的一对顶点表示。
- 操作集:对于任意的图
G
∈
G
r
a
p
h
G \in Graph
G∈Graph,顶点
v
,
v
1
v,v_1
v,v1和
v
2
∈
V
e
r
t
e
x
v_2 \in Vertex
v2∈Vertex,以及任意访问顶点的函数
v
i
s
i
t
(
)
visit()
visit(),具体操作有:
- Graph Create( int VertexNum ):构造并返回一个空图;
- void Destroy(Graph G G G):释放图 G G G占用的存储空间;
- void InsertVertex( Graph G G G):返回一个在 G G G中增加了新顶点 v v v的图;
- void InsertEdge( Graph G G G,Edge E E E ):返回一个在 G G G中增加了新边 ( v 1 , v 2 ) (v_1,v_2) (v1,v2)的图;
代码实现
图的遍历
图遍历方法的分类主要在于访问图中节点的顺序,深度优先搜索以深度为优先顺序,而广度优先搜索以广度为优先顺序。
-
深度优先搜索(Depth First Search,简称DFS )
-
主要步骤(类似于树的先序遍历):
- 从任意一个节点 v 0 v_0 v0出发,访问它的邻接节点 v 1 v_1 v1,再从 v 1 v_1 v1出发,访问 v 1 v_1 v1的未被访问过的邻接节点……
- 当当前访问的节点 v n v_n vn没有邻接节点或者没有未被访问过的邻接节点时,退回 v n v_n vn的上一个节点 v n − 1 v_{n-1} vn−1,搜索 v n − 1 v_{n-1} vn−1的邻接节点,重复 v n v_n vn的操作。
- 当连通图中没有未被访问过的节点时,遍历结束。
-
例:走迷宫(遍历所有图中路径)
步骤:
节点访问顺序: 0 − 2 − 6 − 4 − 3 − 5 − 7 − 1 0-2-6-4-3-5-7-1 0−2−6−4−3−5−7−1 -
代码实现
void DFS( Graph G, int V ) { /* 从第V个顶点出发递归地深度优先遍历图G */ VertexType W; Visited[V] = TRUE; VisitFunc(V); /* 访问第V个顶点 */ for( W = FirstAdjV(G, V); W != NULL; W = NextAdjV (G, V, W) ) { if( Visited[W]==FALSE ) DFS(G, W); /* 对V的尚未访问的邻接顶点W递归调用DFS */ } }
-
-
广度优先搜索(Breadth First Search,简称BFS )
-
主要步骤(类似于树的层序遍历):
- 从任意一个节点 v 0 v_0 v0出发,依次访问它的邻接节点 v 1 , v 2 , ⋯ , v n v_1,v_2, \cdots ,v_n v1,v2,⋯,vn;再依次访问 v 1 , v 2 , ⋯ , v n v_1,v_2, \cdots ,v_n v1,v2,⋯,vn的所有未被访问的邻接节点;
- 重复步骤1,直到连通图中所有节点都被访问为止。
-
例:在下图中,从节点E出发BFS全图
步骤:
( 1 ) (1) (1)
( 2 ) (2) (2)
( 3 ) (3) (3)
( 4 ) (4) (4)
节点访问顺序: E − A − F − H − B − D − G − C E-A-F-H-B-D-G-C E−A−F−H−B−D−G−C -
代码实现:
void BFS(Graph G) { /* 按广度优先遍历图G。使用辅助队列Q和访问标志数组Visited */ Queue *Q=NULL; VertexType U,V,W; for(U = 0; U < G.NV; U++) { Visited[U]=FALSE; } Q = CreatQueue( MaxSize ); /* 创建空队列Q */ for(U = 0; U < G.NV; U++) { /* 从U开始进行BFS */ /* 若U尚未访问 */ if(Visited[U]==FALSE) { Visited[U] = TRUE; VisitFunc(U); /* 访问U */ AddQ (Q, U); /* U入队列 */ while(!IsEmptyQ(Q)) { V = DeleteQ( Q ); /* 队头元素出队并置为V */ for( W = FirstAdjV(G, V); W; W = NextAdjV(G, V, W) ) /*出队列前,把它的未访问过的邻接点入队列*/ { if(Visited[W]==FALSE) { Visited[W] = TRUE; VisitFunc (W); /* 访问W */ AddQ (Q, W); } } } } } }
-