970 likes | 1.11k Views
第七章 图. 7.1 图的定义和术语. 7.2 图的存储结构. 7.3 图的遍历. 7.4 最小生成树. 7.5 拓扑排序. 7.6 关键路径. 7.7 最短路径. 7.1 图的定义和术语. 图定义 图是由顶点集合 (vertex) 及顶点间的关系集合组成的一种数据结构: Graph = ( V, E ) 其中 V = { x | x 某个数据对象 } 是顶点的有穷非空集合; E1 = {(x, y) | x, y V }
E N D
7.1 图的定义和术语 7.2 图的存储结构 7.3 图的遍历 7.4 最小生成树 7.5 拓扑排序 7.6 关键路径 7.7 最短路径
7.1 图的定义和术语 图定义 图是由顶点集合(vertex)及顶点间的关系集合组成的一种数据结构: Graph=( V, E ) 其中 V = { x | x 某个数据对象} 是顶点的有穷非空集合; E1 = {(x, y) | x, y V } 或 E2 = {<x, y> | x, y V && Path (x, y)} 其中, E1是顶点之间关系的有穷集合,也叫做边(edge)集合,此时的图称为无向图。 E2 表示从 x 到 y 的一条弧,且称x为弧尾,y为弧头,这样的图称为有向图。
V2 V1 V3 V5 V4 V1 V2 V4 V3 若顶点vi和vj之间的边没有方向,则称这条边为无向边,表示为(vi,vj)。 如果图的任意两个顶点之间的边都是无向边,则称该图为无向图。 若从顶点vi到vj的边有方向,则称这条边为有向边,表示为<vi,vj>。 如果图的任意两个顶点之间的边都是有向边,则称该图为有向图。
图的基本术语 完全图若有 n 个顶点的无向图有 n(n-1)/2条边, 则此图为无向完全图。有 n 个顶点的有向图有n(n-1)条边, 则此图为有向完全图。 0 0 0 0 1 1 2 1 2 1 3 3 4 5 6 2 2
0 0 0 0 子图 1 2 1 1 2 2 3 3 3 3 • 邻接顶点如果 (u, v) 是 E(G) 中的一条边,则称 u 与 v 互为邻接顶点。 • 子图设有两个图 G=(V, E) 和 G‘=(V’, E‘)。若 V’ V 且 E‘E, 则称 图G’ 是 图G 的子图。 • 权某些图的边具有与它相关的数, 称之为权。这种带权图叫做网络。
顶点的度一个顶点v的度是与它相关联的边的条数。记作TD(v)。在有向图中, 顶点的度等于该顶点的入度与出度之和。 • 顶点 v 的入度是以 v 为终点的有向边的条数, 记作 ID(v); 顶点 v 的出度是以 v 为始点的有向边的条数, 记作 OD(v)。 • 路径 在图 G=(V, E) 中, 若从顶点 vi 出发, 沿一些边经过一些顶点 vp1, vp2, …, vpm,到达顶点vj。则称顶点序列 (vi vp1 vp2 ... vpm vj) 为从顶点vi到顶点 vj的路径。它经过的边(vi, vp1)、(vp1, vp2)、...、(vpm, vj) 应是属于E的边。
A B E C F 有向图 顶点的出度: 以顶点v 为弧尾的弧的数目; 顶点的入度: 以顶点v为弧头的弧的数目。 例如: 顶点的度(TD)=出度(OD)+入度(ID) TD(B) =OD(B)+2ID(B) = 3
路径长度非带权图的路径长度是指此路径上边的条数。带权图的路径长度是指路径上各边的权之和。路径长度非带权图的路径长度是指此路径上边的条数。带权图的路径长度是指路径上各边的权之和。 • 简单路径若路径上各顶点 v1,v2,...,vm 均不 互相重复, 则称这样的路径为简单路径。 • 简单回路 若路径上第一个顶点 v1 与最后一个顶点vm 重合, 则称这样的路径为回路或环。 0 0 0 1 2 1 2 1 2 3 3 3
A B E C F 如:从A到F长度为 3 的路径{A,B,C,F}
连通图与连通分量在无向图中, 若从顶点v1到顶点v2有路径, 则称顶点v1与v2是连通的。如果图中任意一对顶点都是连通的, 则称此图是连通图。非连通图的极大连通子图叫做连通分量。 • 强连通图与强连通分量在有向图中, 若对于每一对顶点vi和vj, 都存在一条从vi到vj和从vj到vi的路径, 则称此图是强连通图。非强连通图的极大强连通子图叫做强连通分量。
C B D A E F C B D A E F 无向图,若图中任意两个顶点之间都有路径相通,则称此图为连通图; 若无向图为非连通图,则图中各个极大连通子图称作此图的连通分量。
A B E C F 有向图,若任意两个顶点之间都存在一条有向路径,则称此有向图为强连通图。 否则,其各个强连通子图称作它的强连通分量。 A B E C F
C B D A E F 生成树:假设一个连通图有 n 个顶点和 e 条边,其中 n-1 条边和 n 个顶点构成一个极小连通子图,称该极小连通子图为此连通图的生成树。在极小连通子图中增加一条边,则一定有环。 在极小连通子图中去掉一条边,则成为非连通图。 C B D A E F
A D C B A F B C E D F E 有n个顶点,n-1条边的图必定是生成树吗? 对非连通图,则称由各个连通分量的生成树的集合为此非连通图的生成森林。
7.2 图的存储结构 一、图的数组(邻接矩阵)存储表示 二、图的邻接表存储表示 三、有向图的十字链表存储表示 四、无向图的邻接多重表存储表示
一、图的数组(邻接矩阵)存储表示 0 (i,j)VR Aij={ 定义:矩阵的元素为 1 (i,j)VR A B C D E F B C A B C D E F A D F E 无向图的邻接矩阵为对称矩阵
A B C D E A B C D E A B D C E 有向图的邻接矩阵为非对称矩阵
邻接矩阵表示法特点: 1)无向图邻接矩阵是对称矩阵,同一条边表示了两次; 2)顶点v的度:在无向图中等于二维数组对应行(或列) 中1的个数;在有向图中, 统计第 i 行 1 的个数可得 顶点 i 的出度,统计第 j 列 1 的个数可得顶点 j 的 入度。 3)判断两顶点v、u是否为邻接点:只需判二维数组对应 分量是否为1; 4)顶点不变,在图中增加、删除边:只需对二维数组对 应分量赋值1或清0; 5)设存储顶点的一维数组大小为n(图的顶点数n), G占 用存储空间:n+n2;G占用存储空间只与它的顶点数有关 ,与边数无关;适用于边稠密的图;
typedef struct ArcCell { // 弧的定义 VRType adj; // VRType是顶点关系类型// 对无权图,用1或0表示相邻否; // 对带权图,则为权值类型。 InfoType *info; // 该弧相关信息的指针 } ArcCell, AdjMatrix[MAX_VERTEX_NUM] [MAX_VERTEX_NUM];
typedef struct { // 图的定义 VertexType // 顶点信息 vexs[MAX_VERTEX_NUM]; AdjMatrix arcs; // 弧的信息 int vexnum, arcnum; // 顶点数,弧数 GraphKind kind; // 图的种类标志 } MGraph;
网络的邻接矩阵 A 9 15 11 B D 7 21 3 C E 2
二、图的邻接表 存储表示 B C A D 0 A 1 4 1 B 0 4 5 2 C 3 5 3 D 2 5 4 E 0 1 5 F 1 2 3 F E 同一个顶点发出的边 链接在同一个边链表中 ,每一个链结点代表一 条边(边结点), 结点中有 另一顶点的下标 adjvex 和指针 nextedge。
有向图的邻接表 A 0 1 2 3 4 A B C D E 1 4 B E 2 C D 3 0 1 可见,在有向图的邻接表中不易找到指向该顶点的弧。 2
A 有向图的逆邻接表 B E 在有向图的邻接表中,对每个顶点,链接的是指向该顶点的弧。 C D A B C D E 3 0 1 2 3 4 3 0 4 2 0
邻接表表示法特点: 1)无向图邻接表边结点数是边数的两倍. 2)顶点vi的度:在无向图中等于第i个链表中的 结点数;在有向图邻接表中,第i行的结点数等于 顶点i的出度,在有向图逆邻接表中,第i行的结点 数等于顶点i的入度。 3)在邻接表上容易找到任一顶点的第一个邻接点 和下一个邻接点 4)设存储顶点的一维数组大小为n(图的顶点数n), G占用存储空间:n+e;G占用存储空间与它的顶 点数和边数有关;适用于边稀疏的图;
弧的结点结构 adjvex nextarc info typedef struct ArcNode { int adjvex; // 该弧所指向的顶点的位置 struct ArcNode *nextarc; // 指向下一条弧的指针 InfoType *info; // 该弧相关信息的指针 } ArcNode;
顶点的结点结构 data firstarc typedef struct VNode { VertexType data; // 顶点信息 ArcNode *firstarc; // 指向第一条依附该顶点的弧 } VNode, AdjList[MAX_VERTEX_NUM];
图的结构定义 typedef struct { AdjList vertices; int vexnum, arcnum; int kind; // 图的种类标志 } ALGraph;
三、有向图的十字链表存储表示 弧的结点结构 弧尾顶点位置 弧头顶点位置 弧的相关信息 指向下一个有相同弧尾的结点 指向下一个有相同弧头的结点 typedef struct ArcBox { // 弧的结构表示 int tailvex, headvex; InfoType *info; struct ArcBox *hlink, *tlink; } VexNode;
顶点的结点结构 顶点信息数据 指向该顶点的第一条入弧 指向该顶点的第一条出弧 typedef struct VexNode { // 顶点的结构表示 VertexType data; ArcBox *firstin, *firstout; } VexNode;
有向图的结构表示(十字链表) typedef struct { VexNode xlist[MAX_VERTEX_NUM]; // 顶点结点(表头向量) int vexnum, arcnum; //有向图的当前顶点数和弧数 } OLGraph;
B A D C 0 1 2 3
四、无向图的邻接多重表存储表示 边的结构表示 typedef struct Ebox { VisitIf mark; // 访问标记 int ivex, jvex; //该边依附的两个顶点的位置 struct EBox *ilink, *jlink; InfoType *info; // 该边信息指针 } EBox;
顶点的结构表示 typedef struct VexBox { VertexType data; EBox *firstedge; // 指向第一条依附该顶点的边 } VexBox; 无向图的结构表示 typedef struct { // 邻接多重表 VexBox adjmulist[MAX_VERTEX_NUM]; int vexnum, edgenum; } AMLGraph;
B A C D E 0 1 2 3 4
7.3 图的遍历 从图中某个顶点出发游历图,访遍 图中其余顶点,并且使图中的每个顶点 仅被访问一次的过程。 深度优先搜索 广度优先搜索 遍历应用举例
一、深度优先搜索遍历图 连通图的深度优先搜索遍历 从图中某个顶点V0 出发,访问此顶点,然后依次从V0的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和V0有路径相通的顶点都被访问到。
W1、W2和W3均为 V 的邻接点,SG1、SG2 和 SG3 分别为含顶点W1、W2和W3的子图。 V w1 w3 w2 w2 SG3 SG1 SG2 访问顶点 V : for (W1、W2、W3 ) 若该邻接点W未被访问, 则从它出发进行深度优先搜索遍历。
从上页的图解可见: 1. 从深度优先搜索遍历连通图的过程类似于树的先根遍历; 2. 如何判别V的邻接点是否被访问? 解决的办法是:为每个顶点设立一个 “访问标志 visited[w]”。
例如: 0 a a 2 3 4 5 c c d d e e f f b g g b 1 6 0 1 2 3 4 5 6 访问标志: F F F F F F F T T T T T T T 访问次序: a c b d g f e
void DFS(Graph G, int v) { // 从顶点v出发,深度优先搜索遍历连通图 G visited[v] = TRUE; VisitFunc(v); for(w=FirstAdjVex(G, v); w!=0; w=NextAdjVex(G,v,w)) if (!visited[w]) DFS(G, w); // 对v的尚未访问的邻接顶点w // 递归调用DFS } // DFS
非连通图的深度优先搜索遍历 首先将图中每个顶点的访问标志设为 FALSE, 之后搜索图中每个顶点,如果未被访问,则以该顶点为起始点,进行深度优先搜索遍历,否则继续检查下一顶点。
例如: 0 1 6 a a b b g g 2 3 4 5 c c d d e e f f h h k k 7 8 0 1 2 3 4 5 6 7 8 访问标志: F F F F F F F F F T T T T T T T T T 访问次序: a c h d k f e b g
void DFSTraverse(Graph G, Status (*Visit)(int v)) { // 对图 G 作深度优先遍历。 VisitFunc = Visit; for (v=0; v<G.vexnum; ++v) visited[v] = FALSE; // 访问标志数组初始化 for (v=0; v<G.vexnum; ++v) if (!visited[v]) DFS(G, v); // 对尚未访问的顶点调用DFS }
二、广度优先搜索遍历图 对连通图,从起始点V到其余各顶点必定存在路径。 w2 w2 w1 w1 其中,V->w1, V->w2, V->w8 的路径长度为1; V V w3 w3 w7 w7 V->w7, V->w3, V->w5 的路径长度为2; w8 w8 w4 w4 w6 w6 V->w6, V->w4 的路径长度为3。 w5 w5
从图中的某个顶点V0出发,并在访问此顶点之后依次访问V0的所有未被访问过的邻接点,之后分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问,直至图中所有和V0有路径相通的顶点都被访问到。从图中的某个顶点V0出发,并在访问此顶点之后依次访问V0的所有未被访问过的邻接点,之后分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问,直至图中所有和V0有路径相通的顶点都被访问到。 若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。
0 1 2 3 4 5 6 7 8 T T T T T T T T T F F F F F F F F F Visited Q w2 w2 w1 V w2 w8 w7 w3 w5 w6 w4 w1 w1 V V w3 w3 访问次序: w7 w7 w8 w8 w4 w4 w6 w6 w5 w5
void BFSTraverse(Graph G, Status (*Visit)(int v)){ for (v=0; v<G.vexnum; ++v) visited[v] = FALSE; //初始化访问标志 InitQueue(Q); // 置空的辅助队列Q for ( v=0; v<G.vexnum; ++v ) if (!visited[v]) { // v 尚未访问 } } // BFSTraverse
visited[u] = TRUE; Visit(u); // 访问u EnQueue(Q, v);// v入队列 while (!QueueEmpty(Q)) { DeQueue(Q, u); // 队头元素出队并置为u for(w=FirstAdjVex(G, u); w!=0; w=NextAdjVex(G,u,w)) if ( ! visited[w]){ visited[w]=TRUE; Visit(w); EnQueue(Q, w);// 访问的顶点w入队列 } // if } // while