一、基本概念
1. 什么是图?
图G = (V, E) , V:有穷顶点集; E:边集
无向图:(v1, v2) = (v2, v1) 顶点对无序
有向图:<v1, v2> 不等于 <v2, v1>, 顶点对有序
2. 什么是子图?
顶点和边相应包含在主图
3. 什么是加权图?
图的每条边对应一个权值。权可以代表费用,时间长度等。
4. 什么是相关联?
指的是顶点与边之间的关系。
(1) 设G=(V, E)是无向图,若边(V1, V2)属于E,则称V1、V2是相邻顶点,边(V1, V2)是与顶点V1、V2相关联的边。
(2) 设G=(V,E)是有向图,若边<V1, V2>属于E,则称V1邻接到V2,V2邻接于V1,边<V1, V2>是顶点V1和V2相关联的边。
5. 什么是度?
(1)无向图中:
V1的度:与顶点V1相关联的边数和
(2)有向图中:
出度:以顶点V1为始点,并与V1相关联的边数
入度:以顶点V1为终点,并与V1相关联的边数
V1的度 = 出度 + 入度
终端顶点(叶子结点):有向图中,出度为0的顶点。
试题:
6. 什么是简单图?
(1)简单无向图:任意两个顶点之间最多一条边,且不含自回路。
(2)简单有向图:任意两个顶点之间最多两条相反的边,且不含自回路。
试题:
7. 什么是完全图?
(1)无向完全图:任意两个顶点之间都有一条边的简单无向图。含有n个顶点的无向完全图有n*(n-1)/2条边
(2)有向完全图:任意两个顶点之间都有反向相反的两条边的简单有向图。含有n个顶点的有向完全图有n*(n-1)条边。
8. 什么是路径?
(1)路径:有向(或无向)图中,无重复边且相邻前后边衔接的边序列称为从V1到Vn的一条路径。序列中的边数称为路径的长度。
(2)简单路径:除了V1和Vn可以相等,路径上所有的顶点各不相同。
(3)回路/环:V1 = Vn的简单路径
试题:
(1).
(2) 注意:简单路径一定是路径
9. 什么是连通图/强连通图?
(1)连通图:无向图 G中,任意两个顶点Vi和Vj之间有路径
(2)强连通图:有向图 G中,任意两个顶点Vi和Vj, 存在Vi到Vj的路径,以及Vj到Vi的路径。
10. 什么是连通分量/强连通分量?
(1) 连通分量:无向图的极大连通子图。如:
(2) 强连通分量:有向图的极大强连通子图。如:
11. 什么是生成树?
(1) 生成树:
含有n个顶点的连通无向图G的生成树是G的一个含有全部n个顶点和n-1条边的连通图
(即含有全部n个顶点的极小连通子图)
(2) 生成森林:
含m个连通分量的无向图的每个连通分量都有一棵生成树,构成图的生成森林。
二、图的存储表示
引言:考虑调用scanf()输入一个图?如何设计输入格式?在内存如何保存一个图?
1. 矩阵
1.1 如何用相邻矩阵表示图?
1.2 如何用相邻矩阵表示加权图?
1.3 如何用相邻矩阵存储图?
(1)用相邻矩阵表示图
a. 需要一个顺序表存储n个顶点
b. 需要一个nn的矩阵存储边
对于有向图,需要n^2个单元
对于无向图,只需要存储矩阵的上三角或者下三角,只需n(n-1)/2个单元
(2)思考:矩阵有何缺点?如何克服?
2. 邻接表
2.1如何用邻接表存储图?
邻接表由顶点表和边表组成
(1)无向图:对于每个顶点,将与顶点相关联的边组织一个链表,称为边表。顶点表的每个表项对应一个顶点,保存与该顶点相关联的表头指针。
(2)有向图:可以将与顶点相关联的出边组织成链表,作为与该顶点相关联的边表,称为出边表;或者将所有入边组织成链表,称为入边表。
2.2 结论
(1)邻接表表示加权图(网), 只需在边表的每个结点加上一个表示权的字段。
(2)邻接表存储具有n个顶点、m条边的图:
a. 顶点表n个表项
b. 无向图的所有边表共有2m个表项
c. 有向图的所有边表共有m个表项
2.3 思考
邻接表表示无向图时,每条边对应两个边表结点,不利于插入、删除等操作。如何修改邻接表,使得每条边只对应一个边表结点?
3. 邻接多重表
3.1 如何用邻接多重表存储无向图
邻接多重表由顶点表和边表组成,每条边只对应一个边表结点。 注意:
(1)顶点表结点包含data域和edge域(指向与该顶点相关联的第一条边)
(2)边表结点包含5个域:
mark:边访问标记
i,j:边(Vi, Vj)的两个顶点的标号
ilink:指向与Vi相关联的下一条边
jlink:指向与Vj相关联的下一条边
练习:
- 如何用邻接多重表存储有向图
邻接表表示有向图时只保存顶点的入边表或出边表。
有向图的邻接多重表同时能表示顶点的出边和入边,而不增加边表节点数。
有向图的邻接多重表包含顶点表和边表:
(1)顶点表的每个表项包含3个域:
data:表示顶点信息
edge1:指向以该顶点为始点的边表的第一条边;
edge2:指向以该顶点为终点的边表的第一条边。
(2)边表结点结构与无向图的邻接多重表相同,但ilink指向以Vi为始点的下一条边;jlink指向以Vj为终点的下一条边。
练习:
三、基于邻接表表示的Graph结构
四、图的遍历
从图的任一顶点出发沿着边访问图的每个顶点恰好一次。
- 两种遍历方法
(1)宽度优先遍历
先访问出发顶点(第1层),然后访问与出发顶点相邻的所有顶点(第2层),接着访问与第2层顶点相邻的所有未被访问的顶点(第3层),……,依此类推。
练习:
宽度优先次序:ABCFDE
思考:尝试使用队列设计宽度优先遍历算法
分析:
队头<f, v>出队,若v未被访问,则访问v,<f, v>加入树,将与v相邻的未访问过的顶点依次入队。
教材算法思路:
a. 选一个未被访问过的顶点加入队列尾部;
b. while(队列不空)
删除队列头结点v;若v未被访问,则访问v,且将与v相邻的所有未访问过的顶点依次加入队列尾部。
c. 如果还有顶点没被访问过,则转a
宽度优先生成树:
宽度优先遍历中,将每次前进路过的结点和边记录下来,得到以出发点为根的树,称为宽度优先生成树。
宽度优先生成树林:
如果必须从多个结点出发才能遍历所有结点,则得到多棵生成树,称为宽度优先生成树林。
练习:
(2)深度优先遍历
深度优先次序:ABDCFE
思考:尝试使用栈实现深度优先?
分析:栈顶<f, v>出栈;若v未访问,则访问v, <f, v>加入树,将与v相邻的未访问过的顶点依次入栈。
教材算法思路:
a. 选一个未被访问过的顶点加入栈;
b. while(栈不空)
栈顶结点v出栈,若v未被访问,则访问v,且将与v相邻的所有未访问过的顶点依次入栈
c. 如果还有顶点没被访问过,则转a
深度优先生成树:
深度优先遍历中,将每次前进路过的结点和边记录下来,得到以出发点为根的树,称为深度优先生成树。
深度优先生成树林:
如果必须从多个结点出发才能遍历所有结点,则得到多棵生成树,称为深度优先生成树林。
练习:
五、最小代价生成树
引例:顶点代表城市,边代表光缆,边的权值代表光缆成本。如何铺设光缆,使得城市之间可以通过光缆进行通信,且铺设成本最低?
- 什么是最小代价生成树?
加权图中,生成树的边的权值之和最小的生成树称为最小代价生成树。
注:图的生成树不唯一
-
跨U和V-U的最小边一定在最小生成树里
基于该定理,设计一个最小生成树算法。 -
Prim算法
Prim算法的时间复杂度O(n^2) -
kruskal算法
练习:
六、单源最短路径问题
定义:给定单个源点s,求s到其它各顶点的最短路径。
注意:s到i的最短路径上i的前面是j,那么s到j的最短路径加上<j,i>是s到i的最短路径。
思考:如何存储最短路径?
使用Dist数组:Dist[i].length = s到顶点i的最短距离,Dist[i].pre = 最短路径上顶点i的前一个顶点。
将顶点集分成A、B两组,A中的顶点已经确定从源到该顶点的最短路径,B中顶点尚未确定从源到该顶点的最短路径。设V0为源点,初始时A={V0}, B = V \ A.
给A和B中的顶点v定义距离值,记为v.length。对于A中的顶点v,v.length为从源到v的最短路径长;对于B中的顶点v, v.length为只允许以A中顶点为中间点的从源到v最短路径长。
B中距离值最小的顶点Vm, 其距离值就是从源到该顶点的最短路径长度。
Dijkstra算法
Dijkstra算法的时间复杂度是O(n^2)
七、每队顶点间最短路径问题
计算每队顶点之间的最短路径
方法1. 以网格中的每个顶点为源点,分别调用Dijkstra(),时间复杂性为O(n^3).
方法2. Floyd算法,时间复杂性是O(n^3)
例如:
思考:如何保存这些结果?
Floyd算法:
假设n个顶点{V0, V1, ……, Vn-1}, 边的权值非负,adj为相邻矩阵。
算法的结果保存在矩阵D和矩阵path中:
八、有向无回路图DAG
引例:
大型的工程可以划分为多个工序,大多数工序与其他工序相关联,即某些工序必须等到其它一些工序完成后才能开始。DAG是描述这类问题的有效工具。
两种DAG:
(1)AOV:顶点表示活动
(2)AOE:边表示活动
一、AOV网
- 什么是AOV网?
用顶点表示活动,边表示活动间的先后关系。活动v1是活动v2的前提,当且仅当AOV网中v1是v2的前驱。
- AOV网的拓扑排序
对AOV网 G=<V,E>的顶点进行拓扑排序–把所有顶点排成一个序列V1, V2, ……,Vn,使得该序列满足:若<Vi, Vj>是G的边,则序列中Vi位于Vj之前。这个序列称为G的一个拓扑排序。
拓扑排序算法:
(1)从图中选择一个入度为0的顶点并输出;
(2)从图中删除此顶点及其所有的出边;
(3)重复1和2, 知道输出图的全部顶点。如果不能输出图的全部顶点,说明图中有回路。
结论:
(1) 通过拓扑排序,可以为AOV网上的活动安排合理的先后执行次序。
(2)拓扑序列通常不唯一
(3)DAG图存在拓扑序列,不存在拓扑序列的有向图存在回路。
二、AOE网
用边表示活动,顶点表示活动的开始和结束(事件),边上的权表示活动所需的时间长度。活动e1是活动e2的前提,当且仅当AOE网中e1的终点是e2的起点。
-
顶点事件的发生?
只有当顶点入边上的活动全部结束后,顶点事件才可以发生;只有当顶点事件发生后,顶点出边上的活动才可以启动。
-
完成工程的最短时间?
表示工程的AOE网有一个入度为0的顶点,称为源点,代表工程的始点,有一个出度为0的顶点,称为汇点,代表工程的终点。完成工程的最短时间为从源点到汇点的最长路径长度。
关键路径:AOE网从源点到汇点的最长路径称为关键路径。
完成工程的最短时间为关键路径长度。 -
最早发生时间?
假设AOE网的顶点集为{V1, V2, ……, Vn},V1为源点(始点),Vn为汇点(终点)。
事件Vj的最早发生时间e(j):从源点到Vj的最长路径长。
-
最晚发生时间
假设AOE网的顶点集为{V1, V2, ……, Vn},V1为源点(始点),Vn为汇点(终点)。
在不耽误工程进度的前提下,事件vi最晚发生时间I(i):关键路径长减去从Vi到汇点的最长路径长。
-
关键事件
最早和最晚发生时间相等的时间为关键事件。
-
关键活动
活动ai=<Vj, Vk>的最早启动时间ae(i)=e(j)最晚启动时间aI(i) = I(k)-t(j, k)
最早和最晚启动时间相等的活动为关键活动。
求关键活动的算法
(1)对顶点进行拓扑排序
(2)按顶点的拓扑次序从前往后,计算每个顶点Vi的最早发生时间e(i)
(3)按顶点的拓扑次序从后往前,计算每个顶点Vk的最晚发生时间l(k)
(4)若l(k)-t(j, k) = e(j),则<Vj, Vk>是关键活动。
注意:
某路径是关键路径的充要条件是其上的每个活动是关键活动(其上每个顶点是关键事件)
总结: