目录
7.1.1 图的基本概念
1.图的定义
(1)含义:图是由非空的有限集合和顶点间的关系集合组成,记作:。
注:其中V是顶点的有限非空集合,E是顶点间关系的有穷集合记作边集。
(2)表示: 顶点与
之间的边无方向性,用无序偶对
表示;若有方向性则用有序偶对
(
邻接到
,
邻接于
)表示。其中
称为弧头,
称为弧尾。
(3)表示中的前者为无向图,后者为有向图。
图示如下:无向图
有向图:
2.图的基本术语
(1)邻接点:无向图中称两个互连的顶点互为邻接点,有向图中表示一条弧,则
邻接
或
邻接自
。
(2)顶点的度,入度与出度:无向图中某顶点的度指的是与它相连的边的数量记作。有向图中顶点的度指的是其出度与入度之和,顶点
的入度指的是以
为弧头记作
,反之,出度指的是以顶点
为弧尾记作
,顶点
的度为
.
注:若图有n个顶点,e条边,则
(3)完全图:无向图中任意两个顶点之间均存在一条边,称为无向完全图。有向图中,任意两个顶点均存在方向相反的两条弧,称为有向完全图。
a.含有n个顶点的无向完全图有条边,图示如下:
b. 含有n个顶点的有向完全图有条边,图示如下:
(4)路径和路径长度:从某顶点出发到某顶点
所经历的一系列顶点的顶点序列为这两个顶点的路径(连续的边构成的顶点序列);路径长度指的是该路径上经过的边或弧的数量(路径上边或弧的数目/权值之和)。
(5)简单路径和回路:
简单路径:路径中不存在重复的顶点
非简单路径:类比简单路径和回路的结合
回路:第一个顶点和最后一个顶点相同的路径
(6)权和网:代表某种意义的数值称为权,带权的图称为网。
(7)子图:对于图和图若满足
,则
是
的子图。
(8)连通图和连通分量(针对无向图):无向图中和
之间存在路径则称二者互相连通。若任意一对顶点的是连通的,称该图为连通图,非连通图的极大连通子图称为连通分量。
(9)强连通图和强连通分量(针对有向图): 有向图中若与
之间,即可以从
到
也可以从
到
,则称该图是强连通图,非强连通图的极大强连通子图叫做强连通分量。
(10)生成树和生成森林:
a.一个连通图G的生成树是具有G中全部顶点的一个极小连通子图。一棵树含有n个顶点的生成树,必然含有n-1条边。
注:极小连通子图:某图是某图的连通子图,在该子图中删除一条边,子图不再连通。
b.非连通图的每个连通分量分别可以得到一棵生成树,各个连通分量的生成树则组合构成了一个生成森林。
c.在有向图中,若有一个顶点的度为0,其余顶点的度均为1,则该有向图可以构成一棵有向树。一个有向图的生成森林由若干棵有向树组成,含有图中全部顶点,但只有构成若干棵弧不相交的有向树的弧。
7.1.2 图的存储结构
1.定义
图所对应的关系是多对多的关系,其没有顺序存储结构,可以使用数组(邻接矩阵)或二维数组(邻接表)存储图中结点和弧的信息。
2.邻接矩阵存储
(1)定义
邻接矩阵是一种顺序存储方式,即:分别引入两个数组,一个用于存储图的顶点的信息称为顶点表;另一个用于存储顶点间的关系,称为邻接矩阵。
(2)矩阵元素定义
1)设图有n(n>0)个顶点,则顶点表
,邻接矩阵
.
2)设图有n(n>0)个顶点,则图G所对应的邻接矩阵arcs是一个n阶方阵,矩阵中的元素定义如下:
3)对于带权图,邻接矩阵的元素定义为相应顶点之间的边的权值,即
注:∞可以用一个较大的数值代替。
(3)图及邻接矩阵的存储结构
注: 1.对于无向图顶点的度=第
行(列)中的1的个数。除此之外无向完全图的邻接矩阵中,对角元素为0,其余为1.
2.有向图中顶点度=入度+出度。顶点的出度=第行元素之和;顶点的入度=第i列元素之和。
注:第i行含义:以结点为尾的弧(出度边) 第i列含义:以结点
为头的弧(入度边)
定义:
typedef struct Mgraph{
VertexType vexs[Max];
int arcs[Maxnum][Maxnum];
int vexnum,arcnum;
}
(4)特征
优点 :容易看出两个顶点之间知否相连以及容易得到各顶点的度。
缺点:邻接矩阵占用的存储单元个数只与图中的顶点个数有关,与边的数目无关。因此对于稀疏的图会造成空间浪费。
3.邻接表存储
(1)定义
是由顺序存储和链式存储相结合的存储方式,类似于树的孩子链表表示法。对于图中的每个顶点v,将所有与其邻接的自顶点连接成一条单链表,称为边表。将所有边表的头结点组成一个一维数组,称为顶点表。
(2) 图及邻接表存储结构
注:1.无向图的特点:
1)邻接表不唯一
2)若无向图中有n个顶点和e条边则其邻接表需要n个头结点和2e个表结点。
3)无向图中顶点的度为第
个单链表中的结点数
4)时间复杂度:O(n+2e)
2.有向图特点:
1)顶点的出度为第
个单链表中的结点个数
2)入度为整个单链表中邻接点域值是i-1的结点个数。
3)空间复杂度:O(n+e)
定义:
typedef char VertexType;
typedef struct EdgeNode{
int adjvex;
EdgeNode * next;
}*EdgeList;
typedef struct {
VertexType data;
EdgeList firstedge;
}VexNode;
typedef struct{
VexNode adjlist[MAX];
int vexnum,arcnum;
}ALGraph;
(3)逆邻接表(求入易,求出难与邻接表相反)
解决邻接矩阵求入度难的问题
(4)邻接多重链表(多指针)
...........
(5)优点
节省空间
7.1.3 图的遍历
1.图遍历的概念
(1)含义
图的遍历是指从图中某一顶点出发,沿着某条路径对图中所有的顶点访问且仅访问一次。
注:图遍历算法需要解决的问题:
1)是否存在回路。
2)对于非连通图,从图的某个顶点出发,不能访问到所有顶点。
3)图中相邻顶点不唯一,选谁?
解决1)设置标记数组,访问过的往数组里面填入特定值表示已经访问过了。解决问题2)任意选择一个没有被访问过的某一顶点出发,直到访问完全部结点。解决问题3)根据不同的邻接点遍历次序可以分为:深度优先遍历和广度优先遍历。
2.深度优先遍历(DFS)
(1)思路:
1)图的深度优先遍历类似于树的先序遍历
2)思路:从任意顶点(未被访问过)出发访问该顶点,依次从v的未被访问的邻接点出发深度优先遍历图,直到所有与v相连的顶点都被访问到。若此时图中还有顶点没有被访问到,则选择一个为被访问的顶点作为新起点,重复以上步骤。
3)举例图示:
如该图所示序号,对应得到的生成树为右边图示。
(2)连通图的深度优先算法递归算法
bool visited[MAX]={false}
void DFS(MGraph G, int v) {
cout << G.vexs[v];
visited[v] = true;
for (int i = 0; i < G, vexnum; i++) {
if (G.arcs[v][i] != 0 && !visited[i]) {
DFS(G, i);
}
}
}
注:1)对于无向图,若此图是非连通图,DFS只能访问从初始点v所在的连通分量的所有顶点,而访问不到其他连通分量中的顶点,因此只需要从各个连通分量中选择一个出发点,多次调用DFS算法进行深度优先遍历。
2)对于有向图,若从初始点出发可以到达图中其余各顶点都存在路径,则直接执行DFS算法可以直接访问完全部顶点,否则从未被访问的顶点中选择新的顶点作为出发点,再次调用DFS算法继续进行深度优先遍历算法,直到全部访问完。
有向图的遍历算法:
void DFSTraverse(MGraph G) {
for (int i = 0; i < G.vexnum; i++) {
if (!visited[i])
DFS(G, i);
}
}
3.广度优先遍历
(1)定义
图的广度优先遍历类似于树的层次遍历。
步骤:a.从某顶点出发,访问该顶点。然后在依次访问它们各自未被访问过的邻接点。分别从
出发,依次访问它们各自未被访问过的邻接点直到图中所有与初始出发点v有路径相连的顶点都被访问过为止。
举例图示:
注:a.广度优先遍历是一种分层的遍历过程,每向前走一步可能访问一批结点,不存在回退的情况,其过程也不是一个递归过程,算法也不是递归的。
b.为了确保“”在上一层先被访问的顶点的邻接点”先于“在上一层后被访问的顶点的邻接点”被访问,采用队列来存储被访问过的顶点(队列先进先出),以实现按层访问。另外设置标记数组visited[]给已访问过的顶点加标记
(2)算法演示
void BFSTraverse(MGraph G, int v) {
bool* visited = new bool[G.vexnum];
SeqQueue q;
int c;
InitQueue(q);
for (int i = 0; i < G.vexnum; i++) {
visited[i] = false;
}
for (int i = 0; i < G.vexnum; i++) {
if (!visited[i]) {
cout << G.vexs[i] << " ";
visited[i] = true;
EnQueue(q, i);
while (!QueueEmpty(q)) {
c = DeQueue(q);
for (int j = 0; j < G.vexnum; j++) {
if (G.arcs[c][j] == 1 && !visited[j]) {
cout << G.vexs[j] << " ";
visited[j] = true;
EnQueue(q, j);
}
}
}
}
}
delete[] visited;
}
注:广度优先遍历算法的时间复杂度和深度优先遍历的算法时间复杂度相同,但广度优先算法采用队列对已经访问过的顶点进行存储,深度优先遍历则采用栈。二者邻接矩阵的时间复杂度为,邻接表时间复杂度为
。
两者的时间复杂度只与存储结构有关,而与搜索路径无关。