590 likes | 724 Views
第六章 图. 6.1 图的概念 图 (Graph)—— 是一种结点之间为多对多关系的数据结构。 逻辑特征是:可以有任意个开始结点和任意个终端结点,其余各个结点可以有任意个前趋和任意个后继。图中的结点常称为顶点。 图的逻辑结构可以用二元组表示: Graph = (V,E) V 是顶点 (vertex) 的集合; E 是边 (edge) 的集合。. 有向图 (Digraph)—— 若图 G 中的每条边都是有方向的,则称图 G 为有向图。. 例. G1 = (V1, E1) V1 = {v1, v2, v3}
E N D
第六章 图 • 6.1 图的概念 • 图(Graph)——是一种结点之间为多对多关系的数据结构。 • 逻辑特征是:可以有任意个开始结点和任意个终端结点,其余各个结点可以有任意个前趋和任意个后继。图中的结点常称为顶点。 • 图的逻辑结构可以用二元组表示: Graph=(V,E) V是顶点(vertex)的集合; E是边(edge)的集合。
有向图(Digraph)——若图G中的每条边都是有方向的,则称图G为有向图。有向图(Digraph)——若图G中的每条边都是有方向的,则称图G为有向图。 例 G1 = (V1, E1) V1 = {v1, v2, v3} E1 = {<v1, v2>, <v2, v1>, <v2, v3>} G2 = (V2, E2) V2 = {v1, v2, v3} E2 = {<v1, v2>, <v2, v1>, <v1, v3>, <v3, v1>, <v2, v3>, <v3, v2>}
无向图(Undigraph)——若图G中的每条边都是没有方向的,则图G称为无向图。无向图(Undigraph)——若图G中的每条边都是没有方向的,则图G称为无向图。 例 G3 = (V3, E3) V3= {v1, v2, v3, v4} E3= {(v1, v2), (v1, v3), (v1, v4), (v2, v3), (v2, v4)} G4 = (V4, E4) V4= {v1, v2, v3 } E4= {(v1, v2), (v1, v3), (v2, v3) }
邻接点与关联边 • 有向图中,若<x,y>是有向图中的一条边,则顶点x 和顶点y称为邻接点。称x邻接到y或y邻 接于x,同时称边<x,y>是顶点x和顶点y 相关联的边(Incident); • 无向图中,若(x,y)是无向图中的一条边,则顶点 x和顶点y互为邻接点,并且称边(x, y)是 顶点 x和顶点y相关联的边。 • 顶点的度 • 有向图中,顶点的度分成入度与出度 • 入度:该顶点入边的数目 • 出度:该顶点出边的数目 • 无向图中,顶点的度为与该顶点相连的边数
子图——设有图G=(V,E) ,如果满足: • V’V • E’E • E'中边所邻接的点均在V'中出现 则 G‘= (V’,E‘)也是一个图,并且称之为G的子图。 • 有向完全图——n个顶点的有向图最大边数是n(n-1) • 无向完全图——n个顶点的无向图最大边数是n(n-1)/2 • 路径——有向图G=(V,E)中,若存在一个顶点序列vp,vi1,vi2,…vin,vq,使得<vp,vi1>,<vi1,vi2 >,…,<vin,vq>均是图中的边,则称此序列是从顶点vp到vq一条路径。 • 路径长度——该路径上经过边的数目。 • 回路——一条路径第一个顶点和最后一个顶点相同的 叫~ • 简单路径——序列中顶点不重复出现的路径叫~ • 简单回路——除了第一个顶点和最后一个顶点外,其余顶点不重复出现的回路叫~
有根图——若存在一个顶点v,从该顶点到其余各个顶点都有路径,则称此图为有根图 • 连通——从顶点x到顶点y有一条路径,则说x和y是连通的 • 连通图——图中任意两个顶点都是连通的叫连通图 • 连通分量——无向图G的极大连通子图称为G的连通分量 • 强连通图——若G中任意两个顶点都是强连通的,则称图G是强连通图 • 强连通分量 ——有向图G的极大强连通子图称为G的强连通分量 • 权——与图的边相关的数值叫权值。 • 网络——边上带权的图称为网络
6.2 图的存储结构 • 邻接矩阵——表示顶点之间邻接关系的矩阵 • 设G=(V,E)是具有n个顶点的图,则G的邻接矩阵是一个n阶方阵A,A中元素的值aij可以定义为:
特点: • 无向图的邻接矩阵对称;有n个顶点的无向图需存储空间为n²有向图邻接矩阵不一定对称;有n个顶点的有向图需存储空间为n² • 无向图中顶点Vi的度是邻接矩阵A中第i行元素之和 • 有向图中, • 顶点Vi的出度是A中第i行元素之和 • 顶点Vi的入度是A中第i列元素之和 • 网的邻接矩阵可定义为:
可以得到邻接矩阵存储结构C语言描述如下: #define n 5 /* 图的顶点数 */ #define e 6 /* 图的边数 */ #define max 10000 /* 设置一个极大数无穷大 */ typedef char vextype; /* 顶点类型 */ typedef int adjtype; /* 权值类型 */ typedef struct { vextype vertex[n+1]; /* 顶点数组 */ adjtype edge[n+1][n+1]; /* 邻接矩阵 */ }adj_matrix;
邻接表 • 邻接表法(Adjacency List)是图的一种链式存储结构 顶点采用顺序方式进行存储, 用n个单链表来存储图中的边,第i个单链表是所有与第i个顶点相关联的边链接而成的,称此单链表为第i个顶点的边表,边表中的每个结点称为边表结点。 在顶点的顺序表中,每个元素增加一个指针域用来存放各个边表的头指针,称此顺序表为顶点表,而顺序表中的每个元素称为顶点表结点。 顶点表和各顶点的边表一起组成图的邻接表。
邻接表存储结构C语言描述如下: typedef struct node { int adjvex; /* 邻接点域 */ struct node *next; /* 指针域 */ }edgenode; /* 定义边表结点 */ typedef struct { vextype vertex; /* 顶点域 */ edgenode *link; /* 指针域 */ }vexnode; /* 定义顶点表结点 */ vexnode adjlist[n+1];
特点 • 无向图中顶点Vi的度为第i个单链表中的结点数 • 有向图中 • 顶点Vi的出度为第i个单链表中的结点个数 • 顶点Vi的入度为整个单链表中邻接点域值是i的结点个数 • 逆邻接表:有向图中对每个结点建立以Vi终点的单链表
边集数组 • 邻接表法(Adjacency List)是图的一种链式存储结构 边集数组(edgeset array)是图的一种顺序存储方式,利用一维数组来存储图中所有的边,数组中的每个元素用来存储图中的一条边,包括:始点、终点的序号及权值,该数组中所含元素的个数要大于等于图中边的条数。 typedef struct edge { int fromvex; /* 边的始点域 */ int endvex; /* 边的终点域 */ int weight; /* 边的权值域 */ }edgeset; /* 定义边集数组类型 */ edgeset ge[e+1]; /* 边集数组全局量 */
6.3 图的遍历 • 深度优先遍历(DFS) • 方法:对于给定的图G,假设初始时所有顶点均未被访问过,则可从G中任选一顶点vi做为初始出发点,深度优先搜索可定义为:访问出发点vi,置访问标记为1,然后依次从vi的未被访问过的邻接点vj出发,继续进行深度优先搜索,直至图中所有和vi有路径相通的顶点均被访问过。很显然图的深度优先搜索过程是递归的。它的特点是尽可能先对图从纵深方向进行搜索,故称为深度优先搜索。 DFS序列为:v1,v2,v4,v8,v5,v3,v6,v7。
深度优先遍历算法 • 递归算法 void DFS(int i) { int j; printf ("输出序号为%d的顶点: %c\n",i,adj->vertex[i]); visited[i]=1; /* 标记vi已经访问过 */ for (j=1;j<=n;j++) /* 依次搜索vi的邻接点 */ if((adj->edge[i][j])&&(!visited[j])) DFS(j);}
void DFSL(int i) { edgenode *p; printf("输出序号为%d的顶点: %c\n",i,adjlist[i].vertex); visited [i]=1; /* 标记vi已经访问过 */ p=adjlist[i].link; /* p为vi的边表头指针 */ while (p) /* 依次搜索vi的邻接点 */ { if(!visited[p->adjvex]) DFSL(p->adjvex); p=p->next; } }
生成树 连通图G的一个子图如果是一棵包含G的所有顶点的树,则该子图称为G的生成树(Spanning Tree) 。
广度优先遍历(BFS) • 方法:对于给定图G,假设初始时的所有顶点均未被访问过,从图G中任选一顶点vi为初始出发点,广度优先搜索遍历可定义为:首先访问出发点vi,接着依次访问vi的所有的邻接的未被访问过的点w1, w2,…, wt,然后,再依次访问与w1,w2,…,wt相邻接的未被访问过的顶点。依此类推,直至图中所有和初始出发点vi有路径相通的顶点都已访问到为止。显然,此方法的特点是尽可能先对横向进行搜索,故称之为广度优先搜索。 BFS序列为:v1,v2,v3,v4,v5,v6,v7,v8
广度优先遍历算法 在广度优先遍历中,先被访问的顶点,其邻接点亦先被访问,所以在算法的实现中需要使用一个队列,用来依次记住被访问过的 顶点。 算法开始时,将初始点Vi访问后插入队列中,以后每从队列 中删除一个元素,就依次访问它的每一个未被访问过的邻接点,并 令其进队。这样当队列为空时,表明所有与初始点有路径相通的顶 点都已访问完毕,算法到此结束。
例 1 2 3 4 5 f f f adjvex next 5 4 5 3 4 5 2 1 1 3 2 1 adjvex next ^ 1 1 1 4 2 2 4 3 ^ 0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 3 3 ^ r r r 4 4 ^ 遍历序列:1 遍历序列:1 4 遍历序列:1 4 3 5 5 ^
例 1 4 3 2 3 2 2 3 4 5 遍历序列:1 4 3 2 遍历序列:1 4 3 2 f f f adjvex next 3 1 1 4 3 5 5 5 1 2 2 4 adjvex next ^ 1 1 2 2 3 2 5 ^ 0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 3 3 ^ r r r 4 4 ^ 遍历序列:1 4 3 2 5 5 5 ^
例 1 2 3 4 5 f f f vexdata firstarc 1 5 3 4 1 5 2 2 3 4 5 1 adjvex next ^ 1 1 2 2 ^ 0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 5 2 5 3 3 ^ r r r 4 4 ^ 5 5 ^ 遍历序列:1 4 3 2 5 遍历序列:1 4 3 2 5 遍历序列:1 4 3 2 5
邻接表法广度优先: void BFSL(int k) /* 用adjlist存储 */ { int i; edgenode *p; SETNULL(Q); printf("输出序号为%d的顶点: %c\n",k,adjlist[k].vertex); /* 访问出发点vk */ visited[k]=1; /* 标记vk已经访问过 */ ENQUEUE (Q,k); /* 顶点vk的序号k入队 */ while(!EMPTY(Q)) /* 队列非空执行 */ { i= DEQUEUE(Q);/* 队头元素顶点序号出队 */ p=adjlist[i].link; while (p!=NULL) { if(!visited[p->adjvex]) { printf("输出序号为%d的顶点: %c\n",p->adjvex,adjlist[p->adjvex].vertex); visited[p->adjvex] =1; ENQUEUE(Q,p->adjvex); } p=p->next; } } }
生成树 连通图G的一个子图如果是一棵包含G的所有顶点的树,则该子图称为G的生成树(Spanning Tree) 。 • 深度优先生成树与广度优先生成树 • 说明 • 一个图可以有许多棵不同的生成树 • 所有生成树具有以下共同特点: • 生成树的顶点个数与图的顶点个数相同 • 生成树是图的极小连通子图 • 一个有n个顶点的连通图的生成树有n-1条边 • 生成树中任意两个顶点间的路径是唯一的 • 在生成树中再加一条边必然形成回路 • 含n个顶点n-1条边的图不一定是生成树
7 1 2 7 5 9 13 24 6 5 10 17 12 3 4 18 要在n个城市间建立通信联络网, 顶点——表示城市 权——城市间建立通信线路所需花费代价 希望找到一棵生成树,它的每条边上的权值之和(即建立 该通信网所需花费的总代价)最小———最小代价生成树 • 6.4最小生成树 • 问题提出 • 问题分析 n个城市间,最多可设置n(n-1)/2条线路 n个城市间建立通信网,只需n-1条线路 问题转化为:如何在可能的线路中选择n-1条,能把 所有城市(顶点)均连起来,且总耗费 (各边权值之和)最小 • 定义:连通网络的所有生成树中边上权值之和最小的生成树称为最小生成树(Minimun Spanning Tree)
MST性质: 假设G=(V, E)是一个连通网络,U为顶点集V的一个非空子集。若边(u,v)是所有的一个端点在U中(即u∈U),另一个端点不在U中(即v∈V-U)的这些边里面,权值最小的一条,则一定存在一棵G的最小生成树包括此边(u,v)。
构造最小生成树方法 • 方法一:普里姆(Prim)算法 • 算法思想:设G=(V, E)是连通网,T=(U,TE)是G的最小生成树,其中U是T的顶点集,TE是T的边集,U和TE的初值均为空集。 • 初始令U={u0},(u0V), TE= • 在所有uU,vV-U的边(u,v)E中,找一条代价最小的边(u0,v0) • 将(u0,v0)并入集合TE,同时v0并入U • 重复上述操作直至U=V为止,则T=(V,TE)为G的最小生成树
方法二:克鲁斯卡尔(Kruskal)算法 • 算法思想:设连通网G=(V,E),令最小生成树T=(U,TE) • 初始状态U=V,TE= • 将图G中的边按权值从小到大的顺序依次选取,若选取的边使生成树T不形成回路,则把它并入TE中,保留作为T的一条边;若选取的边使生成树T形成回路,则将其舍弃。 • 依此类推,直至TE中包含n-1条边为止,此时的T 即为最小生成树。
7.5 拓扑排序 • 问题提出:学生选修课程问题 顶点——表示课程 有向边——表示先决条件,若课程i是课程j的先决条件,则图中有边<i,j> 学生应按怎样的顺序学习这些课程,才能无矛盾、顺利地完成学业——拓扑排序 • 定义 • AOV网——用顶点表示活动,用有向边表示活动的先后关系的有向图称为顶点活动网(Activity On Vertex network),简称AOV网 • 若<vi,vj>是图中有向边,则vi是vj的直接前驱;vj是vi的直接后继 • AOV网中不允许有回路
6.5 最短路经 • 最短路径问题通常是指如何从图中某一顶点(称为源点)到达另一顶点(称为终点)的多条路径中,找到一条路径,使得此路径上经过的各边上的权值总和达到最小。 • 最短路径问题通常可以分成四种不同情况: • 单源点、单目标点最短路径问题; • 单源点、多目标点最短路径问题; • 多源点、单目标点最短路径问题; • 多源点、多目标点最短路径问题。 我们讨论最常见的两种情况:单源点最短路径和任意两点间的最短路径。
单源最短路经 从源点1到其余各顶点的最短路径
迪杰斯特拉(Dijkstra)算法: • 初始化:集合S中只有一个源点,其余顶点都在集合V-S中。此时S中源点的距离值(最短路径)为0;V-S中各个顶点的距离值为:只经过源点到达该顶点的当时最短路径,即从源点到达该顶点的边长(无边时,距离值为∞)。当某点的距离值不等于∞时,可以得到该点的路径(一条边)。 • 首先,从V-S中选择一个距离值最小的顶点v,将其加入到S中,扩充集合S。此时该点的距离值就是最短路径长度。 • 然后,对集合V-S中剩余顶点的距离值进行修正。方法是:假设u是V-S中的一个顶点,u点当前的距离值为len_u,而新加入S的点m的最短路径len_m加上边<u,m> 的长度为L,若L<len_u,则u点当前的距离值修正为:len_u=L。同时修正路径,即在m点的路径后面加上u点即可。 • 最后,重复(2)、(3)步,直到所有顶点全部进入集合S,即V=S为止。
1. 初始化:S={ 1},V-S={ 2,3,4,5}。各点的距离值及路径为: 2. 从V-S中选择距离值最小的顶点2,加入到S中。此时S={ 1,2},V-S={ 3,4,5}。然后对V-S中各点的距离值进行修正,修正后各点的距离值及路径为:
3.再从V-S中选择距离值最小的顶点4,加入到S中。此时S={ 1,2,4}, V-S={ 3,5}。修正V-S中的点的距离值为: 4. 继续从V-S中选择距离值最小的顶点3,加入到S中。此时S={ 1,2, 3,4},V-S={ 5}。修正V-S中的点的距离值为: 5.最后从V-S中选择距离值最小的顶点5,加入到S中。此时S={ 1,2, 3,4,5},V-S={ },算法结束。
存储结构C语言描述: typedef struct { int prenode; int pathlength; }shortestpath; int flag[n+1]; shortestpath sp[n+1];
任意两点间最短路经 • Floyd算法的实现是通过矩阵迭代来完成的。 有向图及其邻接矩阵
6.6 拓扑排序 • 拓扑排序是有向无环图(directed acycline graph)上的重要运算。拓扑排序的目的是将有向无环图中所有的顶点排成一个线性序列,通常称为拓扑序列。 • 拓扑序列必须满足如下条件:对一个有向无环图G=(V,E),若顶点u,v∈V,且u到v有路径,则在此线性序列中,顶点u必排列在顶点v之前。 • AOV网 :用顶点表示活动,顶点之间的有向边表示活动之间的先后关系,从而将实际问题转化为一个有向图,称其为顶点活动网(Activity On Vertex network),简称AOV网。