图
1、定义
\quad \quad
图是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:
G
=
(
V
,
E
)
G=(V,E)
G=(V,E)
其中:G表示一个图,V是图G中顶点的集合,E是图G中顶点之间边的集合。
- 在线性表中,元素个数可以为零,称为空表;
- 在树中,结点个数可以为零,称为空树;
- 在图中,顶点个数不能为零,但可以没有边。
2.基本术语
顶点(Vertex):图中的数据元素。线性表中我们把数据元素叫元素,树中将数据元素叫结点。
边(Edge):顶点之间的逻辑关系用边来表示,边集可以是空的。
有向边(Directed Edge):若从顶点 v i v_i vi到 v j v_j vj有方向,则称这条边为有向边,表示为 < v i , v j > <v_i,v_j> <vi,vj>,也称弧(Arc)。起点是弧尾,终点是弧头。
无向边(Undirected Edge):若顶点 v i v_i vi和 v j v_j vj之间的边没有方向,则称这条边为无向边,表示为 ( v i , v j ) (v_i,v_j) (vi,vj)。
有向图(Directed graphs):如果图的任意两个顶点之间的边都是有向边,则称该图为无向图。
无向图(Undirected graphs):图中任意两个顶点之间的边都是无向边。
简单图:在图中,若不存在顶点到其自身的的边,且同一条边不同时出现。
完全图:任意两个点都有一条边相连。
-
无向完全图:在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图。
- 含有n个顶点的无向完全图有 n ∗ ( n − 1 ) / 2 n*(n-1)/2 n∗(n−1)/2条边。
-
有向完全图:在有向图中,如果任意两个顶点之间都存在方向相反的两条弧,则称该图为有向完全图。
- 含有n个顶点的有向完全图有
n
∗
(
n
−
1
)
n*(n-1)
n∗(n−1)条边。
- 含有n个顶点的有向完全图有
n
∗
(
n
−
1
)
n*(n-1)
n∗(n−1)条边。
稀疏图:有很少边或弧的图(e<nlogn),其中n是顶点的数目。
稠密图:有较多边或弧的图。
网:边/弧带权的图。
权:图中边或弧所有的相关数称为权。
邻接、依附::
-
无向图中,对于任意两个顶点 v i v_i vi和 v j v_j vj,若存在边 ( v i , v j ) (v_i,v_j) (vi,vj),则称顶点 v i v_i vi和 v j v_j vj互为邻接点,同时称边 ( v i , v j ) (v_i,v_j) (vi,vj)依附于顶点 v i v_i vi和 v j v_j vj。
-
有向图中,对于任意两个顶点 v i v_i vi和 v j v_j vj,若存在弧 < v i , v j > <v_i,v_j> <vi,vj>,则称顶点 v i v_i vi邻接到顶点 v j v_j vj,顶点 v j v_j vj邻接自顶点 v i v_i vi,同时称弧 < v i , v j > <v_i,v_j> <vi,vj>依附于顶点 v i v_i vi和 v j v_j vj。
顶点的度:顶点V的度是指依附于该顶点的边数,通常记为 T D ( V ) TD(V) TD(V)
-
在具有n个顶点,e条边的无向图G中,各顶点的度之和与边数之和的关系:
∑ i = 1 n T D ( V i ) = 2 e \sum_{i=1}^nTD(V_i)=2e i=1∑nTD(Vi)=2e -
在有向图中,顶点的度等于该顶点的入度与出度之和。
-
顶点的入度:在有向图中,顶点V的入度是指以该顶点为弧头(终点)的弧的数目,记为 I D ( V ) ID(V) ID(V);
-
顶点的出度:在有向图中,顶点V的出度是指以该顶点为弧尾(始点)的弧的数目,记为 O D ( V ) OD(V) OD(V)。
-
在具有n个顶点,e条边的有向图G中,各顶点的入度之和与各顶点的出度之和的关系以及与边数之和的关系:
∑ 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=1∑nID(Vi)=i=1∑nOD(Vi)=e
-
例:
路径:连续的边构成的顶点序列
路径长度:路径上边或弧的数目/权值之和。
回路(环):第一个顶点和最后一个顶点相同的路径。
简单路径:序列中顶点不重复出现的路径。
简单回路(简单环):除路径起点和终点相同外,其余顶点均不相同的路径。
连通图:在无向图G=(V,{E})中,若对任何两个顶点v,u都存在从v到u的路径,则称G是连通图。
强连通图:在有向图G=(V,{E})中,若对任何两个顶点v,u都存在从v到u的路径,则称G是强连通图。
连通分量:无向图G的极大连通子图称为G的连通分量。
(极大连通子图:该子图是G联通子图,将G的任何不在该子图中的顶点加入,子图不再联通)。
强连通分量:有向图G的极大连通子图称为G的强连通分量。
极小连通子图:该子图是G的连通子图,在该子图中删除任何一条边,子图不再联通。
子图:假设G=(V,{E})和G‘=(V’,{E’}),如果V’包含于V且E’包含于E,则称G’为G的子图。
生成树:包含无向图G所有顶点的极小连通子图。有n个顶点的连通图的生成树有n个顶点和n- 1条边。
有向树:有向图中一顶点入度为0其余顶点入度为1。
森林:一个有向图由若干棵有向树构成生成森林。
图的操作:建立,遍历,查找。
3.存储结构及实现
图的逻辑结构:多对多(多个前驱,多个后继)
顺序存储结构:图没有顺序存储结构,但可以借助二维数组来表示元素之间的关系。即数组表示法(邻接矩阵)。
链式存储结构:多重链表有邻接表,邻接多重表以及十字链表
3.1邻接矩阵(数组)表示法
基本思想:用两个数组,一个一维数组存储图中顶点的信息,一个二维数组(称为邻接矩阵)存储图中各顶点之间的邻接关系。
假设图 G = ( V , E ) G=(V,E) G=(V,E)有n个顶点,则邻接矩阵为n*n二维数组,邻接矩阵的元素值为1或0(1表示两个顶点之间有边,0则表示没有)。
无向图的邻接矩阵表示法:
顶点数组
v
e
r
t
e
x
=
[
v
1
,
v
2
,
v
3
,
v
4
,
v
5
]
vertex=[v_1,v_2,v_3,v_4,v_5]
vertex=[v1,v2,v3,v4,v5]
1.无向图的邻接矩阵是对称的,主对角线为0.
2.顶点i的度=第i行(列)中1的个数。
3.完全图的邻接矩阵中,主对角线元素为0,其余1。
4.如何判断顶点i和顶点j之间是否存在边?
- 若邻接矩阵中相应位置的元素为1,则顶点i和顶点j之间存在边。
5.如何求顶点i的所有邻接点?
- 将数组中第i行元素扫描一遍,若arcs[i][j]为1,则顶点j为顶点i的邻接点。
有向图的邻接矩阵表示法:
顶点数组
v
e
r
t
e
x
=
[
v
1
,
v
2
,
v
3
,
v
4
]
vertex=[v_1,v_2,v_3,v_4]
vertex=[v1,v2,v3,v4]
1.在有向图的邻接矩阵中:
第i行含义:以结点
v
i
v_i
vi为尾的弧(即出度边)。
第i列含义:以结点
v
i
v_i
vi为头的弧(即入度边)。
2.有向图的邻接矩阵可能是不对称的。
3.顶点i的出度=第i行元素之和即第i行元素中1出现的总数。
4.顶点i的入度=第i列元素之和
5.顶点i的度=第i行元素之和+第i列元素之和
网(既有权图)的邻接矩阵表示法:
定义:若两个顶点之间无边或弧,则相应元素用无穷大表示;其余元素值为边或弧上的权。
3.2邻接表表示法(链式)
使用数组表示顶点的集合,使用链表表示边的关系。
顶点:按编号顺序将顶点数据存储在一维数组中
边:关联同一顶点的边(以顶点为为尾的弧)用线性链表存储
单链表:有几个顶点就有几个单链表
头结点:顶点以及链域
data | firstarc |
---|
表结点:与顶点之间有边的顶点在一维数组的索引以及指针域。
adjvex | nextarc |
---|
如果有权重,可以在表结点再加一个域,存放权值。
无向图的邻接表表示法:
分析:
v
1
v_1
v1,
v
2
v_2
v2,
v
3
v_3
v3,
v
4
v_4
v4,
v
5
v_5
v5,五个顶点放在一维数组。
v
1
v_1
v1与顶点之间有边的顶点有
v
2
v_2
v2,
v
4
v_4
v4且
v
2
v_2
v2,
v
4
v_4
v4的索引分别为1,3,故后接两结点值域为3,1;另外,这两个结点的位置可以互换。
特点:
1.邻接表不唯一:邻结点的位置可以前后互换。
2.若无向图中有n个顶点、e条边,则其邻接表需要n个头结点和2e个表结点。适宜存储稀疏图。
3.空间复杂度:若无向图中有n个顶点、e条边,其空间复杂度:O(n+2e)
4.无向图中顶点
v
i
v_i
vi的度为第i个单链表中的结点数
有向图的邻接表表示法:
顶点以出度为边的对应顶点为邻结点建立的邻接表称为邻接表。
v
1
v_1
v1指向
v
2
v_2
v2,
v
3
v_3
v3且
v
2
v_2
v2,
v
3
v_3
v3的索引分别为1,2,故后接两结点值域为2,1;另外,这两个结点的位置可以互换。
特点:
1.邻接表不唯一
2.若有向图中有n个顶点、e条边,则其邻接表需要n个头结点和e个表结点。
3.顶点
v
i
v_i
vi的出度为第i个单链表中的结点个数。
4.顶点
v
i
v_i
vi的入度为整个单链表中邻接点阈值是i-1的结点个数。
5.找出度容易,找入度难。
6.空间复杂度:若有向图中有n个顶点、e条边,其空间复杂度:O(n+e)
顶点以入度为边的对应顶点为邻结点建立的邻接表称为逆邻接表。
图G中
v
4
v_4
v4指向
v
1
v_1
v1且
v
4
v_4
v4的索引为3,故后接结点值域为2,指针域为空。
特点:
1.邻接表不唯一
2.若有向图中有n个顶点、e条边,则其邻接表需要n个头结点和e个表结点。
3.顶点
v
i
v_i
vi的入度为第i个单链表中的结点个数。
4.顶点
v
i
v_i
vi的出度为整个单链表中邻接点阈值是i-1的结点个数。
5.找入度容易,找出度难。
6.空间复杂度:若有向图中有n个顶点、e条边,其空间复杂度:O(n+e)
所以建立有向图的邻接表,应根据所需结果相应的选择哪种邻接表。
网(既有权图)的邻接表表示法:
跟无向图的邻接表差不多,只不过表结点多一个权重域。
邻接表与邻接矩阵存储:
邻接表特点:
1.方便找任一顶点的所有”邻接点“
2.节约稀疏图的空间:
无向稀疏图需要N个头指针+2E个结点(每个结点至少两个域)
有向稀疏图需要N个头指针+E个结点(每个结点至少两个域)
3.对无向图方便计算顶点的度;对有向图:只能计算出度;需要构造"逆邻接表(存指向自己的边)"来方便计算入度。
4.不方便检查任意一对顶点间是否存在边。
3.3邻接矩阵与邻接表表示法的关系
1.联系:邻接表中每个链表对应于邻接矩阵中的一行,链表中结点个数等于一行中非零元素的个数。
2.区别:
1)对于任一确定的无向图,邻接矩阵是唯一的(行列号与定点编号一致),但邻接表不唯一(链接次序与顶点编号无关)。
2)邻接矩阵的空间复杂度为O(
n
2
n^2
n2),而邻接表的空间复杂度为O(n+e),其中n是顶点个数,e是边数。
3.用途:邻接矩阵多用于稠密图,邻接表多用于稀疏图。
3.4十字链表-用于有向图
十字链表是有向图的另一种链式存储结构。可以看成是将有向图的邻接表和逆邻接表结合起来的一种链表。
在十字链表中,每一条弧对应十字链表中的一个弧结点,同时有向图中的每一个顶点在十字链表中对应有一个结点,记作顶点结点。
起点是弧尾,终点是弧头。
firstin指向入度边,firstout指向出度边
在十字链表中,既容易找到以
v
i
v_i
vi为尾的弧,也容易找到以
v
i
v_i
vi为头的弧,因而容易求得顶点的出度和入度。
3.5邻接多重表-用于无向图
邻接多重表是无向图的另一种链式存储结构。
在邻接多重表中,每一条边用一个结点表示,它由如下所示的6个域组成。
4.遍历
定义:从已给的连通图中某一顶点出发,沿着一些边访遍图中所有的顶点,且使每一个顶点仅被访问一次,就叫做图的遍历,它是图的基本运算。
遍历实质:找每一个顶点的邻接点的过程。
图的特点:图中可能存在回路,且图的任一顶点都可能与其他顶点相同,在访问完某一个顶点之后可能会沿着某些边又回到了曾经访问过的顶点。
避免重复访问:设置辅助数组visited[n],用来标记每一个被访问过的点。初始状态visited[i]=0,顶点i被访问,visited[i]值加一。
图常用的遍历:
- 深度优先搜索(Depth_First Search——DFS)
- 广度优先搜索(Breadth_First Search——BFS)
4.1深度优先遍历(DFS)
深度优先遍历顾名思义就是一条路走到黑,再寻其他未走过的路。其类似于树的先序遍历。
思想:假设初始状态是图中所有顶点均未被访问,则从某个顶点v出发,首先访问该顶点,然后依次从它的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和v有路径相通的顶点都被访问到。 若此时尚有其他顶点未被访问到,则另选一个未被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。显然,深度优先搜索是一个递归的过程。
方法:
- 在访问图中某一起始顶点v后,由v出发,访问它的任一邻接顶点w1
- 再从w1出发,访问与w1邻接但未被访问过的顶点w2
- 然后再从w2出发,进行类似的访问
- 如此进行下去,直至到达所有的邻接顶点都被访问过的顶点u为止
- 接着,退回一步,退到前一次刚访问过的顶点,看是否还有其他没有被访问过的顶点
- 如果有,则访问此顶点,之后再从此顶点出发,进行与前述类似的访问
- 如果没有,就再退回一步进行搜索。重复上述过程,直至连通图中所有顶点都被访问过。
时间复杂度:
4.2广度优先遍历(BFS)
BFS类似于树的层次遍历。
方法:从图的某一顶点出发,首先依次访问该顶点的所有邻接顶点,再按照这些顶点被访问的先后顺序依次访问与它们相邻接的所有未被访问的顶点;重复此过程,直至所有顶点均被访问为止。
时间复杂度:
1.如果使用邻接矩阵存储,则BFS对于每一个被访问到的顶点,都要循环检测矩阵中的整整一行(n个元素),故时间复杂度为O(
n
2
n^2
n2)
2.如果使用邻接表存储,虽然有2e个表结点,但只需扫描e个结点即可完成遍历,加上访问n个头结点的时间,时间复杂度为O(
n
+
e
n+e
n+e)
4.3DFS与BFS算法比较
- 空间复杂度相同,都是O(n)(借用了堆栈或队列)
- 时间复杂度只与存储结构有关,而与搜索路径无关。