数据结构-图(第八章)的整理笔记,若有错误,欢迎指正。
图的基本概念
- 图(graph)G由两个集合V(vertex)和E(edge)组成,记为G=(V,E),其中V是顶点的有限集合,记为V(G),E是连接V中两个不同顶点(顶点对)的边的有限集合,记为E(G)。
- 可以用字母或自然数来标识图中的顶点,这里约定用(0≤i≤n-1)表示第i个顶点的编号,其中n为图中顶点的个数。当E(G)为空集时,则图G只有顶点,没有边。
- !注意:线性表可以是空表,树可以是空树,但图不可以是空图。就是说图中不能一个顶点也没有,图的顶点集V一定是非空集,但边集E可以为空,此时图中只有顶点而没有边。
端点和邻接点
- 在一个无向图中,若存在一条边(i,j),则称顶点i和顶点j为该边的两个端点(endpoint),并称它们互为邻接点(adjacent),即顶点i是顶点j的一个邻接点,顶点j也是顶点i的一个邻接点,边(i,j)和顶点i、j关联。关联于相同两个端点的两条或者两条以上的边称为多重边,在数据结构中讨论的图都是指没有多重边的图。
- 在一个有向图中,若存在一条有向边<i,j>(也称为弧),则称此边是顶点i的一条出边,同时也是顶点j的一条入边,i为此边的起始端点(简称为起点),j为此边的终止端点(简称终点),顶点j是顶点i的出边邻接点,顶点i是顶点j的入边邻接点。
有向图
- 在图G中,如果表示边的顶点对(或序偶)是有序的,则称G为有向图(digraph)。在有向图中代表边的顶点对用尖括号括起来,用于表示一条有向边,如<i,j>表示从顶点i到顶点j的一条边,可见<i,j>和<j,i>是两条不同的边。
无向图
- 如果在图G中,若<i,j>∈E(G)必有<j,i>∈E(G),即E(G)是对称的,则用(i,j)代替这两个顶点对,表示顶点i与顶点j的一条无向边,则称G为无向图(undirgraph)。显然在无向图中(i,j)和(j,i)所代表的是同一条边,所以,无向图可以看成是有向图的特例。
简单图、多重图
- 如果一个图G满足:①不存在重复边;②不存在顶点到自身的边,那么称图G为简单图。
- 若图G中某两个结点之间的边数多于一条,又允许顶点通过同一条边和自己关联,则G为多重图。
- 数据结构课程只探讨 “简单图”。
完全图(也称简单完全图)
- 无向完全图(complete undirgraph)——无向图中任意两个顶点之间都存在边(若无向图的顶点数为n,则边数为 C n 2 = n ( n − 1 ) 2 ) C_n^2=\frac{n(n-1)}{2}) Cn2=2n(n−1))。
- 有向完全图(complete digraph)——有向图中任意两个顶点之间都存在方向相反的两条弧(若无向图的顶点数为n,则边数为
2
C
n
2
=
n
(
n
−
1
)
2C_n^2=n(n-1)
2Cn2=n(n−1))。
子图、生成子图
- 设有两个图G=(V, E)和G’=(V’, E’),若V’是V的子集,且E’是E的子集,则称G’是G的子图(subgraph)。
- 若有满足V(G’) = V(G)的子图G’,则称其为G的生成子图。
连通、连通图和连通分量
- 无向图中,若从顶点v到顶点w有路径存在,则称v和w是连通的。
- 若图G中任意两个顶点都是连通的,则称图G为连通图(connected graph),否则称为非连通图(unconnected graph)。
- !注意: 对于n个顶点的无向图G,若G是连通图,则最少有n-1条边;若G是非连通图,则最多可能有 C n − 1 2 C_{n-1}^2 Cn−12条边。
- 无向图中的极大连通子图(子图必须连通,且包含尽可能多的顶点和边)称为连通分量(connected component)。
!注意区分:极大连通子图和极小连通子图 - 极大连通子图是无向图的连通分量,极大即要求该连通子图包含其所有的边;极小连通子图是既要保持图的连通又要使得边数最少的子图。
强连通、强连通图、强连通分量
- 有向图中,若从顶点v到顶点w和从顶点w到顶点v之间都有路径,则称这两个顶点是强连通的。
- 若图中任何一对顶点都是强连通的,则称此图为强连通图(strongly connected graph)。
- !注意: 对于n个顶点的有向图G, 若G是强连通图,则最少有n条边(形成回路)。
- 有向图中的极大强连通子图(子图必须强连通,同时保留尽可能多的边)称为有向图的强连通分量(strongly connected component)。
生成树、生成森林
- 连通图(无向图) 的生成树是包含图中全部顶点的一个极小连通子图(边尽可能的少,但要保持连通)。
- 若图中顶点数为n,则它的生成树含有 n-1 条边。对生成树而言,若砍去它的一条边,则会变成非连通图,若加上一条边则会形成一个回路。
- 在非连通图(无向图)中,连通分量的生成树构成了非连通图的生成森林。
顶点的度、入度、出度
- 对于无向图:顶点v的度是指依附于该顶点的边的条数,记为TD(v)。
在具有n个顶点、e条边的无向图中, ∑ i = 1 n T D ( v i ) = 2 e \sum_{i=1}^nTD(v_i)=2e ∑i=1nTD(vi)=2e,即无向图的全部顶点的度的和等于边数的2倍。 - 对于有向图:
入度是以顶点v为终点的有向边的数目,记为ID(v);
出度是以顶点v为起点的有向边的数目,记为OD(v)。
顶点v的度等于其入度和出度之和,即TD(v) = ID(v) + OD(v)。
在具有n个顶点、e条边的有向图中, ∑ i = 1 n I D ( v i ) = ∑ i = 1 n O D ( v i ) = e \sum_{i=1}^nID(v_i)=\sum_{i=1}^nOD(v_i)=e ∑i=1nID(vi)=∑i=1nOD(vi)=e
边的权和网
- 边的权——在一个图中,每条边都可以标上具有某种含义的数值,该数值称为该边的权值。
- 带权图/网——边上带有权值的图称为带权图,也称网。
- 带权路径长度——当图是带权图时,一条路径上所有边的权值之和,称为该路径的带权路径长度
稠密图、稀疏图
- 当一个图接近完全图时,称为稠密图(dense graph)。
- 当一个图含有较少的边数时(如
e
<
l
o
g
2
n
e<log_2^n
e<log2n),称为稀疏图(sparse graph)。
路径、路径长度和回路
-
路径(path)——顶点 v p v_p vp到顶点 v q v_q vq之间的一条路径是指顶点序列 , v p , v i 1 , v i 2 , . . . , v i m , v q v_p,v_{i_1},v_{i_2},...,v_{i_m},v_q vp,vi1,vi2,...,vim,vq。
-
路径长度(path length)——路径上边的数目
-
回路(cycle)——第一个顶点和最后一个顶点相同的路径称为回路或环。
-
!注意:顶点之间有可能不存在路径。
-
!注意:有向图的路径也是有向的。
简单路径、简单回路
- 简单路径——在路径序列中,顶点不重复出现的路径称为简单路径。
- 简单回路(simple cycle)——除第一个顶点和最后一个顶点外,其余顶点不重复出现的回路称为简单回路。
距离
- 点到点的距离——从顶点u出发到顶点v的最短路径若存在,则此路径的长度称为从u到v的距离。
- 若从u到v根本不存在路径,则记该距离为无穷(∞)。
树、有向树
- 树——不存在回路,且连通的无向图。
- 有向树——一个顶点的入度为0、其余顶点的入度均为1的有向图,称为有向树。
- !注意:n个顶点的树,必有n-1条边。n个顶点的图,若|E|>n-1,则一定有回路。
图的存储
- 图的存储结构除了要存储图中各个顶点本身的信息以外,同时还要存储顶点与顶点之间的所有关系(边的信息)。
邻接矩阵法(adjacency matrix)
邻接表法(adjacency list)
十字链表法(orthogonal list)
- 十字链表是有向图的一种链式存储结构。在十字链表中,对应于有向图中的每条弧有一个点,对应于每个顶点也有一个结点。
- 弧结点中有5个域:尾域(tailvex)和头域(headvex)分别指示尾和头这两个顶点在图中的位置:链域hlink指向弧头相同的下一条弧;链域tlink指向弧尾相同的下一条弧;info域指向该弧的相关信息。这样,弧头相同的弧就在同一个链表上,弧尾相同的弧也在同一个链表上。
- 顶点结点中有3个域:data域存放顶点相关的数据信息,如顶点名称;firstin和firstout两个域分別指向以该顶点为弧头或弧尾的第一个弧结点。
- !注意:顶点结点之间是顺序存储的。
- 在十字链表中,既容易找到为尾的弧,又容易找到为头的弧,因而容易求得顶点的出度和入度。图的十字链表表示是不唯一的,但一个十字链表表示确定一个图。
邻接多重表(adjacency multi-list)
- 邻接多重表是无向图的另一种链式存储结构。
- 在邻接表中,容易求得顶点和边的各种信息,但在邻接表中求两个顶点之间是否存在边和对边执行删除等操作时,需要分别在两个顶点的边表中遍历,效率较低。
- 其中,i和j为该边依附的两个顶点在图中的位置;iLink指向下一条依附于顶点i的边;jLink指向下一条依附于顶点j的边;info为指向和边相关的各种信息的指针域。
每个顶点也用一个结点表示,其中,data域存储该顶点的相关信息,firstedge域指示第一条依附于该顶点的边。 - 在邻接多重表中,所有依附于同一顶点的边串联在同一链表中,由于每条边依附于两个顶点,因此每个边结点同时链接在两个链表中。对无向图而言,其邻接多重表和邻接表的差别仅在于:同一条边在邻接表中用两个结点表示,而在邻接多重表中只有一个结点。
删除一个结点
四种存储方式的对比
邻接表 | 邻接矩阵 | 十字链表 | 邻接多重表 | |
---|---|---|---|---|
空间复杂度 | 无向图:O(|V|+2|E|);有向图:O(|V|+|E|) | O(|V| 2 ^2 2) | O(|V|+|E|) | O(|V|+|E|) |
适合用于 | 存储稀疏图 | 存储稠密图 | 只能存有向图 | 只能存无向图 |
表示方式 | 不唯一 | 唯一 | 不唯一 | 不唯一 |
计算度/出度/入度 | 计算有向图的度、入度不方便、其余很方便 | 必须遍历对应行或列 | 很方便 | 很方便 |
找相邻的边 | 找有向图的入边不方便,其余很方便 | 必须遍历对应行或列 | 很方便 | 很方便 |
图的基本操作
图 的 基 本 操 作 { A d j a c e n t ( G , x , y ) : 判 断 图 G 是 否 存 在 边 < x , y > 或 ( x , y ) 。 N e i g h b o r s ( G , x ) : 列 出 图 G 中 与 结 点 x 邻 接 的 边 。 I n s e r t V e r t e x ( G , x ) : 在 图 G 中 插 入 顶 点 x 。 D e l e t e V e r t e x ( G , x ) : 从 图 G 中 删 除 顶 点 x 。 A d d E d g e ( G , x , y ) : 若 无 向 边 ( x , y ) 或 有 向 边 < x , y > 不 存 在 , 则 向 图 G 中 添 加 该 边 。 R e m o v e E d g e ( G , x , y ) : 若 无 向 边 ( x , y ) 或 有 向 边 < x , y > 存 在 , 则 从 图 G 中 删 除 该 边 。 F i r s t N e i g h b o r ( G , x ) : 求 图 G 中 顶 点 x 的 第 一 个 邻 接 点 , 若 有 则 返 回 顶 点 号 。 若 x 没 有 邻 接 点 或 图 中 不 存 在 x , 则 返 回 − 1 。 N e x t N e i g h b o r ( G , x , y ) : 假 设 图 G 中 顶 点 y 是 顶 点 x 的 一 个 邻 接 点 , 返 回 除 y 外 顶 点 x 的 下 一 个 邻 接 点 的 顶 点 号 , 若 y 是 x 的 最 后 一 个 邻 接 点 , 则 返 回 − 1 。 G e t _ e d g e _ v a l u e ( G , x , y ) : 获 取 图 G 中 边 ( x , y ) 或 < x , y > 对 应 的 权 值 。 S e t _ e d g e _ v a l u e ( G , x , y , v ) : 设 置 图 G 中 边 ( x , y ) 或 < x , y > 对 应 的 权 值 为 v 。 图的基本操作\begin{cases} Adjacent(G,x,y):判断图G是否存在边<x,y>或(x,y)。 \\ Neighbors(G,x):列出图G中与结点x邻接的边。 \\ InsertVertex(G,x):在图G中插入顶点x。 \\ DeleteVertex(G,x):从图G中删除顶点x。 \\ AddEdge(G,x,y):若无向边(x,y)或有向边<x,y>不存在,则向图G中添加该边。 \\ RemoveEdge(G,x,y):若无向边(x,y)或有向边<x,y>存在,则从图G中删除该边。 \\ FirstNeighbor(G,x):求图G中顶点x的第一个邻接点,若有则返回顶点号。若x没有邻接点或图中不存在x,则返回-1。 \\ NextNeighbor(G,x,y):假设图G中顶点y是顶点x的一个邻接点,返回除y外顶点x的下一个邻接点的顶点号,若y是x的最后一个邻接点,则返回-1。 \\ Get\_edge\_ value(G,x,y):获取图G中边(x,y)或<x,y>对应的权值。 \\ Set\_edge\_value(G,x,y,v):设置图G中边(x,y)或<x,y>对应的权值为v。 \\ \end{cases} 图的基本操作⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧Adjacent(G,x,y):判断图G是否存在边<x,y>或(x,y)。Neighbors(G,x):列出图G中与结点x邻接的边。InsertVertex(G,x):在图G中插入顶点x。DeleteVertex(G,x):从图G中删除顶点x。AddEdge(G,x,y):若无向边(x,y)或有向边<x,y>不存在,则向图G中添加该边。RemoveEdge(G,x,y):若无向边(x,y)或有向边<x,y>存在,则从图G中删除该边。FirstNeighbor(G,x):求图G中顶点x的第一个邻接点,若有则返回顶点号。若x没有邻接点或图中不存在x,则返回−1。NextNeighbor(G,x,y):假设图G中顶点y是顶点x的一个邻接点,返回除y外顶点x的下一个邻接点的顶点号,若y是x的最后一个邻接点,则返回−1。Get_edge_value(G,x,y):获取图G中边(x,y)或<x,y>对应的权值。Set_edge_value(G,x,y,v):设置图G中边(x,y)或<x,y>对应的权值为v。
时间复杂度的分析
邻接矩阵 | 邻接表 | |
---|---|---|
Adjacent(G,x,y) | O(1) | O(1)~O(|V|) |
Neighbors(G,x) | O(|V|) | O(1)~O(|V|) |
InsertVertex(G,x) | O(1) | O(1) |
DeleteVertex(G,x) | O(|V|) | O(1)~O(|E|) |
Addedge(G,x,y) | O(1) | O(1)(头插法)~O(|V|(尾插法)) |
RemoveEdge(G,x,y) | O(1) | O(1)~O(|V|) |
FirstNeighbor(G,x) | O(1)~O(|V|) | O(1) |
NextNeighbor(G,x,y) | O(1)~O(|V|) | O(1) |
Get_edge_ value(G,x,y) | O(1) | O(1)~O(|V|) |
Set_edge_value(G,x,y,v) | O(1) | O(1)~O(|V|) |
其中,|V|表示图G中顶点的个数,|E|表示图G中边的条数。
图的遍历
- 从给定图中任意指定的顶点(称为初始点)出发,按照某种搜索方法沿着图的边访问图中的所有顶点,使每个顶点仅被访问一次,这个过程称为图的遍历。
- 如果给定图是连通的无向图或者是强连通的有向图,则遍历过程一次就能完成,并可按访问的先后顺序得到由该图的所有顶点组成的一个序列。
- 图的遍历比树的遍历更复杂,因为从树根到达树中的任意结点只有一条路径,而从图的初始点到达图中的每个顶点可能存在着多条路径。当沿着图中的一条路径访问过某一顶点之后,可能还沿着另一条路径回到该顶点,即存在回路。为了避免同一个顶点被重复访问,必须记住每个被访问过的顶点。为此,可设置一个访问标记数组visited[ ],当顶点被访问过时,数组中的元素visited[ ]置为true,否则置为false。
- 根据搜索方法的不同,图的遍历方法有两种:一种叫深度优先遍历(Depth First Search,DFS),另ー种叫广度优先遍历(Breadth First Search,BFS)。
广度优先搜索
广度优先生成树
- 在广度遍历的过程中,我们可以得到一棵遍历树,称为广度优先生成树。需要注意的是,一给定图的邻接矩阵存储表示是唯一的,故其广度优先生成树也是唯一的,但由于邻接表存储表示不唯一,故其广度优先生成树也是不唯一的。
广度优先生成森林
- 对非连通图的广度优先遍历,可得到广度优先生成森林。
深度优先搜索
深度优先生成树
- 与广度优先搜索一样,深度优先搜索也会产生一棵深度优先生成树。当然,这是有条件的,即对连通图调用DFS才能产生深度优先生成树,否则产生的将是深度优先生成森林。与BFS类似,基于邻接表存储的深度优先生成树是不唯一的。
深度优先生成森林
图的遍历与图的连通性
- 图的遍历算法可以用来判断图的连通性。
- 对于无向图来说,若无向图是连通的,则从任一结点出发,仅需一次遍历就能够访问图中的所有顶点;若无向图是非连通的,则从某一个顶点出发,一次遍历只能访问到该顶点所在连通分量的所有顶点,而对于图中其他连通分量的顶点,则无法通过这次遍历访问。
- 对于有向图来说,若从初始点到图中的每个顶点都有路径,则能够访问到图中的所有顶点,否则不能访问到所有顶点。
- 故在BFSTraverse()或DFSTraverse()中添加了第二个for循环,再选取初始点,继续进行遍历,以防止一次无法遍历图的所有顶点。
- 对于无向图,上述两个函数调用BFS(G,i)或DFS(G,i)的次数等于该图的连通分量数;而对于有向图则不是这样;因为一个连通的有向图分为强连通的和非强连通的,它的连通子图也分为强连通分量和非强连通分量,非强连通分量一次调用BFS(G,i)或DFS(G,i)无法访问到该连通分量的所有顶点。