第5章 图.docx
- 文档编号:3732042
- 上传时间:2022-11-25
- 格式:DOCX
- 页数:56
- 大小:569.23KB
第5章 图.docx
《第5章 图.docx》由会员分享,可在线阅读,更多相关《第5章 图.docx(56页珍藏版)》请在冰豆网上搜索。
第5章图
第5章图
在数据结构中,图比前面章节所讲述的的线性表、树等都更为复杂。
而且,图这种数据结构在解决实际问题中也有着广泛的应用,比如说:
当驱车旅游时,需要在众多路线中选择一条最佳路线;对大型工程进行管理时,怎样才能够提前完成工程;在电路板上布线时,如何保证在连线最短的情况下,连接多个节点。
这些问题都可以通过本章所讲述的内容解决。
5.1图的概念
5.1.1图的定义
图就是顶点和边的集合。
一般描述为图G={V,E},V(G)为图G的顶点集合,必须是有穷非空集合,E(G)为图G的边集合,可以是空集。
图5.1.1列出了两个典型的图。
5.1.2图的术语
顶点:
图中的数据元素。
边:
连接图中顶点的连线,表示两个顶点之间的某种关系。
可以用顶点对来表示,如图5.1.1(a)中顶点V1和V2之间的关系可以用(V1,V2)表示,图5.1.1(b)中顶点V1到顶点V2之间的关系可以用
弧:
连接图中顶点的有向连线,表示两个顶点之间的某种关系。
可以用顶点对来表示,图5.1.1(b)中顶点V1到顶点V2之间的关系可以用顶点对
弧头:
弧的终止顶点。
弧尾:
弧的开始顶点。
无向图:
由顶点和边组成,边不具备方向性。
有向图:
由顶点和弧组成。
完全图:
图中的任意两个顶点之间都存在一条边,在一个含有n个顶点的无向完全图中,有n(n-1)/2条边。
如图5.1.2(a)所示。
有向完全图:
图中的任意两个顶点之间都存在一对相反方向的弧,在一个含有n个顶点的有向完全图中,有n(n-1)条边。
如图5.1.2(b)所示。
稀疏图:
具有很少边或弧的图。
稠密图:
具有很多边或弧的图,即一个图接近完全图。
权:
与图中的边或者弧有关的数据信息称为权(weight)。
在实际应用中,权值可以有某种含义。
比如,在一个反映城市交通线路的图中,边上的权值可以表示该条线路的长度或者等级,如图5.1.3(a)所示;对于一个电子线路图,边上的权值可以表示两个端点之间的电阻、电流或电压值;对于反映工程进度的图而言,边上的权值可以表示从前一个工程到后一个工程所需要的时间等等,如图5.1.3(b)所示。
网:
边上带权的图称为网图或网络(network),是一个无向网图。
如果边是有方向的带权图,则就是一个有向网图,如图5.1.3所示。
(a)交通图(b)施工进度图
图5.1.3权与网
子图:
如果存在这样的两个图:
G={V,E}G’={V’,E’}
那么称图G’是图G的子图。
如图5.1.4所示,图5.1.4(b)和图5.1.4(c)是图5.1.4(a)的子图。
路径,路径长度:
顶点Vp到顶点Vq之间的路径(path)是指顶点序列Vp,Vi1,Vi2,…,Vim,Vq.。
其中,(Vp,Vi1),(Vi1,Vi2),…,(Vim,.Vq)分别为图中的边或者弧。
路径上边或者弧的数目称为路径长度。
图5.1.1(a)所示的无向图中,V1→V3→V4→V5与V1→V2→V5是从顶点V1到顶点V5的两条路径,路径长度分别为3和2。
简单路径:
没有重复顶点的路径,如图5.1.5(a)所示路径为V0→V1→V3→V2,图5.1.5(b)示意非简单路径V0→V1→V3→V0→V1→V2
回路:
如果一条路径的第一个顶点和最后一个顶点是同一个顶点,那么这条路径构成了一个回路。
简单回路:
除了第一个和最后一个顶点外,不再有重复顶点的回路,称为简单回路。
如图5.1.5(c)所示路径为V0→V1→V3→V0。
(a)简单路径(b)非简单路径(c)简单回路
图5.1.5路径
自回路:
如果一条边的头和尾都是同一个顶点,称之为自回路。
自回路的方向是没有意义的,它既可以是有向边,也可以是无向边。
连通图、连通分量:
在无向图中,若从顶点Vi到顶点Vj有路径(其中i≠j),则称顶点Vi与Vj是连通的。
如果图中任意一对顶点都是连通的,则称此图是连通图。
非连通图的极大连通子图叫做连通分量。
图5.1.1(a)所示的无向图为连通图,其连通分量为1。
图5.1.4(a)中图G包含2个连通子图,其连通分量为2。
强连通图、强连通分量:
在有向图中,若对于每一对顶点Vi和Vj(其中i≠j),都存在一条从Vi到Vj和从Vj到Vi的路径,则称此图是强连通图。
非强连通图的极大强连通子图叫做强连通分量。
如图5.1.6所示。
(a)强连通图(b)有向图G及其2个强连通分量
图5.1.6强连通图和强连通分量
极小连通子图:
也称为生成树,图共有n个顶点,但只有n-1条边。
如果在该图中上添加1条边,必定构成一个环,若图中有n个顶点,却少于n-1条边,必为非连通图。
如图5.1.7所示。
(a)无向图G(b)无向图G的生成树
图5.1.7极小连通子图
对于非连通图中极小连通子图集合称为此非连通图的生成森林。
邻接:
如果(Vi,Vj)是E(G)中的一条无向边,那么顶点Vi和顶点Vj是邻接的;如果
关联:
如果(Vi,Vj)是E(G)中的一条无向边,那么边(Vi,Vj)是和顶点Vi、Vj相关联的;如果
度:
无向图中,和某顶点相关联的边的数量,称为这个顶点的度。
入度:
有向图中,以某个顶点为弧头的弧的数量,称为这个顶点的入度。
出度:
有向图中,以某个顶点为弧尾的弧的数量,称为这个顶点的出度。
顶点4的度:
3顶点0入度:
1,出度:
3
顶点2的度:
4顶点2入度:
1出度:
0
图5.1.9度、入度和出度
平行:
如果有若干条边的头和尾都是相同的,称之为平行边。
多重图:
包含有平行边的图都称之为多重图。
简单图:
既不包含自回路也不包含平行边的图,称为简单图。
本章以后所讲的都是简单图。
5.1.3图的抽象数据类型
上面已经详细讨论了图的定义及相关概念,下面我们将重点围绕图的抽象数据类型进行讨论。
ADT图的抽象数据类型
ADTGraph
{
DataSet:
非空有限顶点集合V。
RelationSet:
非空有限顶点由多条边连接起来,构造成的顶点和边的关系。
OperationSet:
CreateGraph(&G);
建立图的算法
DestroyGraph(&G);
删除图的算法
LocateVex(&G,v);
在图中查找顶点v
GetVex(&G,v);
获取顶点v的值
PutVex(&G,v,value);
修改顶点v的值
DFSTraverse(&G,v);
从顶点v开始,对图进行深度优先搜索
BFSTraverse(&G,v);
从顶点v开始,对图进行宽度优先搜索
}
5.2图的存储结构
相对于树来说,图的结构更为复杂,顶点、边之间的联系更为密切,简单的二重链表,甚至更多重链表已经无法表达它们之间的这些复杂关系了。
由于图中各个顶点的度差别可能很大,这就给图的存储表示带来了很大的困难。
这里先给出几种常见的存储表示方法,但要强调的是,在解决实际问题的时候,并不一定要拘泥于这里所给的描述,可以根据实际情况来最终确定存储表示的方法。
5.2.1邻接矩阵表示法
1.邻接矩阵的概念
邻接矩阵表示法是一种简单的存储表示方法,它的优点是简单,缺点就是空间开销在某些情况下可能比较大,而且空间的利用率可能较低。
这种表示法既适用于无向图,也适用于有向图。
这种方法的基本思想就是,对于有n个顶点的图,就定义一个n阶矩阵,把各个顶点之间存在的、不存在的边都穷举出来。
对于不存在的边,带权图和不带权图的实现上有所差别。
如果图G是无向不带权图,可以用下面的邻接矩阵来表示,其中1表示顶点Vi和Vj之间邻接,0表示不邻接:
如果图G是有向不带权图,可以用下面的邻接矩阵来表示:
如果图G是无向带权图,可以用下面的邻接矩阵来表示,其中1表示顶点Vi和Vj之间邻接,∞表示不邻接,程序设计时无穷大通常表示为远大于权值Wi,j即可:
如果图G是有向带权图,可以用下面的邻接矩阵来表示:
图5.2.1列举出了四种典型的图,图5.2.2列举出了对应的邻接矩阵。
图5.2.2四种典型的图对应的邻接矩阵
从图示中可以看出,无向图对应的邻接矩阵是对称的,有向图对于的邻接矩阵则不一定对称。
采用这种表示方法,可以根据矩阵的第i行j列元素的值,直接判断某两个顶点之间是否邻接。
而且计算顶点的度也很方便:
对于无向图中的顶点Vi,它的度就是邻接矩阵中第i行的和,对于有向图来中的顶点Vi,它的入度就是第i列的和,出度就是第i行的和。
2.邻接矩阵的实现
按照前面的描述,图可以采用一个二维数组来存储表示。
具体的存储结构定义如下:
structGraph
{
int**data;
intvertex_num;
intedge_num;
};
data指向存储数据的存储空间,vertex_num指示顶点的数量,edge_num指示边的数量。
3.基于邻接矩阵表示的图的建立
采用邻接矩阵表示方法,可以用下面的算法建立图。
基于邻接矩阵表示的图的建立算法
//基于邻接矩阵表示的有向带权图的建立算法
voidCreateGraph(Graph&g)
{
intn,first,second;
cout<<"请输入边的数量";
cin>>g.edge_num;
cout<<"请输入顶点的数量";
cin>>g.vertex_num;
g.data=newint*[g.vertex_num];
for(n=0;n g.data[n]=newint[g.vertex_num]; for(first=0;first for(second=0;second g.data[first][second]=INFINITY; for(n=0;n { cout<<"请输入第"< cin>>first; cout<<"请输入第"< cin>>second; cout<<"请输入权"; cin>>g.data[first][second]; } } 该算法虽然只是介绍了有向带权图的建立,稍加改造,同样可以建立基于邻接矩阵的其它类型的图。 5.2.2邻接表表示法 1.邻接表的概念 对于图G中的某个顶点Vi,把与它相邻接的所有顶点(如果是有向图,则是所有邻接自该顶点的所有顶点)串起来,构成一个单链表,这个链表就称为顶点Vi的邻接表。 如果图G有n个顶点,那么就会得到n条邻接表。 为了有效地对这n条邻接表进行有效的管理,每条邻接表的前面都增设一个表头结点。 所有的表头结点可以存储在一个数组中。 为了避免各顶点信息的重复存储,可以规定,各顶点的基本信息存放在表头结点中,表头结点的基本格式如图5.2.3所示。 data分量存储各表头的基本信息。 而在邻接表中,只需要把边表示出来就可以了。 这些边的一个顶点是相同的,需要把边的另外一个顶点表示出来;另外,边本身可能存在权等信息,也需要表示出来。 其基本格式如图5.2.4所示。 another_vertex表示该边的另外一个顶点在表头结点数组中的下标,info表示该边的权等信息,next指向链表中的下一个结点。 如图5.2.5所示,给出了一个无向图的邻接表表示。 如图5.2.6所示,给出了一个有向图的邻接表表示。 一个图对应的邻接矩阵是唯一的,而对应的邻接表却不是唯一的,因为邻接表中各结点出现的顺序和建立图时的输入顺序有关。 当解决实际问题时,到底是采用邻接矩阵表示好,还是采用邻接表表示好呢? 下面作一个分析。 邻接表表示中的一条单链表,对应于邻接矩阵的一行,边表中结点个数等于一行中非零元素的个数。 若无向图G具有n个顶点e条边的图G,那么它的邻接表表示中有n个表头结点和2e个链表结点; 若有向图G具有n个顶点e条边的图G,那么它的邻接表表示中有n个表头结点和e个链表结点; 不管图G是否有向,只要有n个顶点,那么在邻接矩阵表示法中,就要占据n*n个存储单元。 如果图G是稀疏图,邻接表表示比邻接矩阵表示节省存储空间;如果不是是稀疏图,因为邻接表中有额外的附加链域,那么采取邻接矩阵表示法较好。 2.逆邻接表的概念 在邻接表表示中,对于无向图,顶点的度很容易计算,第i个单链表中的结点个数就是顶点Vi的度。 对于有向图,第i个单链表中的结点个数就是顶点Vi的出度,要想计算顶点的入度,就需要访问每一条单链表。 在某些情况下,可能要大量计算有向图顶点的出度,在某些情况下,可能要大量计算有向图顶点的入度。 为了方便地解决后一个问题,这里引进逆邻接表的概念。 逆邻接表的概念和邻接表的概念基本一样,唯一的差别在于: 邻接表表示中,如果是有向图,是把所有邻接自某顶点的所有顶点串起来,构成一条单链表;而在逆邻接表表示中,是把所有邻接至某顶点的所有顶点串起来,构成一条单链表。 图5.2.7给出了一个有向图的逆邻接表表示。 3.邻接表的实现 邻接表中,结点的定义如下: typedefintInfoType; structAdjNode { intanother_vertex; InfoTypeinfo; AdjNode*next; }; another_vertex表示该边的另外一个顶点在表头结点数组中的下标,info表示该边的权等信息,next指向链表中的下一个结点。 表头结点数组的定义如下: structAdjList { ETypedata; AdjNode*head; }AdjList; structAdjGraph { AdjList*head_list; intvertex_num;//顶点数 intedge_num;//边数 }; 4.基于邻接表的图的建立 采用邻接表表示方法,可以用下面的算法建立图。 基于邻接表表示的图的建立算法 //基于邻接表表示的有向带权图的建立算法 voidCreateGraph(AdjGraph&g) {intn,first,second,weight; AdjNode*p; cout<<"请输入边的数量"; cin>>g.edge_num; cout<<"请输入顶点的数量"; cin>>g.vertex_num; g.head_list=newAdjList[g.vertex_num]; for(n=0;n g.head_list[n].head=NULL; for(n=0;n {cout<<"请输入第"< cin>>first; cout<<"请输入第"< cin>>second; cout<<"请输入该弧的权"; cin>>weight; p=newAdjNode; p->info=weight; p->another_vertex=second; p->next=g.head_list[first].head; g.head_list[first].head=p; } } 该算法虽然只是介绍了有向图的建立,稍加改造,同样可以建立基于邻接表的无向图,以及基于逆邻接表的有向图。 5.2.3十字链表 十字链表(OrthogonalList)是有向图的一种存储方法,它实际上是邻接表与逆邻接表的结合,即把每一条边的边结点分别组织到以弧尾顶点为头结点的链表和以弧头顶点为头顶点的链表中。 在十字链表表示中,顶点表和边表的结点结构分别如图5.2.8的(a)和(b)所示。 在弧结点中有五个域: 其中尾域(tailvex)和头(headvex)分别指示弧尾和弧头这两个顶点在图中的位置,链域hlink指向弧头相同的下一条弧,链域tlink指向弧尾相同的下一条弧,info域指向该弧的相关信息。 弧头相同的弧在同一链表上,弧尾相同的弧也在同一链表上。 它们的头结点即为顶点结点,它由三个域组成: 其中vertex域存储和顶点相关的信息,如顶点的名称等; firstin和firstout为两个链域,分别指向以该顶点为弧头或弧尾的第一个弧结点。 例如,图5.2.9中所示图的十字链表所示。 若将有向图的邻接矩阵看成是稀疏矩阵的话,则十字链表也可以看成是邻接矩阵的链表存储结构,在图的十字链表中,弧结点所在的链表非循环链表,结点之间相对位置自然形成,不一定按顶点序号有序,表头结点即顶点结点,它们之间而是顺序存储。 有向图的十字链表结构定义: #defineMAX_VERTEX_NUM20 structArcBox { inttailvex,headvex;//该弧的尾和头顶点的位置 structArcBox*hlink,tlink;//分别为弧头相同和弧尾相财的弧的链域 InfoTypeinfo;//该弧相关信息的指针 }; structVexNode { VertexTypevertex: ArcBoxfisrin,firstout;//分别指向该顶点第一条入弧和出弧 }; structOLGraph { VexNodexlist[MAX_VERTEX_NUM];//表头向量 intvexnum,//有向图的顶点数 ntarcnum;//有向图的弧数 }; 图5.2.9有向图及其十字链表表示示意 在十字链表中既容易找到以为尾的弧,也容易找到以Vi为头的弧,因而容易求得顶点的出度和入度(或需要,可在建立十字链表的同时求出)。 同时,建立十字链表的时间复杂度和建立邻接表是相同的。 在某些有向图的应用中,十字链表是很有用的工具。 5.2.4邻接多重表 邻接多重表(AdjacencyMultilist)主要用于存储无向图。 因为,如果用邻接表存储无向图,每条边的两个边结点分别在以该边所依附的两个顶点为头结点的链表中,这给图的某些操作带来不便。 例如,对已访问过的边做标记,或者要删除图中某一条边等,都需要找到表示同一条边的两个结点。 因此,在进行这一类操作的无向图的问题中采用邻接多重表作存储结构更为适宜。 邻接多重表的存储结构和十字链表类似,也是由顶点表和边表组成,每一条边用一个结点表示,其顶点表结点结构和边表结点结构如图5.2.10所示。 其中,顶点表由两个域组成,vertex域存储和该顶点相关的信息firstedge域指示第一条依附于该顶点的边;边表结点由六个域组成,mark为标记域,可用以标记该条边是否被搜索过;ivex和jvex为该边依附的两个顶点在图中的位置;ilink指向下一条依附于顶点ivex的边;jlink指向下一条依附于顶点jvex的边,info为指向和边相关的各种信息的指针域。 图5.2.11无向图的邻接多重表 图5.2.11所示邻接多重表中,所有依附于同一顶点的边串联在同一链表中,由于每条边依附于两个顶点,则每个边结点同时链接在两个链表中。 可见,对无向图而言,其邻接多重表和邻接表的差别,仅仅在于同一条边在邻接表中用两个结点表示,而在邻接多重表中只有一个结点。 因此,除了在边结点中增加一个标志域外,邻接多重表所需的存储量和邻接表相同。 在邻接多重表上,各种基本操作的实现亦和邻接表相似。 邻接多重表存储结构定义: #defineMAX_VERTEX_NUM20 typedefemnu{unvisited,visited}VisitIf; structEBox { VisitIfmark: //访问标记 intivex,jvex;//该边依附的两个顶点的位置 structEBoxilink,jlink;//分别指向依附这两个顶点的下一条边 InfoTypeinfo;//该边信息指针 }; structVexBox { VertexTypedata; EBoxfistedge;//指向第一条依附该顶点的边 }; structAMLGraph { VexBoxadjmulist[MAX_VERTEX_NUM]; intvexnum,edgenum;//无向图的当前顶点数和边数 }; 5.3图的遍历 在实际应用中,图的遍历操作是一个非常重要的操作。 图的遍历是从某个顶点出发,按某种秩序对图中所有顶点各作一次访问。 若给定的图是连通图,则从图中任一顶点出发按某种秩序就可以访问到该图的所有顶点。 图的遍历主要有两种方法: 深度优先搜索、广度优先搜索。 图的遍历比树的遍历复杂得多,因为图中的任一顶点都可能和其余顶点相邻接,所以在访问了某个顶点之后,可能会延着某条路径又回到了该顶点。 为了避免有顶点被重复访问,必须记住每个顶点是否被访问过。 所以,在遍历的过程中,设置一个标志数组visited[n],它的初值为FALSE,一旦访问了某顶点,便将其对应的数组元素置为TRUE。 5.3.1深度优先搜索遍历 1.概念 深度优先搜索遍历(Depth_FirstSearch,DFS)是按照如下步骤进行的: 在图G中任选一顶点Vi为初始出发点,首先,访问出发点Vi,并将其标记为已访问过,然后,依次从Vi出发搜索Vi的每一个邻接点Vj,若Vj未曾访问过,则以Vj为新的出发点继续进行深度优先搜索遍历。 显然上述搜索法是递归定义的,它的特点是尽可能先对纵深方向进行搜索,所以称为深度优先搜索遍历。 如图5.3.1所示,假定从顶点V0出发开始搜索,标志数组的初值全为FALSE,访问顶点V0后置其标志为TRUE;与顶点V0邻接的顶点有三个,任选一个邻接顶点,比如 1)顶点V1,经检查,其标志数组元素为FALSE,可以访问顶点V1,然后置其标志为TRUE; 2)再顶点V1出发进行搜索,与顶点V1邻接的有两个顶点,顶点V0对应的标志数组元素为TRUE,顶点V5对应的标志数组元素为FALSE,选择顶点V5进行访问,然后置其标志为TRUE; 3)再从顶点V5出发进行搜索,与顶点V5邻接的有三个顶点,顶点V1对应的标志数组元素为TRUE, 4)顶点V2和V4对应的标志数组元素为FALSE,在顶点V2和V4中任选一个邻接顶点,比如顶点V2,对其访问后置其标志为TRUE; 5)再从顶点V2出发进行搜索,与顶点V2邻接的有两个顶点,顶点V5对应的标志数组元素为TRUE,顶点V4对应的标志数组元素为FALSE,选择顶点V4进行访问,然后置其标志为TRUE; 6)再从顶点V4出发进行搜索,与顶点V4邻接的有三个顶点,它们对应的标志数组元素都为TRUE,所以返回到访问顶点V4之前访问过的顶点V2; 7)再从顶点V2出发进行
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第5章