第7章 图 图(Graph...

Info icon This preview shows page 1. Sign up to view the full content.

View Full Document Right Arrow Icon
This is the end of the preview. Sign up to access the rest of the document.

Unformatted text preview: 第7章 图 图 (Graph) 是一种比线性表和树更为复杂的 数据结构。 线性结构 : 是研究数据元素之间的一对一关 系。在这种结构中,除第一个和最后一个元素外,任 何一个元素都有唯一的一个直接前驱和直接后继。 树结构 : 是研究数据元素之间的一对多的关 系。在这种结构中,每个元素对下 ( 层 ) 可以有 0 个 或多个元素相联系,对上 ( 层 ) 只有唯一的一个元素 相关,数据元素之间有明显的层次关系。 图结构 : 是研究数据元素之间的多对多的关系。 在这种结构中,任意两个元素之间可能存在关系。即 结点之间的关系可以是任意的,图中任意元素之间都 可能相关。 图的应用极为广泛,已渗入到诸如语言学、逻 辑学、物理、化学、电讯、计算机科学以及数学的其 它分支。 7.1 图的基本概念 7.1.1 图的定义和术语 一个图 (G) 定义为一个偶对 (V,E) ,记为 G=(V,E) 。其中: V 是 顶点 (Vertex) 的非空有限集合,记为 V(G) ; E 是无序集 V&V 的一个子集,记为 E(G) ,其 元素是图的 弧 (Arc) 。 将顶点集合为空的图称为空图。其形式化定义为: G=(V , E) V={v|v data object} E={<v,w>| v,w V∧p(v,w)} P(v,w) 表示从顶点 v 到顶点 w 有一条直接通路。 弧 (Arc) : 表示两个顶点 v 和 w 之间存在一个关系 ,用顶点偶对 <v,w> 表示。通常根据图的顶点偶对将图分为 有向图和无向图。 有向图 (Digraph) : 若图 G 的关系集合 E(G) 中, 顶点偶对 <v,w> 的 v 和 w 之间是 有序 的,称图 G 是有向图 。 在有向图中,若 <v,w> E(G) ,表示从顶点 v 到顶点 w 有一条 弧 。 其中: v 称为 弧尾 (tail) 或 始点 (initial node) , w 称为 弧头 (head) 或 终点 (terminal node) 。 无向图 (Undigraph) : 若图 G 的关系集合 E(G) 中,顶点偶对 <v,w> 的 v 和 w 之间是 无序 的,称图 G 是无 向图。 在无向图中,若 <v,w> E(G) ,有 <w,v> E(G) ,即 E(G) 是对称,则用无序对 (v,w) 表示 v 和 w 之间的 一条 边 (Edge) ,因此 (v,w) 和 (w,v) 代表的是同一条边 。 例 1 :设有有向图 G1 和无向图 G2 ,形式化定义分别是: G1=(V1 , E1) V1={a,b,c,d,e} E1={<a,b>,<a,c>, <a,e>,<c,d>,<c,e> ,<d,a>,<d,b>,<e,d>} G2=(V2 , E2) V2={a,b,c,d} E2={(a,b), (a,c), (a,d), (b,d), (b,c), (c,d)} 它们所对应的图如图 7-1 所示。 a b a d d b c e c (a) 有向图 G1 (b) 无向图 G2 图 7-1 图的示例 完全无向图: 对于无向图,若图中顶点数为 n ,用 e 表示边的数目,则 e [0 , n(n-1)/2] 。具有 n(n-1)/2 条边的无向图称为完全无向图。 完全无向图另外的定义是: 对于无向图 G=(V , E) ,若 vi , vj V ,当 vi≠vj 时,有 (vi ,vj)E ,即图中任意两个不同的顶点间 都有一条无向边,这样的无向图称为完全无向图。 完全有向图 : 对于有向图,若图中顶点数为 n , 用 e 表示弧的数目,则 e [0 , n(n-1)] 。具有 n(n1) 条边的有向图称为完全有向图。 完全有向图另外的定义是: 对于有向图 G=(V , E) ,若 vi , vj V ,当 vi ≠vj 时,有 <vi ,vj> E∧<vj , vi > E ,即 图中任意两 个不同的顶点间都有一条弧 ,这样的有向图称为 完全有向 图。 有很少边或弧的图( e<n ㏒ n )的图称为 稀疏图 ,反之称为 稠密图 。 权 (Weight) : 与图的边和弧相关的数。权可以表示 从一个顶点到另一个顶点的距离或耗费。 子图和生成子图 : 设有图 G=(V , E) 和 G’=(V’ , E’) ,若 V’ V 且 E’ E ,则称图 G’ 是 G 的 子图 ;若 V’=V 且 E’ E ,则称图 G’ 是 G 的一个 生成子图 。 顶点的邻接 (Adjacent) : 对于无向图 G=(V , E) ,若边 (v,w) E ,则称顶点 v 和 w 互为 邻接 点 ,即 v 和 w 相邻接。边 (v,w) 依附 (incident) 与顶点 v 和w 对于有向图 G=(V , E) ,若有向弧 <v,w> E , 则称顶点 v “ 邻接到 ”顶点 w ,顶点 w “ 邻接自 ”顶点 v , 弧 <v,w> 与顶点 v 和 w “ 相关联 ” 。 顶点的度、入度、出度 : 对于无向图 G=(V , E) , vi V ,图 G 中 依附 于 vi 的边的数目称为 顶点 vi 的 度 (degree) ,记为 TD(vi) 。 即 显然,在无向图中,所有顶点度的和是图中边的 2 倍。 ∑ TD(vi)=2e i=1, 2, …, n , e 为图的边数。 对有向图 G=(V , E) ,若 vi V ,图 G 中 以 vi 作 为起点 的有向边 ( 弧 ) 的数目称为顶点 vi 的 出度 (Outdegree) ,记为 OD(vi) ; 以 vi 作为终点 的有向边 ( 弧 ) 的数目称为顶点 vi 的 入度 (Indegree) ,记为 ID(vi) 。顶点 vi 的 出度 与 入度 之和称为 vi 的 度 ,记为 TD(vi) 。 即 TD(vi)=OD(vi)+ID(vi) 路径 (Path) 、路径长度、回路 (Cycle) : 对无向图 G=(V , E) ,若从顶点 vi 经过若干条边能到达 vj ,称顶点 vi 和 vj 是 连通 的,又称顶点 vi 到 vj 有 路径 。 对有向图 G=(V , E) ,从顶点 vi 到 vj 有 有向路径 , 指的是从顶点 vi 经过若干条有向边 ( 弧 ) 能到达 vj 。 或 路径 是图 G 中连接两顶点之间所经过的顶点序列。 即 Path=vi0vi1…vim , vij V 且 (vij-1, vij) E j=1,2, …,m 或 Path=vi0vi1 …vim , vij V 且 <vij-1, vij> E j=1,2, …,m 路径上边或有向边 ( 弧 ) 的数目称为该 路径 的 长度 。 在一条路径中,若 没有重复相同 的顶点,该路径称 为 简单路径 ;第一个顶点和最后一个顶点相同的路径称为 回路 ( 环 ) ;在一个回路中,若除第一个与最后一个顶点 外,其余顶点不重复出现的回路称为 简单回路 ( 简单环 ) 。 连通图、图的连通分量 : 对无向图 G=(V , E) ,若 vi , vj V , vi 和 vj 都是连通的,则称图 G 是 连 通图 ,否则称为 非连通图 。若 G 是非连通图,则 极大的连 通子图 称为 G 的 连通分量 。 对有向图 G=(V , E) ,若 vi , vj V ,都有 以 vi 为起点 , vj 为终点 以及以 vj 为起点, vi 为终点的有向 路径,称图 G 是 强连通图 ,否则称为 非强连通图 。若 G 是非强连通图,则 极大的强连通子图 称为 G 的 强连通分量 。 “ 极大 ”的含义:指的是对子图再增加图 G 中的其它 顶点,子图就不再连通。 生成树、生成森林 : 一个连通图 ( 无向图 ) 的生成树是一个极小连通子图,它 含有图中全部 n 个 顶点 和只有足以构成一棵树的 n-1 条边 ,称为图的 生 成树 ,如图 7-2 所示。 关于无向图的生成树的几个结论: ◆ 一棵有 n 个顶点的生成树有且仅有 n-1 条边; ◆ 如果一个图有 n 个顶点和小于 n-1 条边,则是 非连通图; ◆ 如果多于 n-1 条边,则一 定有环; ◆ 有 n-1 条边的图不一定是 生成树。 a d b c 图 7-2 图 G2 的一棵生成树 有向图的生成森林是这样一个子图,由若干棵 有向树组成,含有图中全部顶点。 有向树是只有一个顶点的入度为 0 ,其余顶点的入度 均为 1 的有向图,如图 7-3 所示。 网: 每个边 ( 或弧 ) 都附加一个权值的图,称 为带权图。带权的连通图 ( 包括弱连通的有向图 ) 称 为网或网络。网络是工程上常用的一个概念,用来表 示一个工程或某种流程,如图 7-4 所示。 a a b d c e c d c (a) 有向图 b c e b (b) 生成森林 图 7-3 有向图及其生成森林 a 6 3 2 c 1 b 3 4 5 e d 图 7-4 带权有向图 7.1.2 图的抽象数据类型定 义 图是一种数据结构,加上一组基本操作就构成 了图的抽象数据类型。 图的抽象数据类型定义如下: ADT Graph{ 数据对象 V :具有相同特性的数据元素的集合, 称为顶点集。 数据关系 R : R={VR} VR={<v,w>|<v,w>| v,wV∧p(v,w) , <v,w> 表示 从 v到w的 弧, P(v,w) 定义弧 <v,w> 的信息 } 基本操作 P : Create_Graph() : 图的创建操作。 初始条件:无。 操作结果:生成一个没有顶点的空图 G 。 GetVex(G, v) : 求图中的顶点 v 的值。 初始条件:图 G 存在, v 是图中的一个顶点。 操作结果:生成一个没有顶点的空图 G 。 …… DFStraver(G,V) :从 v 出发对图 G 深度优先遍历 。 初始条件:图 G 存在。 操作结果:对图 G 深度优先遍历,每个顶点 ⋯⋯ BFStraver(G,V) :从 v 出发对图 G 广度优先遍历 。 初始条件:图 G 存在。 操作结果:对图 G 广度优先遍历,每个顶点 访问且只访问一次。 } ADT Graph 详见 p156~157 。 7.2 图的存储结构 图的存储结构比较复杂,其复杂性主要表现在: ◆ 任意顶点之间可能存在联系,无法以数据元素 在存储区中的物理位置来表示元素之间的关系。 ◆ 图中顶点的度不一样,有的可能相差很大,若 按度数最大的顶点设计结构,则会浪费很多存储 单元,反之按每个顶点自己的度设计不同的结构 ,又会影响操作。 图的常用的存储结构有: 邻接矩阵 、 邻接链表 、 十字链表 、 邻接多重表 和 边表 。 7.2.1 邻接矩阵 ( 数组 ) 表示法 基本思想 : 对于有 n 个顶点的图,用一维数组 vexs[n] 存储顶点信息,用二维数组 A[n][n] 存储顶点之 间关系的信息。该二维数组称为 邻接矩阵 。在邻接矩阵中 ,以顶点在 vexs 数组中的下标代表顶点,邻接矩阵中的 元素 A[i][j] 存放的是顶点 i 到顶点 j 之间关系的信息。 1 无向图的数组表示 (1) 无权图的邻接矩阵 无向无权图 G=(V , E) 有 n(n≧1) 个顶点,其邻接矩 阵是 n 阶对称方阵,如图 7-5 所示。其元素的定义如下: 1 若 (vi , vj)E ,即 vi , vj 邻接 A[i][j]= 0 若 (vi , vj)E ,即 vi , vj 不邻接 a d b c (a) 无向图 vexs a b c d (b) 顶点矩阵 0 1 1 1 1 0 1 1 1 1 0 1 1 1 1 0 (c) 邻接矩阵 图 7-5 无向无权图的数组存储 (2) 带权图的邻接矩阵 无向带权图 G=(V , E) 的邻接矩阵如图 7-6 所 示。其元素的定义如下: Wij 若 (vi , vj)E ,即 vi , vj 邻接,权值为 wij A[i][j]= ∞ 若 (vi , vj)E ,即 vi , vj 不邻接时 a 6 3 2 c 1 b 3 4 e 5 d (a) 带权无向图 vexs a b c d e ∞ 6 2 ∞ ∞ 6 2 ∞∞ ∞ 3 4 3 3 ∞ 1 ∞ 4 3 ∞ 5 3 ∞ 5 ∞ (b) 顶点矩阵 (c) 邻接矩阵 图 7-6 无向带权图的数组存储 (3) 无向图邻接矩阵的特性 ◆ 邻接矩阵是对称方阵; ◆ 对于顶点 vi ,其度数是第 i 行的非 0 元素的个 数; ◆ 无向图的边数是上 ( 或下 ) 三角形矩阵中非 0 元素个数。 2 有向图的数组表示 (1) 无权图的邻接矩阵 若有向无权图 G=(V , E) 有 n(n≧1) 个顶点,则 其邻接矩阵是 阶对称方阵,如图 所示。元素定 1 n若 <vi, vj>E ,从 v7-7 i 到 vj 有弧 A[i][j]= 义如下: 0 若 <vi , vj>E 从 vi 到 vj 没有弧 a b e d c (a) 有向图 图 7-7 vexs a b c d e (b) 顶点矩阵 0 0 0 1 0 1 0 0 1 0 1 0 0 0 0 0 1 0 0 1 1 0 0 1 0 (c) 邻接矩阵 有向无权图的数组存储 (2) 带权图的邻接矩阵 有向带权图 G=(V , E) 的邻接矩阵如图 7-8 所示。 其元素的定义如下: A[i][j]= wij 若 <vi,vj>E ,即 vi , vj 邻接,权值为 wij ∞ 若 <vi,vj>E ,即 vi , vj 不邻接时 a 6 3 2 c 1 b 3 4 5 d (a) 带权有向图 e vexs a b c d e (b) 顶点矩阵 ∞6 2 ∞∞ ∞∞∞ ∞ 3 ∞ 3 ∞ 1 ∞ ∞4 ∞∞ 5 ∞∞∞∞∞ (c) 邻接矩阵 图 7-8 带权有向图的数组存储 ⑶ 有向图邻接矩阵的特性 ◆ 对于顶点 vi ,第 i 行的非 0 元素的个数是其出 度 OD(vi) ;第 i 列的非 0 元素的个数是其入度 ID(vi) 。 ◆ 邻接矩阵中非 0 元素的个数就是图的弧的数目 。 3 图的邻接矩阵的操作 图的邻接矩阵的实现比较容易,定义两个数组 分别存储顶点信息 ( 数据元素 ) 和边或弧的信息 ( 数 据元素之间的关系 ) 。其存储结构形式定义如下: #define INFINITY MAX_VAL /* 最大值∞ */ /* 根据图的权值类型,分别定义为最大整数或实数 */ #define MAX_VEX 30 /* 最大顶点数目 */ typedef enum {DG, AG, WDG,WAG} GraphKind ; /* { 有向图,无向图,带权有向图,带权无向图 } */ typedef struct ArcType { VexType vex1, vex2 ; /* 弧或边所依附的两个顶点 */ ArcValType ArcVal ; /* 弧或边的权值 */ ArcInfoType ArcInfo ; /* 弧或边的其它信息 */ }ArcType ; /* 弧或边的结构定义 */ typedef struct { GraphKind kind ; /* 图的种类标志 */ int vexnum , arcnum ; /* 图的当前顶点数和弧数 */ VexType vexs[MAX_VEX] ; /* 顶点向量 */ AdjType adj[MAX_VEX][MAX_VEX]; }MGraph ; /* 图的结构定义 */ 利用上述定义的数据结构,可以方便地实现图 的各种操作。 (1) 图的创建 AdjGraph *Create_Graph(MGraph * G) { printf(“ 请输入图的种类标志:” ) ; scanf(“%d”, &G->kind) ; G->vexnum=0 ; return(G) ; } /* 初始化顶点个数 */ (2) 图的顶点定位 图的顶点定位操作实际上是确定一个顶点在 vexs 数组中的位置 ( 下标 ) ,其过程完全等同于在顺 序存储的线性表中查找一个数据元素。 算法实现: int LocateVex(MGraph *G , VexType *vp) { int k ; for (k=0 ; k<G->vexnum ; k++) if (G->vexs[k]==*vp) return(k) ; return(-1) ; } /* 图中无此顶点 */ (3) 向图中增加顶点 向图中增加一个顶点的操作,类似在顺序存储 的线性表的末尾增加一个数据元素。 算法实现: int AddVertex(MGraph *G , VexType *vp) { int k , j ; if (G->vexnum>=MAX_VEX) { printf(“Vertex Overflow !\n”) ; return(-1) ; } if (LocateVex(G , vp)!=-1) { printf(“Vertex has existed !\n”) ; return(-1) ; } k=G->vexnum ; G->vexs[G->vexnum++]=*vp ; if (G->kind==DG||G->kind==AG) for (j=0 ; j<G->vexnum ; j++) G->adj[j][k].ArcVal=G->adj[k][j].ArcVal=0 ; /* 是不带权的有向图或无向图 */ else for (j=0 ; j<G->vexnum ; j++) { G->adj[j][k].ArcVal=INFINITY ; G->adj[k][j].ArcVal=INFINITY ; /* 是带权的有向图或无向图 */ } return(k) ; } (4) 向图中增加一条弧 根据给定的弧或边所依附的顶点,修改邻接矩 阵中所对应的数组元素。 算法实现: int AddArc(MGraph *G , ArcType *arc) { int k , j ; k=LocateVex(G , &arc->vex1) ; j=LocateVex(G , &arc->vex1) ; if (k==-1||j==-1) { printf(“Arc’s Vertex do not existed !\n”) ; return(-1) ; } if (G->kind==DG||G->kind==WDG) { G->adj[k][j].ArcVal=arc->ArcVal; G->adj[k][j].ArcInfo=arc->ArcInfo ; /* 是有向图或带权的有向图 */ } else { G->adj[k][j].ArcVal=arc->ArcVal ; G->adj[j][k].ArcVal=arc->ArcVal ; G->adj[k][j].ArcInfo=arc->ArcInfo ; G->adj[j][k].ArcInfo=arc->ArcInfo ; /* 是无向图或带权的无向图 , 需对称赋值 */ } return(1) ; } 7.2.2 邻接链表法 基本思想: 对图的每个顶点建立一个单链表, 存储该顶点所有邻接顶点及其相关信息。每一个单链 表设一个表头结点。 第 i 个单链表表示依附于顶点 Vi 的边 ( 对有向 图是以顶点 Vi 为头或尾的弧 ) 。 1 结点结构与邻接链表示例 链表中的结点称为 表结点 ,每个结点由三个域组成 ,如图 7-9(a) 所示。其中邻接点域 (adjvex) 指示与顶 点 Vi 邻接的顶点在图中的位置 ( 顶点编号 ) ,链域 (nextarc) 指向下一个与顶点 Vi 邻接的表结点,数据域 (info) 存储和边或弧相关的信息,如权值等。对于无权图 ,如果没有与边相关的其他信息,可省略此域。 每个链表设一个表头结点 ( 称为 顶点结点 ) ,由两 个域组成,如图 7-9(b) 所示。链域 (firstarc) 指向链表 中的第一个结点,数据域 (data) 存储顶点名或其他信息 。 表结点: adjvex info nextarc 图 7-9 顶点结点: 邻接链表结点结构 data firstarc 在图的邻接链表表示中,所有 顶点结点 用一个向 量 以顺序结构形式存储,可以随机访问任意顶点的链表 ,该向量称为 表头向量 ,向量的下标指示顶点的序号。 用邻接链表存储图时,对无向图,其邻接链表是 唯一的,如图 7-10 所示;对有向图,其邻接链表有两 种形式,如图 7-11 所示。 v1 v2 v4 v5 v3 0 1 2 3 4 v1 v2 v3 v4 v5 ┇┇ 1 0 0 0 2 MAX_VEX-1 图 7-10 无向图及其邻接链表 2 2 ⋀ 1 2 3 ⋀ 3 ⋀ 3 4 ⋀ 4 ⋀ 0 1 2 3 4 v4 v1 v5 v3 v2 (a) 有向图 v1 2 v2 0 ⋀ v3 3 v4 1 v5 1 ┇┇┇ 1 3 ⋀ 0 1 2 ⋀ 3 ⋀ MAX_VEX-1 0 1 2 3 4 v1 1 v2 2 v3 1 v4 2 v5 1 ┇┇┇ (b) 正邻接链表,出度直观 2 ⋀ 0 2 ⋀ 3 ⋀ 0 4 ⋀ 2 ⋀ MAX_VEX-1 (c) 逆邻接链表,入度直观 图 7-11 有向图及其邻接链表 4 ⋀ 2 邻接表法的特点 ◆ 表头向量中每个分量就是一个单链表的头结点, 分量个数就是图中的顶点数目; ◆ 在边或弧稀疏的条件下,用邻接表表示比用邻接 矩阵表示节省存储空间; ◆ 在无向图,顶点 Vi 的度是第 i 个链表的结点数; ◆ 对 有向图 可以建立 正邻接表 或 逆邻接表 。正邻接 表是以顶点 Vi 为出度 ( 即为弧的起点 ) 而建立的邻接 表;逆邻接表是以顶点 Vi 为入度 ( 即为弧的终点 ) 而 建立的邻接表; ◆ 在有向图中,第 i 个链表中的结点数是顶点 Vi 的 出 ( 或入 ) 度;求入 ( 或出 ) 度,须遍历整个邻接表 ; ◆ 在邻接表上容易找出任一顶点的第一个邻接点和下一 个邻接点; 3 结点及其类型定义 #define MAX_VEX 30 /* 最大顶点数 * / typedef int InfoType; typedef enum {DG, AG, WDG,WAG} GraphKind ; typedef struct LinkNode { int adjvex ; // 邻接点在头结点数组中的位置 ( 下 标) InfoType info ; // 与边或弧相关的信息 , 如权值 struct LinkNode *nextarc ; // 指向下一个表结点 }LinkNode ; /* 表结点类型定义 */ typedef struct VexNode { VexType data; // 顶点信息 int indegree ; // 顶点的度 , 有向图是入度或出度或没 有 LinkNode *firstarc ; // 指向第一个表结点 }VexNode ; /* 顶点结点类型定义 * / typedef struct ArcType { VexType vex1, vex2 ; /* 弧或边所依附的两个顶点 */ InfoType }ArcType ; info ; // 与边或弧相关的信息 , 如权值 /* 弧或边的结构定义 * / typedef struct { GraphKind kind ; /* 图的种类标志 int vexnum ; VexNode AdjList[MAX_VEX] ; }ALGraph ; /* 图的结构定义 * / */ 利用上述的存储结构描述,可方便地实现图的 基本操作。 (1) 图的创建 ALGraph *Create_Graph(ALGraph * G) { printf(“ 请输入图的种类标志:” ) ; scanf(“%d”, &G->kind) ; G->vexnum=0 ; /* 初始化顶点个数 * / return(G) ; } (2) 图的顶点定位 图的顶点定位实际上是确定一个顶点在 AdjList 数组中的某个元素的 data 域内容。 算法实现: int LocateVex(ALGraph *G , VexType *vp) { int k ; for (k=0 ; k<G->vexnum ; k++) if (G->AdjList[k].data==*vp) return(k) ; return(-1) ; } /* 图中无此顶点 */ (3) 向图中增加顶点 向图中增加一个顶点的操作,在 AdjList 数组的 末尾增加一个数据元素。 算法实现: int AddVertex(ALGraph *G , VexType *vp) { int k , j ; if (G->vexnum>=MAX_VEX) { printf(“Vertex Overflow !\n”) ; return(-1) ; } if (LocateVex(G , vp)!=-1) { printf(“Vertex has existed !\n”) ; return(-1) ; } G->AdjList[G->vexnum].data=*vp ; G->AdjList[G->vexnum].degree=0 ; G->AdjList[G->vexnum].firstarc=NULL ; k=++G->vexnum ; return(k) ; } (4) 向图中增加一条弧 根据给定的弧或边所依附的顶点,修改单链表: 无向图修改两个单链表;有向图修改一个单链表。 算法实现: int AddArc(ALGraph *G , ArcType *arc) { int k , j ; LinkNode *p ,*q ; k=LocateVex(G , &arc->vex1) ; j=LocateVex(G , &arc->vex2) ; if (k==-1||j==-1) { printf(“Arc’s Vertex do not existed !\n”) ; return(-1) ; } p=(LinkNode *)malloc(sizeof(LinkNode)) ; p->adjvex=arc->vex1 ; p->info=arc->info ; p->nextarc=NULL ; /* 边的起始表结点赋值 */ q=(LinkNode *)malloc(sizeof(LinkNode)) ; q->adjvex=arc->vex2 ; q->info=arc->info ; q->nextarc=NULL ; /* 边的末尾表结点赋值 */ if (G->kind==AG||G->kind==WAG) { q->nextarc=G->adjlist[k].firstarc ; G->adjlist[k].firstarc=q ; p->nextarc=G->adjlist[j].firstarc ; G->adjlist[j].firstarc=p ; } /* 是无向图 , 用头插入法插入到两个单链表 */ else /* 建立有向图的邻接链表 , 用头插入法 */ { q->nextarc=G->adjlist[k].firstarc ; G->adjlist[k].firstarc=q ; /* 建立正邻接链表用 */ //q->nextarc=G->adjlist[j].firstarc ; //G->adjlist[j].firstarc=q ; /* 建立逆邻接链表用 */ } 7.2.3 十字链表法 十字链表 (Orthogonal List) 是有向图 的另一种链式存储结构,是将有向图的正邻接表和逆 邻接表结合起来得到的一种链表。 在这种结构中,每条弧的弧头结点和弧尾结点 都存放在链表中,并将 弧结点 分别组织到 以弧尾结点 为头 ( 顶点 ) 结点 和 以弧头结点为头 ( 顶点 ) 结点 的 链表中。这种结构的结点逻辑结构如图 7-12 所示。 顶点结 Data点firstin firstout 图 7-12 弧结点 tailvex headvex info hlink tlink 十字链表结点结构 ◆ data 域:存储和顶点相关的信息; ◆ 指针域 firstin :指向以该顶点为弧头的第一条 弧所对应的弧结点; ◆ 指针域 firstout :指向以该顶点为弧尾的第一 条弧所对应的弧结点; ◆ 尾域 tailvex :指示弧尾顶点在图中的位置; ◆ 头域 headvex :指示弧头顶点在图中的位置; ◆ 指针域 hlink :指向弧头相同的下一条弧; ◆ 指针域 tlink :指向弧尾相同的下一条弧; ◆ Info 域:指向该弧的相关信息; 结点类型定义 #define INFINITY MAX_VAL /* 最大值∞ * / #define MAX_VEX 30 // 最大顶点数 typedef struct ArcNode { int tailvex , headvex ; // 尾结点和头结点在 图中的位置 InfoType info ; // 与弧相关的信息 , 如权值 struct ArcNode *hlink , *tlink ; }ArcNode ; /* 弧结点类型定义 * / typedef struct VexNode { VexType data; // 顶点信息 ArcNode *firstin , *firstout ; }VexNode ; /* 顶点结点类型定义 * / typedef struct { int vexnum ; VexNode xlist[MAX_VEX] ; }OLGraph ; /* 图的类型定义 * / 图 7-13 所示是一个有向图及其十字链表 ( 略去 了表结点的 info 域 ) 。 从这种存储结构图可以看出,从一个顶点结点 的 firstout 出发,沿表结点的 tlink 指针构成了正邻 接表的链表结构,而从一个顶点结点的 firstin 出发, 沿表结点的 hlink 指针构成了逆邻接表的链表结构。 V0 V1 1 V1 V2 V3 ∧ ∧ 2 V2 2 0 3 V3 3 0∧ 图 7-13 0 2 0 1 0 V0 2 3 ∧∧ 3 1∧ 有向图的十字链表结构 3 2 ∧∧ 7.2.4 邻接多重表 邻接多重表 (Adjacency Multilist) 是无 向图的另一种链式存储结构。 邻接表是无向图的一种有效的存储结构,在无向 图的邻接表中,一条边 (v,w) 的两个表结点分别初选 在以 v 和 w 为头结点的链表中,很容易求得顶点和边 的信息,但在涉及到边的操作会带来不便。 邻接多重表的结构和十字链表类似, 每条边用一 个结点表示 ;邻接多重表中的顶点结点结构与邻接表中 的完全相同,而表结点包括六个域如图 7-14 所示。 顶点结点 表结点 data firstedge mark ivex jvex info ilink jlink 图 7-14 邻接多重表的结点结构 ◆ Data 域:存储和顶点相关的信息; ◆ 指针域 firstedge :指向依附于该顶点的第一条 边所对应的表结点; ◆ 标志域 mark :用以标识该条边是否被访问过 ; ◆ ivex 和 jvex 域:分别保存该边所依附的两个顶 点在图中的位置; ◆ info 域:保存该边的相关信息; ◆ 指针域 ilink :指向下一条依附于顶点 ivex 的 边; ◆ 指针域 jlink :指向下一条依附于顶点 jvex 的 边; 结点类型定义 #define INFINITY MAX_VAL /* 最大值∞ * / #define MAX_VEX 30 /* 最大顶点数 * / typedef emnu {unvisited , visited} Visitting ; typedef struct EdgeNode { Visitting mark ; // 访标记 int ivex , jvex ; // 该边依附的两个结点在图中的位 置 InfoType info ; // 与边相关的信息 , 如权值 struct EdgeNode *ilink , *jlink ; // 分别指向依附于这两个顶点的下一条边 }EdgeNode ; /* 弧边结点类型定义 */ typedef struct VexNode { VexType data; // 顶点信息 ArcNode *firsedge ; // 指向依附于该顶点的 第一条边 }VexNode ; /* 顶点结点类型定义 */ typedef struct { int vexnum ; VexNode mullist[MAX_VEX] ; }AMGraph ; 图 7-15 所示是一个无向图及其邻接多重表。 邻接多重表与邻接表的区别 : 后者的同一条边用两个表结点表示,而前者只用一 个表结点表示 ; 除标志域外,邻接多重表与邻接表表达的 信息是相同的,因此,操作的实现也基本相似。 v1 v4 v2 v3 0 1 2 3 v1 v2 v3 v4 0 1 0 2∧ 2 1∧ 2 3 图 7-15 无向图及其多重邻接链表 0∧ 3∧ 7.2.5 图的边表存储结 构 在某些应用中,有时主要考察图中各个边的权值 以及所依附的两个顶点,即 图的结构主要由边来表示 , 称为 边表存储结构 。 在边表结构中,边采用顺序存储,每个边元素由 三部分组成 :边所依附的 两个顶点和边的权值 ; 图的顶 点用另一个顺序结构的顶点表存储。如图 7-16 所示。 边表存储结构的形式描述如下 : #define INFINITY MAX_VAL /* 最大值∞ * / #define MAX_VEX 30 /* 最大顶点数 * / #define MAX_EDGE 100 /* 最大边数 * / typedef struct ENode { int ivex , jvex ; /* 边所依附的两个顶点 * / WeightType weight ; }ENode ; /* 边表元素类型定义 /* 边的权值 */ */ typedef struct { int vexnum , edgenum ; /* 顶点数和边数 */ VexType vexlist[MAX_VEX] ; /* 顶点表 * / ENode edgelist[MAX_EDGE] ; }ELGraph ; /* 边表 */ 6 v0 7 顶点表 边 表 0 0 v1 9 8 v2 1 3 2 1 v3 4 v4 2 2 图 7-16 无向图的边表表示 3 0 1 2 3 4 v0 v1 v2 v3 v4 1 2 3 4 3 4 4 6 7 2 9 8 3 4 7.3 图的遍历 图的遍历 (Travering Graph) : 从图的某 一顶点出发,访遍图中的其余顶点,且每个顶点仅被 访问一次。图的遍历算法是各种图的操作的基础。 ◆ 复杂性: 图的任意顶点可能和其余的顶点相邻 接,可能在访问了某个顶点后,沿某条路径搜索 后又回到原顶点。 ◆ 解决办法: 在遍历过程中记下已被访问过的顶 点。设置一个辅助向量 Visited[1…n](n 为顶点 数 ) ,其初值为 0 ,一旦访问了顶点 vi 后,使 Visited[i] 为 1 或为访问的次序号 。 图的遍历算法有 深度优先搜索算法 和 广度优先搜 索算法 。采用的数据结构是 ( 正 ) 邻接链表 。 7.3.1 深度优先搜索算 深度优先搜索 (D法 epth First Search--DFS) 遍历类似 树的先序遍历 ,是 树的先序遍历的推广 。 1 算法思想 设初始状态时图中的所有顶点未被访问,则: ⑴ : 从图中某个顶点 vi 出发 ,访问 vi ;然后找到 vi 的 一个邻接顶点 vi1 ; ⑵ :从 vi1 出发,深度优先搜索访问和 vi1 相 邻接且 未被访问的所有顶点; ⑶ :转⑴ ,直到和 vi 相 邻接的所有顶点都被访问为 止 ⑷ :继续选取图中未被访问顶点 vj 作为起始顶点, 转 (1) ,直到图中所有顶点都被访问为止。 图 7-17 是无向图的深度优先搜索遍历示例 ( 红色 箭头 ) 。某种 DFS 次序是 : v1→ v3 → v2 → v4 → v5 v1 v2 v3 v4 v5 v1 v2 v3 v4 v5 ┇┇ 0 1 2 3 4 (a) 无向图 G 2 2 0 4 ⋀ 3 ⋀ MAX_VEX-1 (b) G 的邻接链表 图 7-17 无向图深度优先搜索遍历 1 ⋀ 0 ⋀ 1 ⋀ 2 算法实现 由算法思想知,这是一个递归过程。因此,先设 计一个从某个顶点 ( 编号 ) 为 v0 开始 深度优先 搜索的 函数 ,便于调用。 在遍历整个图时,可以对图中的每一个未访问的 顶点执行所定义的函数。 typedef emnu {FALSE , TRUE} BOOLEAN ; BOOLEAN Visited[MAX_VEX] ; void DFS(ALGraph *G , int v) { LinkNode *p ; Visited[v]=TRUE ; Visit[v] ; /* 置访问标志,访问顶点 v */ p=G->AdjList[v].firstarc; /* 链表的第一个结点 */ while (p!=NULL) { if (!Visited[p->adjvex]) DFS(G, p->adjvex) ; /* 从 v 的未访问过的邻接顶点出发深度优先搜索 */ p=p->nextarc ; } } void DFS_traverse_Grapg(ALGraph *G) { int v ; for (v=0 ; v<G->vexnum ; v++) Visited[v]=FALSE ; /* 访问标志初始化 */ p=G->AdjList[v].firstarc ; for (v=0 ; v<G->vexnum ; v++) if (!Visited[v]) DFS(G , v); } 3 算法分析 遍历时,对图的每个顶点至多调用一次 DFS 函 数。其实质就是对每个顶点查找邻接顶点的过程,取 决于存储结构。当图有 e 条边,其时间复杂度为 O(e) ,总时间复杂度为 O(n+e) 。 7.3.2 广度优先搜索算 法 广度优先搜索 (B readth First Search-BFS) 遍历类似 树的按层次遍历 的过程 。 1 算法思想 设初始状态时图中的所有顶点未被访问,则: ⑴ : 从图中某个顶点 vi 出发 ,访问 vi ; ⑵ :访问 vi 的所有相 邻接且未被访问的所有顶点 vi1 , vi2 , …, vim ; ⑶ :以 vi1 , vi2 , …, vim 的次序 ,以 vij(1≦j≦m) 依此作为 vi ,转⑴; ⑷ : 继续选取图中 未被访问 顶点 vk 作为起始顶 点 ,转⑴,直到图中所有顶点都被访问为止。 图 7-18 是有向图的广度优先搜索遍历示例 ( 红色箭 头 ) 。 上述图的 BFS 次序是 : v1→ v2 → v4 → v3 → v5 v1 v2 v1 2 v2 0 ⋀ v3 3 v4 1 v5 1 ┇┇┇ 0 1 2 3 4 v4 v5 v3 (a) 有向图 G’ 1 0 1 2 ⋀ 3 ⋀ MAX_VEX-1 (b) G’ 的正邻接链表 图 7-18 有向图广度优先搜索遍历 3 ⋀ 4 ⋀ 2 算法实现 为了标记图中顶点是否被访问过,同样需要一个访 问标记数组;其次,为了依此访问与 vi 相邻接的各个顶 点 ,需要附加一个队列来保存访问 vi 的相邻接的 顶点。 typedef emnu {FALSE , TRUE} BOOLEAN ; BOOLEAN Visited[MAX_VEX] ; typedef struct Queue { int elem[MAX_VEX] ; int front , rear ; }Queue ; /* 定义一个队列保存将要访问顶点 * / void BFS_traverse_Grapg(ALGraph *G) { int k ,v , w ; LinkNode *p ; Queue *Q ; Q=(Queue *)malloc(sizeof(Queue)) ; Q->front=Q->rear=0 ; /* 建立空队列并初始化 */ for (k=0 ; k<G->vexnum ; k++) Visited[k]=FALSE ; /* 访问标志初始化 */ for (k=0 ; k<G->vexnum ; k++) { v=G->AdjList[k].data ; /* 单链表的头顶点 */ if (!Visited[v]) /* v 尚未访问 */ { Q->elem[++Q->rear]=v ; /* v 入对 */ while (Q->front!=Q->rear) { w=Q->elem[++Q->front] ; Visited[w]=TRUE ; /* 置访问标志 */ Visit(w) ; /* 访问队首元素 */ p=G->AdjList[w].firstarc ; while (p!=NULL) { if (!Visited[p->adjvex]) Q->elem[++Q->rear]=p->adjvex ; p=p->nextarc ; } } /* end while */ } /* end if */ } /* end for */ } 用广度优先搜索算法遍历图与深度优先搜索算法 遍历图的唯一区别是邻接点搜索次序不同,因此,广 度优先搜索算法遍历图的总时间复杂度为 O(n+e) 。 图的遍历可以系统地访问图中的每个顶点,因此 ,图的遍历算法是图的最基本、最重要的算法,许多 有关图的操作都是在图的遍历基础之上加以变化来实 现的。 7.4 图的连通性问题 本节所讨论的内容是图的遍历算法的具体应用。 7.4.1 无向图的连通分量与生成树 1 无向图的连通分量和生成树 对于无向图,对其进行遍历时: ◆ 若是 连通图 :仅需从图中 任一顶点出发 ,就能访 问图中的所有顶点; ◆ 若是 非连通图 :需从图中 多个顶点出发 。每次从 一个新顶点出发所访问的顶点集序列 恰好是 各个连 通分量的顶点集; 如图 7-19 所示的无向图是非连通图,按图中给定 的邻接表进行深度优先搜索遍历, 2 次调用 DFS 所得到 的顶点访问序列集是: { v1 ,v3 ,v2} 和 { v4 ,v5 } v1 v2 v3 v4 v5 (a) 无向图 G 0 1 2 3 4 v1 v2 v3 v4 v5 ┇┇ 2 2 0 4 ⋀ 3 ⋀ 1 ⋀ 0 ⋀ 1 ⋀ v1 v2 v3 v4 v5 (c) 深度优先生成森林 MAX_VEX-1 (b) G 的邻接链表 图 7-19 无向图及深度优先成森林 ⑴ 若 G=(V,E) 是无向连通图 , 顶点集和边集分别 是 V(G) , E(G) 。若从 G 中 任意点出发遍历时, E(G) 被分成两个互不相交的集合: T(G) :遍历过程中所 经过的边 的集合; B(G) :遍历过程中 未经过的边 的集合; 显然: E(G)=T(G)∪B(G) , T(G)∩B(G)=Ø 显然, 图 G’=(V, T(G)) 是 G 的极小连通子图 ,且 G’ 是一棵树 。 G’ 称为图 G 的一棵生成树 。 从任意点出发 按 DFS 算法 得到生成树 G’ 称为 深度优 先生成树 ; 按 BFS 算法 得到的 G’ 称为 广度优先生成树 。 ⑵ 若 G=(V,E) 是无向非连通图 , 对图进行遍历时得 到若干个连通分量的顶点集 : V1(G) ,V2(G) ,…,Vn(G) 和相应所经过的边集 : T1(G) ,T2(G) , …,Tn(G) 。 则对应的顶点集和边集的二元组: Gi=(Vi(G),Ti(G)) (1≦i≦n) 是对应分量的生成树 , 所有这些 生成树构成了原 来非连通图的生成森林 。 说明 : 当给定无向图要求画出其对应的生成树或生成森林 时, 必须先给出相应的邻接表,然后才能根据邻接表画出 其对应的生成树或生成森林 。 2 图的生成树和生成森林算法 对图的深度优先搜索遍历 DFS( 或 BFS) 算法稍 作修改,就可得到构造图的 DFS 生成树算法。 在算法中,树的存储结构采用孩子—兄弟表示法。 首先建立从某个顶点 V 出发,建立一个树结点,然后 再分别以 V 的邻接点为起始点,建立相应的子生成树 ,并将其作为 V 结点的子树链接到 V 结点上。显然 ,算法是一个递归算法。 算法实现: (1) DFStree 算法 typedef struct CSNode { ElemType data ; struct CSNode *firstchild , *nextsibling ; }CSNode ; CSNode *DFStree(ALGraph *G , int v) { CSNode *T , *ptr , *q ; LinkNode *p ; int w ; Visited[v]=TRUE ; T=(CSNode *)malloc(sizeof(CSNode)) ; T->data=G->AdjList[v].data ; T->firstchild=T->nextsibling=NULL ; // 建立根结点 q=NULL ; p=G->AdjList[v].firstarc ; while (p!=NULL) { w=p->adjvex ; if (!Visited[w]) { ptr=DFStree(G,w) ; /* 子树根结点 */ if (q==NULL) T->firstchild=ptr ; else q->nextsibling=ptr ; q=ptr ; } p=p->nextarc ; } return(T) ; } (2) BFStree 算法 typedef struct Queue { int elem[MAX_VEX] ; int front , rear ; }Queue ; /* 定义一个队列保存将要访问顶点 */ CSNode *BFStree(ALGraph *G ,int v) { CSNode *T , *ptr , *q ; LinkNode *p ; Queue *Q ; int w , k ; Q=(Queue *)malloc(sizeof(Queue)) ...
View Full Document

{[ snackBarMessage ]}

What students are saying

  • Left Quote Icon

    As a current student on this bumpy collegiate pathway, I stumbled upon Course Hero, where I can find study resources for nearly all my courses, get online help from tutors 24/7, and even share my old projects, papers, and lecture notes with other students.

    Student Picture

    Kiran Temple University Fox School of Business ‘17, Course Hero Intern

  • Left Quote Icon

    I cannot even describe how much Course Hero helped me this summer. It’s truly become something I can always rely on and help me. In the end, I was not only able to survive summer classes, but I was able to thrive thanks to Course Hero.

    Student Picture

    Dana University of Pennsylvania ‘17, Course Hero Intern

  • Left Quote Icon

    The ability to access any university’s resources through Course Hero proved invaluable in my case. I was behind on Tulane coursework and actually used UCLA’s materials to help me move forward and get everything together on time.

    Student Picture

    Jill Tulane University ‘16, Course Hero Intern