文章目录
5.1 图(grpah)的基本概念
5.1 应用背景
- 在图中, 圆圈称为顶点, 连线称为边, 连线附带的数值称为边的权.
5.2 图的定义和术语
-
有向图, 无向图:图G由两个集合V(顶点, Vertex)和E(边,Edge)组成, 记为G=(V, E), 其中V是顶点的有穷非空集合, E是边的集合, 边是V中顶点的偶对. 若顶点的偶对是有序的则称为有向图, 使用尖括号
<>
括起来, 若是顶点偶对是无序的, 则为无向图, 使用圆括号
()
括起来.具体的表示方式如下: 有向图: v, w∈V, <v,w>∈ E , 无向图: v, w∈V, (v, w)∈E - 弧, 弧头, 弧尾: 有向图的边称为弧, 设v, w∈V, 若<v, w>∈E, 则有序但对<v, w>表示有向图G从v到w的一条弧, v称为弧尾或始点, w称为弧头或终点., 无向图中vw之间一条边, 在有向图中<v, w>和<w, v>是两条不同的弧,
也可以理解为, 无向图中的边,就是线段AB和线段BA指的同一个, 但在有向图, 就不是线段了, 是射线, 是有方向的, 射线AB和射线BA由于方向不同而不同.
-
无向图中, 有边(v, w), 则v与w互为邻接点,称边(v, w)与顶点v和w相关联.
-
任何两点之间都有边的无向图称为
无向完全图
-
一个具有n个顶点的无向完全图的边数为C
2
n
=n(n-1)/2
-
一个具有n个顶点的无向完全图的边数为C
-
任何两点之间都有弧的有向图称为
有向完全图
-
一个具有n个顶点的有向完全图的边数P
2
n
=n(n-1)
-
一个具有n个顶点的有向完全图的边数P
-
权, 带权图: 图的边附带数值, 这个数值叫权. 每条边都带权的图称为带权图.
-
顶点的度, 入度, 出度: 无向图中顶点v的度是指与该顶点相关联的边的数目, 记为D(v), 如果G是个有向图, 则把以v为终点的弧的数目记为ID(v), 把以v为始点的弧的数目记为OD(v),
-
子图: 设G=(V, E), 若E’是E的子集, V’是V的子集, 且E’中的边仅与V’中的顶点相关联, 则图G’=(V’, E’)称图G的一个子图.
-
路径, 路径长度: 路径上边(或弧)的数目称为
路径长度
,- 简单路径, 回路, 简单回路:序列中顶点不重复出现的路径称为简单路径,
-
第一个顶点和最后一个顶点相同的路径称为
回路或环
, -
除了第一个顶点和最后一个顶点相同外, 其它顶点不重复的回路称为
简单回路或简单环
-
连通, 连通图, 连通分量: 在无向图中, 若顶点v到v’有路径, 则称为v到v’是
连通
的,如图中的任意两个顶点都是连通的,则称
为连通图
,
连通分量
是无向图中的
极大连通子图
-
强连通, 强连通图, 强连通分量: 对于有向图来说, 如果图中任意一对顶点vi和vj, 都有顶点vi到vj的路径, 也有vj到vi的路径, 即两个顶点是双向连通, 那么该有向图就是
强连通图
, 有向图的极大强连通图称为强连通分量. -
生成树, 生成森林: 一个连通图的生成树, 是含有该连通图的全部顶点的一个极小连通子图, 若连通图G的顶点个数为n, 则G生成树的边为n-1, 若子图中的边数大于n-1, 则图中一定有环; 若小于n-1, 则一定不连通.
图的基本运算:
(1) 建立图 CreateGraph(G, V, E): 创建一个图G, 其中V是G的顶点集合, E是G的边的集合
(2) 取顶点信息 GetVex(G, u): 获取图G中顶点u的信息
(3) 取边信息 Getarc(G, u, v): 获取图G中边(u, v)或<u, v>的信息
(4) 查询第一个邻接点 FirstVex(G, u): 获取图G中顶点u的第一个邻接点
(5) 查询下一下邻接点 NextVex(G, u, v): 已经知v是u的一个邻接点, 获取图G中顶点u的下一个邻接点
(6) 插入顶点 InsertVex(G, v):
(7) 删除顶点 DeleteVex(G, v)
(8) 插入边InsertArc(G, v, w)
(9) 删除边 DeleteArc(G, v, w)
(10) 遍历图(G, tag)当tag=0, 方法为深度优先, tag=1为广度优先搜索.
5.2 图的存储结构(p134)
邻接矩阵, 邻接表, 十字链表和邻接多重表
5.2.1 邻接矩阵
邻接矩阵就是用矩阵来描述图中顶点之间的关联关系,使用二维数组实现矩阵.
- 无向图的邻接矩阵是一个对称矩阵.
5.2.2 邻接(Adjacent)表
邻接表是顺序存储与链式存储相结合的存储方法.
对于无向图, 第i个单链表中的结点表示依赖于顶点vi的边, 对于有向图来说, 是顶点为始点的弧, 这个单链表称为顶点vi的邻接表.
每一个单链表高一个表头结点, 每一个表头结点有两个域
vertex
和
firstarc
,
vertex
用来存放顶点vi的信息,
firstarc
用来指向邻接表中的第一个结点.
为了便于访问, 将所以单链表的头结点组成一个一维数组Adjlist, , 单链表中每一个结点称为表结点, 包括两个域: 邻接点域(adjvex)和链域(nextarc), 邻接点域用以存放与vi相邻的顶点在数组Adjlist中的序号, 链域用于指向vi邻接的下一个结点. 在带权图的表结点中增加一个权值域, 用于存储边的权值(weight)
vertex | firstarc adjvex | nextarc adjver|weight|nextarc
//类型定义
#define vnum 20
typedef struct arcnode{
int adjvex; // 下一个顶点编号
WeightType weight; // 带权图的权值域, 若无, 可删除
struct arcnode *nextarc; // 指向下一条边的指针
} ArcNode;
typedef struct vernode{
int vertex; // 顶点编号
ArcNode *firstarc; // 指向第一条边的指针
} AdjList[vnum];
typedef struct gp{
AdjList adjlist;
int vexnum, arcnum; // 项点和边的个数
} Graph;
如果一个无向图有n个顶点, e个边, 那么它的邻接表需要n个头结点, 2e个表结点, 显然在边稀疏(e<<n(n-1)/2)的情况下, 用邻接表表示比今天邻接矩阵节省存储空间.
如何求顶点的度?
无向图中顶点vi的度恰为第i个单链表中的结点数
对有向图, 第i个单链表中的结点个数只是顶点vi的出度. 为了求入度, 则需要遍历整个邻接表. 在所有单链表中, 其邻接域的值为i的结点个数, 就是vi的入度.
-
可以采用逆邻接表
建立有向图的邻接表的算法描述如下:
CreateAdjList(Graph *g){
int n, e, i, j, k;
ArcNode *p;
scanf("%d %d", n, e); // 读入顶点数和边数
g->vexnum=n, g->arcnum=e;
for (i=0; i<=n; i++){
g->adjlist[i].vertex = i;
g->adjlist[i].firstarc = NULL;
}
for (k=0; k<=e; k++){
scanf("%d %d", &i, &j);
p = (ArcNode*)malloc(sizeof(ArcNode)); // 生成j的表结点
p-adjvex = j; // 先改编号
p->nextarc=g->adjlist[i].firstarc; // 将结点j链到i的单链表中
g->adjlist[i].firstarc=p;
}
}
与邻接矩阵能方便地对顶点进行随机访问不同, 在邻接表中要判定任意两个顶点vi和vj是否有边相边, 需要遍历第i个 或者第j个单链表, 因此对于图来说,使用邻接矩阵和邻接表作为存储结构各有利弊.
5.3 图的遍历
深度优先和广度优先, 都适用于有向图和无向图, 类似于树的遍历操作.
5.3.1 连通图的深度优先搜索
深度搜索需要注意两点:
- 搜索到达某个顶点时, 图中仍有顶点未被访问, 如果这个顶点的所有邻接点都被访问过, 那么搜索就要回到前一个被访问过的顶点, 再从该顶点的下一未被访问的邻接点开始深度优先搜索;
-
深度优先的搜索顺序不是唯一的.
算法描述如下:
DeepFs(Graph g, int v){
访问v;
visited[v] = 1;
找出g中v的第一个邻接点w;
while (w存在){
if w未被访问 DeepFs(g, w);
w = g中v的下一个邻接点;
}
}
- 以邻接表为存储结构, 查询邻接点操作实际上是顺序查找链表, 相应的深度优先算法如下:
DeepFs(Grahp g; int v){
ArcNode *p;
printf("%d", v);
visited[v] = 1;
p = g.ajdlist[v].firstarc;
while (p!=NULL){
if (!visited[p->adjvex]) DeepFs(g, p->adjvex);
p = p->nextarc;
}
}
这里的visitd为全局变量. 第一次调用DeepFs前, 需要将visited中每个元素初始化0, 以邻接表为存储结构, 深度优先的算法的时间复杂度是O(n+e), n为顶点数, e为边数,
- 以邻接矩阵的存储结构, 可以通过循环语句访问邻接矩阵的某一行, 相应的算法如下:
DeepFs(Graph g, int v){
int j;
printf("%d", v);
visited[v] = 1;
for (j=0; j<n; j++){ // n为顶点数, j为顶点编号
m = g->arcs[v][j];
if (m && !visited[j]) DeepFs(g, j)
}
}
时间复杂为O(n
2
), 其中为n为顶点数
5.3.2 连通图的广度优先搜索
广度优先类似树的按层次遍历的过程
5.4 图的应用
5.4.1 最小生成树
- 连通图的一次遍历所经过的边的集合及图中所顶点的集合就构成该图的一棵生成树.
- Prim算法
- 构造最小生成树的克鲁斯卡尔方法(Kruskal)
- 单源最短路径
5.4.3拓扑排序
- AOV网
- 工程或者某种流程可以分为若干个小的工程或阶段,这些小的工程或阶段就称为活动.这种使用顶点表示活动的有向图称为AOV网, AOV网中的弧表示活动之间存在着制约关系 .
-
拓扑排序
设G=(V, E)是一个有n个顶点的有向图, V中顶点的序列v1, v2,…, vn称为一个拓扑序列, 当且仅当该序列满足下列条件: 若在有向图G中, 从顶点i到j有一条路径, 则在拓扑序列中顶点i必须排在j之前, 找一个有向图的一个拓扑序列的过程称为拓扑排序
任何一个
无环有向图
, 其全部顶点可以排成一个拓扑序列.
拓扑排序算法的时间复杂度为O(n+e), n是顶点数, e是边数