第七章图 内容简介
图结构中结点之间的关系是任意的,每个元素都可以和其他任何元素相关,即元素之间是多对多的关系。
图作为一种非线性数据结构,被广泛应用于多个技术领域,例如系统工程、化学分析、统计力学、遗传学、控制论、人工智能、编译系统等领域。
在离散数学中侧重于对图的理论的研究。
【本章要点】
l 图的基本概念
l 图的存储结构
l 图的遍历
l 图的连通性
l 有向无环图应用——拓扑排序、关键路径
l 最短路径
l 总结与提高
第1讲 图的基本概念——内容简介
【本讲要点】
图的定义与ADT定义
图的基本术语
第 1 讲 图的基本概念——教学讲义
一、图的定义
图(Graph)是一种网状数据结构,其形式化定义如下:
- Graph=(V,R)
- V={x∣x∈DataObject}
- R={VR}
- VR={<x,y>∣P(x,y)∧(x,y∈V)}
DataObject 为一个集合,该集合中的所有元素具有相同的特性。V 中的数据元素通常称为顶点(vertex),VR 是两个顶点之间的关系的集合。P(x,y)表示x 和 y 之间有特定的关联属性 P。
若<x,y>∈VR,则<x,y>表示从顶点 x 到顶点 y 的一条弧(arc),并称 x 为弧尾(tail)或起始点,称 y 为弧头(head)或终端点,此时图中的边是有方向的,称这样的图为有向图。
若<x,y>∈VR,必有<y,x>∈VR,即 VR 是对称关系,这时以无序对(x,y)来代替两个有序对,表示 x 和 y 之间的一条边(Edge),此时的图称为无向图。
下图分别给出了一个有向图和一个无向图的示例。
所谓的“顶点在图中的位置”是指该顶点在这个人为的随意排列中的位置序号。
同理,也可以对某个顶点的邻接点进行人为的排序,在这个序列中,自然形成了第 1 个和第 k 个邻接点,并称第 k+1 个邻接点是第 k 个邻接点的下一个邻接点,而最后一个邻接点的下一个邻接点为“空”。
下面给出图的抽象数据类型定义:
ADT Graph
数据对象 V: 一个集合,该集合中的所有元素具有相同的特性。
数据关系 R: R={VR}
VR={<x,y>∣P(x,y)∧(x,y∈V)}
基本操作:
操作前提:已知图 G 不存在
操作结果:创建图 G。
(2)DestoryGraph(G)
操作前提:已知图 G 存在;
操作结果:销毁图 G。
(3)LocateVertex(G,v)
操作前提:已知图 G 存在,顶点 v 值合法;
操作结果:若图 G 中存在顶点 v,则返回顶点 v 在图 G 中的位置。若图 G中没有顶点 v,则函数返回值为“空”。
(4)GetVertex(G,i)
操作前提:已知图 G 存在;
操作结果:返回图 G 中的第 i 个顶点的值。若 i 大于图 G 中顶点数,则函数返回值为“空”。
(5)FirstAdjVertex(G,v)
操作前提:已知图 G 存在,顶点 v 值合法;
操作结果:返回图 G 中顶点 v 的第一个邻接点。若 v 无邻接点或图 G 中无顶点 v,则函数返回值为“空”。
(6)NextAdjVertex(G,v,w)
操作前提:已知图 G 存在,w 是图 G 中顶点 v 的某个邻接点。
操作结果:返回顶点 v 的下一个邻接点(紧跟在 w 后面)。若 w 是 v 的最后一个邻接点,则函数返回值为“空”。
(7)InsertVertex(G,u)
操作前提:已知图 G 存在,u 值合法;
操作结果:在图 G 中增加一个顶点 u。
(8)DeleteVertex(G,v)
操作前提:已知图 G 存在,v 值合法;
操作结果:删除图 G 的顶点 v 及与顶点 v 相关联的弧。
(9)InsertArc(G,v,w)
操作前提:已知图 G 存在,v 值、w 值合法;
操作结果:在图 G 中增加一条从顶点 v 到顶点 w 的弧。
(10)DeleteArc(G,v,w)
操作前提:已知图 G 存在,v 值、w 值合法,存在弧(v,w) ;
操作结果:删除图 G 中从顶点 v 到顶点 w 的弧。
(11)TraverseGraph(G)
操作前提:已知图 G 存在;
操作结果:按照某种次序,对图 G 的每个顶点访问一次且仅访问一次。
二、基本术语
(1)完全图、稀疏图与稠密图
设 n 表示图中顶点的个数,用 e 表示图中边或弧的数目,并且不考虑图中每个顶点到其自身的边或弧。即若<vi,vj>∈VR,则 vi≠vj。
对于无向图而言,其边数 e 的取值范围是 0~n(n-1)/2。有 n(n-1)/2 条边(图中每个顶点和其余 n-1个顶点都有边相连)的无向图为无向完全图。
对于有向图而言,其边数 e 的取值范围是 0~n(n-1)。有 n(n-1)条边(图中每个顶点和其余 n-1 个顶点都有弧相连)的有向图为有向完全图。
对于有很少条边的图(e < n log n)称为稀疏图,反之称为稠密图。
(2)子图
设有两个图 G=(V,{E})和图 G/=(V/,{E/}),若 V/ V 且 E/ E 则称图 G/为 G 的子图。下图给出了上面 G1 和 G2 的子图。
(3)邻接点
对于无向图 G=(V,{E}),如果边(v,v/)∈E,则称顶点 v,v/互为邻接点,即 v,v/ 相邻接。边(v,v/)依附于顶点 v 和 v/,或者说边(v,v/)与顶点 v 和v/ 相关联。
对于有向图 G=(V,{A})而言,若弧<v,v/>∈A,则称顶点 v 邻接到顶点 v/,顶点 v/ 邻接自顶点 v,或者说弧<v,v/>与顶点 v,v/相关联。
(4)度、入度和出度
对于无向图而言,顶点 v 的度是指和 v 相关联边的数目,记作 TD(v)。
例如:上面图 G2 中顶点 C 的度是 3,A 的度是 2;在有向图中顶点 v 的度有出度和入度两部分,其中以顶点 v 为弧头的弧的数目成为该顶点的入度,记作 ID(v),
以顶点 v 为弧尾的弧的数目称为该顶点的出度,记作 OD(v),则顶点 v 的度为TD(v)= ID(v)+ OD(v)。
例如:上面图 G1 中顶点 A 的入度是 ID(A)=1,出度OD(A)=2,顶点 A 的度 TD(A)= ID(A)+ OD(A)=3。
一般地,若图 G 中有 n 个顶点,e条边或弧,则图中顶点的度与边的关系如下:边等于所有顶点度之和除二
(5)权与网
实际应用中,图的边或弧上往往与具有一定意义的数有关,即每一条边都有与它相关的数,称为权,
这些权可以表示从一个顶点到另一个顶点的距离或耗费等信息。这种带权的图称为赋权图或网,如下图所示
(6)路径与回路
无向图 G=(V,{E})中从顶点 v 到 v/的路径是一个顶点序列 vi0,vi1,vi2,…,vin,其中(vij-1,vij)∈E,1≤j≤n。
如果图 G 是有向图,则路径也是有向的,顶点序列应满足<vij-1,vij>∈A,1≤j≤n。
在一个路径中,若其第一个顶点和最后一个顶点是相同的即 v = ,则路径的长度是指路径上经过的弧或边称该路径为一个回路或环。
若表示路径的顶点序列中的顶点各不相同,则称这样的路径为简单路径。
除了第一个和最后一个顶点外,其余各顶点均不重复出现的回路为简单回路。如下图(a),(b)所示。
(7)连通图
在无向图 G=(V,{E})中,若从 vi 到 vj 有路径相通,则称顶点 vi 与 vj 是连通的。
如果对于图中的任意两个顶点 vi、vj∈V,vi,vj 都是连通的,则称该无向图 G 为连通图。
例如:G2 就是连通图。
无向图中的极大连通子图称为该无向图的连通分量。
在有向图 G=(V,{A})中,若对于每对顶点 vi、vj∈V 且 vi≠vj,从 vi 到 vj 和 vj 到 vi 都有路径,则称该有向图为强连通图。
有向图的极大强连通子图称做有向图的强连通分量,
图 G1 的强连通分量如下图(c)所示。
一个连通图的生成树是一个极小连通子图,
它含有图中全部顶点,但只有足以连通 n 个点的 n-1 条边的数目。
作业
一个有n个顶点的有向图最多有 边
-
A.n
-
B.n(n-1)
-
C.n(n-1)/2
-
D.2n
具有n个顶点的有向图至少应有 弧才能确保是一个强连通图。
-
A.n-1
-
B.n
-
C.n(n-1)
-
D.n(n-1)/2
在一个无向图中,所有顶点的度之和等于边条数的 2 倍。
具有6个顶点的无向图至少应有 5 条边才能确保是一个连通图。
一个有向图G中所有顶点的入度之和是所有顶点出度之和的1 倍。
第2讲 图的存储结构——内容简介
图的存储方法有很多,本节介绍4种较常用的存储表示法。
【本讲要点】
l 邻接矩阵表示法;
l 邻接表;
l 邻接多重表;
l 十字链表。
每种存储表示方法各有利弊,可以根据实际应用问题来选择合适的存储表示方法。
第 2 讲 图的存储结构——教学讲义
本讲介绍 4 种较常用的存储表示法:①邻接矩阵表示法;②邻接表;③邻接多重表;④十字链表
由于每种方法各有利弊,因此可以根据实际应用问题来选择合适的存储表示方法。
①邻接矩阵表示法
图的邻接矩阵表示法(Adjacency Matrix)也称作数组表示法。
它采用两个数组来表示图:一个是用于存储顶点信息的一维数组,另一个是用于存储图中顶点之间关联关系的二维数组,
这个关联关系数组被称为邻接矩阵。若 G 是一具有 n 个顶点的无权图,G 的邻接矩阵是具有如下性质的 n×n 矩阵 A:
上图所示 G1 和 G2 的邻接矩阵如下所示
若图 G 是一个有 n 个顶点的网,则它的邻接矩阵是具有如下性质的 n×n 矩阵 A
例如:下图就是一个有向网及其邻接矩阵的示例
邻接矩阵表示法的 C 语言描述如下:
#define MAX_VERTEX_NUM 20/*最多顶点个数*/
#define INFINITY 32768/*表示极大值,即∞*/
/* 图的种类:DG 表示有向图, DN 表示有向网, UDG 表示无向图, UDN 表示无向网*/
typedef enum { DG, DN, UDG, UDN } GraphKind;
typedef char VertexData;/*假设顶点数据为字符型*/
typedef struct ArcNode {
AdjType adj; /* 对于无权图,用 1 或 0 表示是否相邻;对带权图,则为权值类型 */
OtherInfo info;
} ArcNode;
typedef struct {
VertexData vertex[MAX_VERTEX_NUM];
/*顶点向量*/
ArcNode arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; /*邻接矩阵*/
int vexnum, arcnum; /*图的顶点数和弧数*/
GraphKind kind;/*图的种类标志*/
} AdjMatrix; /*(Adjacency Matrix Graph)*/
邻接矩阵法的特点如下:
l 存储空间: 对于无向图而言,它的邻接矩阵是对称矩阵(因为若(vi,vj)∈E(G),则(vj,vi)∈E(G)),因此可以采用特殊矩阵的压缩存储法,
即只存储其下三角即可,这样,一个具有 n 个顶点的无向图 G,它的邻接矩阵需要 n(n-1)/2 个存储空间即可。
但对于有向图而言 ,其中的弧是有方向的,即若<vi,vj>∈E(G),不一定有<vj,vi>∈E(G),
因此,有向图的邻接矩阵不一定是对称矩阵,对于有向图的邻接矩阵的存储则需要 n^2 个存储空间。
l 便于运算: 采用邻接矩阵表示法,便于判定图中任意两个顶点之间是否有边相连,即根据 A[i,j]=0 或 1 来判断。
另外还便于求得各个顶点的度。对于无向图而言,其邻接矩阵第 i 行元素之和就是图中第 i 个顶点的度:
对于有向图而言,其邻接矩阵第 i 行元素之和就是图中第 i 个顶点的出度
对于有向图而言,其邻接矩阵第 i 列元素之和就是图中第 i 个顶点的入度
采用邻接矩阵存储法表示图,很便于实现图的一些基本操作,如实现访问图G 中 V 顶点第一个邻接点的函数 FirstAdjVertex(G,v)可按如下步骤实现:
FirstAdjVertex(G,v):
⑴ 首先,由 LocateVertex(G,v)找到 v 在图中的位置,即 v 在一维数组vertex 中的序号 i。
⑵ 二维数组 arcs 中第 i 行上第一个 adj 域非零的分量所在的列号 j,便是v 的第一个邻接点在图 G 中的位置。
⑶ 取出一维数组 vertex[j]中的数据信息,即与顶点 v 邻接的第一个邻接点的信息。
对于稀疏图而言,不适于用邻接矩阵来存储,因为这样会造成存储空间的浪费。
用邻接矩阵法创建有向网的算法如下:
【算法描述】
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#define MAX_VERTEX_NUM 20 /*最多顶点个数*/
#define INFINITY 32768 /*表示极大值,即∞*/
#define True 1
#define False 0
#define Error -1
#define Ok 1
#define AdjType int
#define OtherInfo int
typedef enum { DG, DN, UDG, UDN } GraphKind; /*图的种类:DG表示有向图, DN表示有向网, UDG表示无向图, UDN表示无向网*/
typedef char VertexData; /*假设顶点数据为字符型*/
typedef struct ArcNode
{
AdjType adj; /*对于无权图,用1或0表示是否相邻;对带权图,则为权值类型*/
OtherInfo info;
}ArcNode;
typedef struct
{
VertexData vexs[MAX_VERTEX_NUM]; /*顶点向量*/
ArcNode arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; /*邻接矩阵*/
int vexnum, arcnum; /*图的顶点数和弧数*/
GraphKind kind; /*图的种类标志*/
}AdjMatrix; /*(Adjacency Matrix Graph)*/
/*用邻接矩阵法创建有向网的算法如下:*/
int LocateVertex(AdjMatrix* G, VertexData v) /*求顶点位置函数*/
{
int j = Error, k;
for (k = 0; k < G->vexnum; k++)
if (G->vexs[k] == v)
{
j = k;
break;
}
return(j);
}
int CreateDN(AdjMatrix* G) /*创建一个有向网*/
{
int i, j, k, weight;
VertexData v1, v2;
printf("输入图的顶点数和弧数\n");
fflush(stdin);
scanf("%d,%d", &G->arcnum, &G->vexnum); /*输入图的顶点数和弧数*/
for (i = 0; i < G->vexnum; i++) /*初始化邻接矩阵*/
for (j = 0; j < G->vexnum; j++)
G->arcs[i][j].adj = INFINITY;
for (i = 0; i < G->vexnum; i++)
{
printf("输入图的顶点\n");
fflush(stdin);
scanf("%c", &G->vexs[i]); /* 输入图的顶点*/
}
for (k = 0; k < G->arcnum; k++)
{
printf("输入一条弧的两个顶点及权值\n");
fflush(stdin);
scanf("%c,%c,%d", &v1, &v2, &weight);/*输入一条弧的两个顶点及权值*/
i = LocateVertex(G, v1);
j = LocateVertex(G, v2);
G->arcs[i][j].adj = weight; /*建立弧*/
}
return(Ok);
}
void main()
{
AdjMatrix G;
CreateDN(&G);
}
该算法的时间复杂度为 O( +e×n),其中 O(n )时间耗费在对二维数组arcs 的每个分量的 arj 域初始化赋值上。O(e×n)的时间耗费在有向网中边权的赋值上
②邻接表表示法
它克服稀疏图邻接矩阵造成存储空间的很大浪费的弊病
基本思想是有向图只存出度,无向图存相邻
每个顶点建立一个带头结点的边链表,每个边链表的头结点又构成一个表头结点表。
(1)表头结点表。数据域(vexdata)用于存储顶点的名或其它有关信息;
链域(firstarc)用于指向链表中顶点
(2)边表:
邻接点域(adjvex)用于存放与顶点 vi 相邻接的顶点在图中的位置;
链域(nextarc)用于指向与顶点 vi 相关联的下一条边或弧的结点;
数据域(info)用于存放与边或弧相关的信息(如赋权图中每条边或弧的权值等)。
如下图所示分别是图 G1、G2 邻接表表示示例,其中边表中顶点无顺序要求。
邻接表存储结构的形式化说明如下:
#define MAX_VERTEX_NUM 20 /*最多顶点个数*/
typedef enum { DG, DN, UDG, UDN } GraphKind; /*图的种类*/
typedef struct ArcNode {
int adjvex; /*该弧指向顶点的位置*/
struct ArcNode* nextarc; /*指向下一条弧的指针*/
OtherInfo info; /*与该弧相关的信息*/
} ArcNode;
typedef struct VertexNode {
VertexData data;/*顶点数据*/
ArcNode* firstarc;/*指向该顶点第一条弧的指针*/
} VertexNode;
typedef struct {
VertexNode vertex[MAX_VERTEX_NUM];
int vexnum, arcnum; /*图的顶点数和弧数*/
GraphKind kind;/*图的种类标志*/
}AdjList; /*基于邻接表的图(Adjacency List Graph)*/
■存储空间
对于有 n 个顶点,e 条边的无向图而言,若采取邻接表作为存储结构,则需要 n 个表头结点和 2e 个表结点。很显然在边很稀疏(即 e 远小于 n(n-1)/2 时)
的情况下,用邻接表存储所需的空间要比邻接矩阵所需的 n(n-1)/2 要节省得多。
■无向图的度
在无向图的邻接表中,顶点 vi 的度恰好就是第 i 个单链表上结点的个数。
■有向图的度
在有向图中,第 i 个单链表上结点的个数只是顶点 vi 的出度,只需通过表头向量表中找到第 i 个顶点的边链表的头指针,实现顺链查找即可。
如果要判定任意两个顶点(vi 和 vj)之间是否有边或弧相连,则需要搜索所有
作业
对于一个n个顶点的无向图,若采用邻接矩阵表示,则该矩阵的大小为()
-
A.n
-
B.n(n-1)
-
C.
-
D.
有向图的邻接矩阵一定是对称矩阵。()
-
A.✓
-
B.×
用邻接矩阵存储无向图G时,其第i行中1的个数与第i列中1的个数相等。()
-
A.✓
-
B.×
-
对于一个有n个顶点,e条边的无向图,若采用邻接表表示,则表头结点数组的大小为 n 。
-
对于一个有n个顶点,e条边的无向图,若采用邻接表表示,则边结点有 2e 或2*e 个。
-
用邻接矩阵存储有向图G时,其第i列的所有元素之和等于该顶点的 入度 。
第3讲 图的遍历——内容简介
图的遍历就是从图中的某个顶点出发,按某种方法对图中的所有顶点访问且仅访问一次。图的遍历算法是求解图的连通性问题、拓扑排序和关键路径等算法的基础。
【本讲要点】
图的遍历通常有两种方法:
l 深度优先搜索
l 广度优先搜索
如果从一个无向图的任意一个顶点出发进行一次深度优先搜索即可访问所有顶点,则该图一定是( )
-
A.完全图
-
B.连通图
-
C.有回路
-
D.森林
图的深度优先遍历类似于树的( )遍历
-
A.先序遍历
-
B.中序遍历
-
C.后序遍历
-
D.层次遍历
图的广度优先遍历类似于树的( )遍历
-
A.先序遍历
-
B.中序遍历
-
C.后序遍历
-
D.层次遍历
第4讲 图的连通性——内容简介
【本节要点】
l 利用图的遍历算法来判断图是否为连通图;求一个连通图的连通分量;求取简单路径
l 最小代价生成树的算法
n 普里姆算法
n 克鲁斯卡尔算法
任何一个连通图( )生成树。
-
A.只有一棵
-
B.有一棵或多棵
-
C.一定有多棵
-
D.可能不存在
Prim算法适合求( )的最小生成树。
-
A.边稠密连通网
-
B.边稀疏连通网
-
C.边稠密无向网
-
D.边稀疏无向网
对于n个顶点的连通图G来说,如果其中的某个子图有n个顶点,n-1条边,则该子图一定是G的生成树。( )
-
A.true
-
B.false
对于n个顶点的连通图而言,它的生成树一定有 n-1 条边。
第5讲 有向无环图应用之拓扑排序——内容简介
有向无环图(Directed Acyclic Graph)是指一个无环的有向图,简称DAG。
【本节要点】
l 有向无环图的判定
l 拓扑排序算法
-
A.是个有向无环图
-
B.是个含有回路的有向图
-
C.含有多个入度为0的顶点
-
D.是个强连通图
-
A.true
-
B.false
在AOV网中,顶点表示 活动 。
第6讲 有向无环图应用之关键路径——内容简介
有向图在工程计划和经营管理中有着广泛的应用。用顶点表示事件,用弧表示活动,弧的权值表示活动所需要的时间。 这种有向无环图叫做边表示活动的网(Activity On Edge Network),简称AOE-网。
【本节要点】
l 关键路径定义
l 关键路径求取算法
-
A.从源点到汇点的最长路径
-
B.从源点到汇点的最短路径
-
C.最长回路
-
D.最短回路
关键活动若不能按期完成就会影响整个工程的完成时间,若某些关键活动能提前完成,将可能使整个工程提前完成。()
-
A.true
-
B.false
在AOE网中,关键路径上的活动称为 关键活动 。
第7讲 最短路径——内容简介
【本节要点】
l 求一结点到其他结点的最短路径;
l 求任意两点间的最短路径。
求最短路径的Dijkstra算法的时间复杂度为( )
n为图中顶点数,e为图中边数。
-
A.O(n)
-
B.O(n+e)
-
C.O( )
-
D.O(ne)
求最短路径的Dijkstra算法不适用于有回路的有向网( )
- A.true
- B.false