2.21k likes | 2.36k Views
第七章 图. 图 (Graph) 是一种比树更为复杂的非线性数据结构 . 在树形结构中 , 数据元素之间的关系是层次型的 , 树中除叶子以外的每一个数据元素可以和它下一层的多个数据元素存在关系 ; 但除根元素以外的每一个数据元素只能且必须和它上一层中的一个数据元素存在关系 . 即 一对多 的关系 . 而在图形结构中 , 数据元素之间的关系是任意的 , 图中每一个数据元素可以和任何其它数据元素相关联 , 是 多对多 的关系 . 本章主要介绍图的 存储结构 以及图的 基本操作的实现. 7.1 图的基本概念. 7.1.1 图的定义.
E N D
第七章 图 图(Graph)是一种比树更为复杂的非线性数据结构. 在树形结构中,数据元素之间的关系是层次型的, 树中除叶子以外的每一个数据元素可以和它下一层的多个数据元素存在关系; 但除根元素以外的每一个数据元素只能且必须和它上一层中的一个数据元素存在关系.即一对多的关系. 而在图形结构中,数据元素之间的关系是任意的, 图中每一个数据元素可以和任何其它数据元素相关联,是多对多的关系. 本章主要介绍图的存储结构以及图的基本操作的实现.
7.1 图的基本概念 7.1.1 图的定义 图是由数据元素的集合及数据元素间的关系集合组成的一种数据结构:Graph=(V,E),其中 : V={x|x某个数据对象}是数据元素的集合,在图中,数据元素一般被称为顶点(vertex); E={(v,w)|v,wV}或E={<v,w>|v,wV&&Path (v,w)}是数据元素之间关系的集合,也叫做边(edge)集合;Path(v,w)表示从顶点v到顶点w的一条单向通路,它是有方向的.
在图中如果顶点对(v,w)是无序的,则称此图为无向图(undirected graph). 顶点对(v,w)称为与顶点v和顶点w相关联的一条边.由于这条边没有方向,所以(v,w)与(w,v)是同一条边;
在图中如果顶点对<v,w>是有序的,则称此图为有向图(directed graph). 顶点对<v,w>称为从顶点v到顶点w的一条有向边(又称为弧), 其中v称为有向边<v,w>的始点(弧尾); w称为有向边<v,w>的终点(弧头). 显然<v,w>与<w,v>是两条不同的弧. 一般表示无向图中边的顶点对用一对圆括号括()起来; 表示有向图中弧的顶点对用一对尖括号<>括起来.
右图所示的4个图,其中G1和G2是无向图,G3和G4是有向图.右图所示的4个图,其中G1和G2是无向图,G3和G4是有向图. 在图G3和G4中,为了表示有向边,边的方向用箭头画出,箭头从有向边的始点指向有向边的终点; 在无向图中用线段表示边.
在下面对图的讨论中,对图作一些限制: 第一、图中不能有从顶点自身到自身的边(即自身环),就是说不应有形如(x,x)或<x,x>的边.如图(a)所示的带自身环的图不讨论. 第二、两个顶点v和w之间相关联的边不能多于一条.如图(b)所示的多重图也不讨论.
7.1.2 图的术语 1.完全图(complete graph): 若一个无向图有n个顶点,请问该无向图最多有多少条边? 又,若一个有向图有n个顶点,请问该有向图最多有多少条弧?
7.1.2 图的术语 2 2 1 1 3 3 完全无向图 完全有向图 1.完全图(complete graph): 在有n个顶点的无向图中,若有 n(n-1)/2条边,则称此无向图为完全无向图. 在有n个顶点的有向图中,若有n(n-1)条边,则称此图为完全有向图. 显然,在完全图中边(弧)数目达到最大.
7.1.2 图的术语 A A 9 9 15 15 11 11 B B E E 7 7 21 21 3 3 C C F F 2 2 2.权(weight): 在某些图的应用中,边(弧)上具有与它相关的系数,称之为权. 这些权可以表示从一个顶点到另一个顶点的距离、所花费的代价、所需的时间、次数等等. 这种带权图也被称为网络(network).分别有:无向网和有向网. 无向网 有向网
7.1.2 图的术语 A A 9 9 15 15 11 11 B B E E 7 7 21 21 3 3 C C F F 2 2 3.邻接点(adjacent vertex): 如果(v,w)是无向图G中的一条边,则称v与w互为邻接顶点,而且边(v,w)被称为依附于顶点v和w. 如果<v,w>是有向图G 中的一条弧,则称顶点v邻接到顶点w(也称v是w的前驱),顶点w邻接自顶点v(也称w是v的后继),弧<v,w>与顶点v与w相关联. 无向网 有向网
7.1.2 图的术语 4.子图(subgraph): 设有两个图G=(V,E)和G’=(V’,E’).若V’V且E’E,则称图G’是图G的子图. 下图(a)和(b)是无向图G1的两个子图,图(c)和(d)是有向图G3的两个子图.
7.1.2 图的术语 • 5.顶点的度(degree): • 无向图中,顶点的度是指与每个顶点相连的边数 • 有向图中,顶点的度分成入度与出度 • 入度ID(Vi) :是指以该顶点为头的弧的数目 • 出度OD(Vi) :是指以该顶点为尾的弧的数目 • 顶点的度TD(Vi)=出度OD(Vi)+入度ID(Vi) G2中顶点1的度为2 G4中顶点1的入度为2 出度为1 顶点1的度为3
例 例 7.1.2 图的术语 1 2 5 4 5 7 1 3 6 3 2 4 6 G1 G2 6.路径(path): 在图G=(V,E)中,若从顶点vi出发,沿一些边(或弧)经过一些顶点vp1,vp2,…,vpk,到达顶点vj, 则顶点序列(vi,vp1,vp2,…,vpk,vj )被称为: 从顶点vi到顶点vj的路径. 路径:1,2,3,5,6,3 路径长度:5 简单路径:1,2,3,5 回路:1,2,3,5,6,3,1 简单回路:3,5,6,3 路径:1,2,5,7,6,5,2,3 路径长度:7 简单路径:1,2,5,7,6 回路:1,2,5,7,6,5,2,1 简单回路:1,2,3,1
例 例 1 2 4 5 5 7 1 3 6 3 2 4 6 G2 G1 7.1.2 图的术语 7.路径长度(path length): 对于不带权的图,路径长度是指这条路径上边(弧)的数目. 路径:1,2,3,5,6,3 路径长度:5 简单路径:1,2,3,5 回路:1,2,3,5,6,3,1 简单回路:3,5,6,3 路径:1,2,5,7,6,5,2,3 路径长度:7 简单路径:1,2,5,7,6 回路:1,2,5,7,6,5,2,1 简单回路:1,2,3,1
A A 9 9 15 15 11 11 B B E E 7 7 21 21 3 3 C C F F 2 2 7.1.2 图的术语 7.路径长度(path length): 对于带权图,路径长度是指路径上各边(弧)的权之和. 路径:A,B,C,F 路径长度:15+3+2 路径:A,E,C,F,B 路径长度:9+21+2+7
例 例 7.1.2 图的术语 1 2 5 4 5 7 1 3 6 3 2 4 6 G1 G2 8.简单路径、回路和简单回路: 对于一路径(v1,v2,…,vm),若路径上各顶点均不相同,则称这路径为简单路径. 路径:1,2,3,5,6,3 路径长度:5 简单路径:1,2,3,5 回路:1,2,3,5,6,3,1 简单回路:3,5,6,3 路径:1,2,5,7,6,5,2,3 路径长度:7 简单路径:1,2,5,7,6 回路:1,2,5,7,6,5,2,1 简单回路:1,2,3,1
例 例 7.1.2 图的术语 1 2 4 5 5 7 1 3 6 3 2 4 6 G2 G1 8.简单路径、回路和简单回路: 若路径上第一个顶点v1和最后一个顶点vm相同,则称这样的路径为回路或环. 路径:1,2,3,5,6,3 路径长度:5 简单路径:1,2,3,5 回路:1,2,3,5,6,3,1 简单回路:3,5,6,3 路径:1,2,5,7,6,5,2,3 路径长度:7 简单路径:1,2,5,7,6 回路:1,2,5,7,6,5,2,1 简单回路:1,2,3,1
例 例 7.1.2 图的术语 1 2 4 5 5 7 1 3 6 3 2 4 6 G2 G1 8.简单路径、回路和简单回路: 若回路中除起点和终点相同外,其余顶点各不相同,则称为简单回路. 路径:1,2,3,5,6,3 路径长度:5 简单路径:1,2,3,5 回路:1,2,3,5,6,3,1 简单回路:3,5,6,3 路径:1,2,5,7,6,5,2,3 路径长度:7 简单路径:1,2,5,7,6 回路:1,2,5,7,6,5,2,1 简单回路:1,2,3,1
7.1.2 图的术语 9.连通图与连通分量: 在无向图中,若从顶点vi到顶点vj有路径,则称顶点vi与vj是连通的. 如果无向图中任意两个顶点都是连通的,则称此无向图是连通图. 非连通图的极大连通子图(包括所有连通的顶点和这些顶点依附的所有的边)叫做连通分量. 如右图(a)所示是一个非连通图,图(b)所示是相应的三个连通分量. G是一个共有28条边的非连通无向图,则该图至少有个顶点. 9
7.1.2 图的术语 10.强连通图与强连通分量(strongly connected digraph): 在有向图中,若对于顶点vi和vj,同时存在一条从vi到vj和从vj到vi的路径,则称顶点vi和顶点vj是强连通的. 如果有向图中任意两个顶点都是强连通的,则称此有向图为强连通图. 非强连通图的极大强连通子图叫做强连通分量. G3是强连通图,G4是非强连通图, G4有两个强连通分量
C 7.1.2 图的术语 B D A E F 11.生成树和生成森林: 假设一个连通图有 n 个顶点和 e 条边,其中 n-1 条边和 n 个顶点构成一个极小连通子图,称该极小连通子图为此连通图的生成树. 对非连通图,则称由各个连通分量的生成树的集合为此非连通图的生成森林.
D A A A 7.1.2 图的术语 B C B F C D E F B E E C F 11.生成树和生成森林: 如果一个有向图只有一个入度为零的顶点,且其它顶点的入度均为1,则称这个有向图为有向树. 一个有向图的生成森林—— 是由若干棵互不相交的有向树组成,含有图中全部顶点和图中的部分弧.
7.1.3 图的基本操作 图的基本操作一般有: (1)InsertVertex(vertex):在图中插入一个顶点vertex。 (2)InsertEdge(v1,v2):在图中插入一条边(v1, v2)。 (3)DeleteVertex(v):在图中删除一个顶点v及依附于v的边。 (4)DeleteEdge(v1,v2):在图中删除一条边(或弧)。
7.1.3 图的基本操作 (5)GetWeight(v1,v2):在图中取边(v1,v2)上的权。 (6)GetFirstNeighbor(int v ):在图中取顶点v的第一个邻接点。 (7)GetNextNeighbor(int v1,int v2 ):在图中取顶点v1的针对于v2的下一个邻接点(v2也是v1的一个邻接点)。 (8)Travers( ):遍历图,按某种次序依次访问图中的每一个顶点,并使每一个顶点只被访问一次。 (9)IsEmpty():判断图是否为空。
7.2 图的存储结构 由于在图中,任何两个顶点之间都可能存在联系,所以无法在存储位置上反映数据元素之间的联系,因此图没有顺序存贮结构. 按图中顶点之间的联系,图的存储结构似乎采用多重链表表示比较恰当. 但是若采用多重链表,则链表中结点的结构难以确定.
7.2 图的存储结构 1 V1 V2 ^ ^ V4 ^ V3 ^ 2 例 3 4 G1 但是若采用多重链表,则链表中结点的结构难以确定. 因为结点中的指针数若按顶点度的最大值来设置,则会浪费空间,因为有很多顶点的度小于最大值;
7.2 图的存储结构 1 V1 2 例 V4 V2 3 4 G1 V3 若顶点的指针数按每个顶点的度数来设置,则存储结构中会有很多顶点的结构不一致,给图的运算带来困难.
7.2 图的存储结构 因此,图的存储结构不宜采用多重链表. 图的存贮结构应根据具体问题的要求来设计. 常用的存贮结构有邻接矩阵、邻接表、邻接多重表和十字链表.
7.2.1 邻接矩阵 1,如果<i,j>∈E或者(i,j) ∈E arcs[i][j]= 0,否则 W(i,j): i≠j, 且(i,j) ∈E arcs[i][j]= ∞: i≠j,且(i,j) ∈E 0: i=j 在图的邻接矩阵表示中,除了记录每一个顶点信息的顶点表外,还有一个表示各个顶点之间关系的矩阵,称之为邻接矩阵。 若设图G=(V,E)是一个有n个顶点的图,则图的邻接矩阵是一个二维数组Arcs[n][n],它的定义为: 对于网络(或带权图),邻接矩阵定义如下:
7.2.1 邻接矩阵 下图给出了无向图、有向图和有向网的邻接矩阵。其中一维数组Vertexes[]用于存储顶点的信息,二维数组arcs[][]用于存储边(或弧)的信息。
7.2.1 邻接矩阵 从图中可知,无向图的邻接矩阵是对称的,将第i行的元素值或第i列的元素值累加起来就得到顶点i的度。即:
7.2.1 邻接矩阵 • 有向图的邻接矩阵可能不对称。 • 如果第i行第j列为1,则表示有一条从顶点Vi到顶点Vj的有向边,将第i行的所有元素值累加起来就得到顶点Vi的出度OD(Vi)。 • 如果第j行第i列为1,则表示有一条从顶点Vj到顶点Vi的有向边,将第i列的所有元素值累加起来就得到顶点Vi的入度ID(Vi)。
7.2.1 邻接矩阵 邻接矩阵作为存储表示的图的类声明: constint MaxVertexes = 20; //最大的顶点数 template <classvertexType, classarcType>class Graph { //vertexType是顶点类型,arcType是边(弧)的类型 private: SeqList<vertexType> Vertexes (MaxVertexes); //存储顶点信息的顺序表 arcType Arcs[MaxVertexes][MaxVertexes]; //存储边(或弧)信息的矩阵 int CurrentNumArcs; //当前的边(或弧)数 int FindVertex ( SeqList<vertexType> & L, const vertexType &v ) //查找顶点v是否存在 { return L.Locate(v); } int GetVertexPos ( const vertexType &v ) //取顶点v在数组中的位置 { return FindVertex (Vertexes,v ); } 线性表的基本操作
7.2.1 邻接矩阵 public: Graph ( int num = MaxVertexes );//构造函数 int IsEmpty( ) const { return Vertexes.empty( ); } //判断图是否为空 int NumberOfVertexes ( ) { return Vertexes.size; } //取图的顶点数 int NumberOfArcs ( ) { return CurrentNumArcs; } //取图的边(或弧)数 vertexType GetValue ( int v ) //取图中顶点v的值, 如果不存在顶点v,则返回空 arcType GetWeight ( int v1, int v2 ); //取边(v1, v2)(或弧< v1, v2 >)上的权
7.2.1 邻接矩阵 int GetFirstNeighbor ( int v ); //取图中顶点v的第一个邻接点的序号。如果不存在,则返回-1 int GetNextNeighbor ( int v1, int v2 ); //取图中顶点v1的在v2之后的下一个邻接点的序号。如果不存在,则返回-1 int InsertVertex ( vertexType & v ); //在图中插入顶点v int InsertArc( int v1, int v2, arcType weight ); //在图中插入依附于顶点v1和v2的边(或弧),weight是相应边(或弧)的信息 void DeleteVertex ( int v ); //删除顶点v,及依附于v的边(或弧) void DeleteArc ( int v1, int v2 ); //在图中删除依附于顶点v1和v2的边(或弧) }
在邻接矩阵上,图的部分成员函数的实现: template <class vertexType, class arcType> Graph<vertexType, arcType> :: Graph( int num) {//构造函数 for ( int i = 0; i < num; i++ ) //邻接矩阵初始化 for ( int j = 0; j < num; j++ ) Arcs[i][j] = 0; CurrentNumArcs = 0; //当前边(或弧)数为0 }
template <class vertexType, class arcType> vertexType Graph<vertexType, arcType> :: GetValue ( int v ) {//取图中第v个顶点 if ( v <0 || v > =Vertexes.len ) //如果顶点v不存在,则返回空 return NULL; else return Vertexes.GetData[v]; } 线性表的基本操作
template <class vertexType, class arcType> arcType Graph<vertexType, arcType> :: GetWeight( int v1, int v2 ) { //取出以顶点v1和v2为两端点的边上的权值 if ( v1 <0 || v1 > =Vertexes.len || v2 <0 || v2 > Vertexes.len ) return -1; //如果顶点v1或v2不存在,则返回-1 elsereturn Arcs[v1][v2]; }
template <class vertexType, class arcType>int Graph<vertexType, arcType>:: GetFirstNeighbor ( const int v ) {//取出顶点位置为v的第一个邻接顶点的位置 if ( v >=0 && v <Vertexes.len ) { for ( int j = 0; j < Vertexes.len; j++ ) if ( Arcs [v][j] ==1 ) return j; } return -1; }
template <class vertexType, class arcType> int Graph<vertexType, arcType> :: GetNextNeighbor ( int v1, int v2 ) { //取图中顶点v1的在v2之后的下一个邻接点的序号。如果不存在返回-1 if (v1 >=0 && v1 <Vertexes.len && v2 >=0 && v2 <Vertexes.len ) { for ( int j= v2+1; j < Vertexes.len; j++ ) if ( Arcs[v1][j] == 1 ) return j; } return -1; }
template <class vertexType, class arcType> int Graph<vertexType, arcType> :: InsertVertex ( vertexType & v ){ //在图中插入顶点v。插入成功,返回1;否则返回0 return Vertexes.insert ( v, Vertexes.len); //在顶点数组Vertexes的最后插入v }
template <class vertexType, class arcType> int Graph<vertexType, arcType> :: InsertArc( int v1,int v2, arcType weight ){ //在图中插入弧< v1 v2>。插入成功,返回1;否则返回0。weight是相应弧的信息 if ( v1 <0 || v1 > =Vertexes.len || v2 <0 || v2 >= Vertexes.len ) //如果顶点v1或v2不存在,则返回0 return 0; Arcs[v1][v2] = weight; CurrentNumArcs++; return 1; }
template <class vertexType, class arcType> void Graph<vertexType, arcType> :: DeleteVertex ( int v ){ //删除顶点v,及依附于v的弧 for ( int i = 0; i < Vertexes.len; i++ ) for ( int j = 0; j < Vertexes.len; j++ ) if ( i == v || j == v ) && Arc[i][j] != 0 ) { Arc[i][j] = 0; CurrentNumArcs--; } Vertexes.delete( v ); }
template <class vertexType, class arcType> void Graph<vertexType, arcType> :: DeleteArc ( int v1, int v2 ); //在图中删除弧< v1,v2> if ( v1 <0 || v1 > =Vertexes.len || v2 <0 || v2 > Vertexes.len ) return 0; //如果顶点v1或v2不存在,则返回0 Arcs[v1][v2] = 0; CurrentNumArcs-- ; return 1; } 7.2.2 邻接表
7.2.2 邻接表 实现:为图中每个顶点建立一个单链表,第i个单链表中的结点表示依附于顶点Vi的边(有向图中指以Vi为尾的弧)。 顶点结点: data域: 存放顶点信息 firstarc域: 指示第一个邻接点 弧(边)结点: adjvex域:邻接点域,存放与Vi邻接的点在顶点数组中的位置 nextarc域:指示下一条边或弧(下一个邻接点)
7.2.2 邻接表 下图(b)所示的是有向图的邻接表表示。
7.2.2 邻接表 • 特点 • 无向图中顶点Vi的度为第i个单链表中的边结点数 • 有向图中 • 顶点Vi的出度为第i个单链表中的弧结点个数 • 顶点Vi的入度为所有单链表中邻接点域值是i的弧结点个数 ? 计算顶点的入度比较麻烦!!
7.2.2 邻接表 为此,对有向图可以建立逆邻接表,如图(c)所示。在有向图的逆邻接表中,顶点vi的弧链表中链接的是所有以vi为弧头的弧尾顶点。
7.2.2 邻接表 对于带权图(网络),必须在邻接表的边(或弧)结点中增加一个存放边(或弧)上的权值的域weight。如下图所示的是一个带权图的邻接表表示。
7.2.2 邻接表 • 在邻接表的边(或弧)链表中,各个边(或弧)结点的链入顺序任意,视边(或弧)结点输入次序而定。 • 设图中有 n 个顶点,e 条边, • 则用邻接表表示无向图时,需要 n 个顶点结点,2e 个边结点; • 用邻接表表示有向图时,若不考虑逆邻接表,只需 n 个顶点结点,e 个弧结点。