目录
图
定义:
基本术语:
图的存储结构
邻接矩阵
邻接表
十字链表
邻接多重表
图的遍历
深度优先搜索(Depth First Search,DFS)
广度优先搜索(Breadth First Search,BFS)
图的应用
最小生成树
普利姆算法
克鲁斯卡尔算法
最短路径
单源最短路径(Dijkstra迪杰斯特拉算法):
所有顶点间的最短路径(Floyd弗洛伊德算法):
有向无环图应用
拓扑排序
关键路径
图
定义:
图(Graph)G由两个集合V和E组成,记为G=(V, E),其中V是顶点的有穷非空集合,E是V中顶点偶对的有穷集合,这些顶点偶对称为边。V(G)和E(G)通常分别表示图G的顶点集合和边集合,E(G)可以为空集。若EG)为空,则图G只有顶点而没有边。
对于图G,若边集E(G)为有向边的集合,则称该图为有向图;若边集E(G)为无向边的集合,则称该图为无向图。
基本术语:
子图:假设有两个图G=(V,E)和G1=(V1,E1);如果V1V,E1E,则称G1为G的子图。
完全图:任意两个顶点都有一条边相连。(指的是无向图)
无向完全图和有向完全图:对于无向图,若具有n(n-1)/2条边,则称为无向完全图;对于有向图,若具有n(n-1)条弧,则称为有向完全图。
稀疏图和稠密图:有很少条边或弧(如e<nlogn)的图称为稀疏图,反之称为稠密图
权和网:在实际应用中,每条边可以标上具有某种含义的数值,该数值称为该边上的权,这些权可以表示从一个顶点到另一个顶点的距离或耗费。这种带权的图通常称为网。
邻接点:对于无向图G,如果图的边(v, v1)E,则称顶点v和v1互为邻接点,即v和v1相邻接。
关联(依附):边/弧与顶点之间的关系;边(v, v')依附于顶点v和v1,或者说边(v, v1)与顶点v和v1相关联。
顶点的度:与该顶点相关联的边的数目,记为TD(v);在有向图中,顶点的度等于该顶点的入度与出度之和,顶点v的入度是以v为终点的有向边的条数,记作ID(v),顶点的出度是以v为始点的有向边的条数,记作OD(v)。
路径:接续的边构成的顶点序列
路径长度:路径上边或弧的数目/权值之和
回路(环):第一个顶点和最后一个顶点相同的路径
简单路径:除路径起点和终点可以相同外,其余顶点均不相同的路径
简单回路(简单环):除路径起点和终点相同外,其余顶点均不相同的路径。
连通:两个顶点之间有路径,则称这两个顶点是连通的。
连通图:对于图中任意两个顶点都是连通的,则称该图是连通图
强连通图:在有向图中对于任意两个顶点是连通的,则称该图是强连通图。
连通分量:指的是无向图中的极大连通子图(极大连通子图意思为该子图是G的连通子图,将G的任何不在该子图中的顶点加入,子图不再连通)
强连通分量:有向图的极大强连通子图
极小连通子图:该子图是G的连通子图,在该子图中删除任何一条边子图不再连通
连通图的生成树:包含无向图所有顶点的极小连通子图
有向树:有一个顶点的入度为0,其余顶点的入度均为1的有向图称为有向树
生成森林:对于非连通图,由各个连通分量的生成树的集合
图的存储结构
图没有顺序存储结构,但可以使用二维数组(邻接矩阵)来存储
邻接矩阵
邻接矩阵表示法思路:
- 邻接矩阵为顶点个数阶的方阵
- 若两个顶点之间邻接(即有边相连)即在矩阵对应位置为1,否则为0
特点:
- 无向图的邻接矩阵是对称的;有向图的可能是不对称的
- 顶点i的度就是第i行(列)中1的个数
- 完全图的邻接矩阵中,对角元素为0,其余均为1
- 对于有向图,顶点的出度就是第i行元素之和;顶点的入度就是第i列元素之和;顶点的度就是第i行元素之和+第i列元素之和
- 对于网的邻接矩阵写法同上,只是在0或1基础上乘以权即可
采用邻接矩阵表示法创建无向网:
该算法的时间复杂度为O().
邻接矩阵的优点:
- 便于判断两个顶点之间是否有边
- 便于计算各个结点的度
缺点:
- 不便于增加和删除顶点
- 不便于统计边的数目,需要扫描邻接矩阵所有元素才能统计完毕,时间复杂度为O()
- 空间复杂度高 O(),对于 稀疏图而言尤其浪费空间。
邻接表
邻接表表示法:
邻接表是图的一种链式存储结构。在邻接表中,对图中每个顶点v1建立一个单链表,把与v1相邻接的顶点放在这个链表中。邻接表中每个单链表的第一个节点存放有关顶点的信息,把这一结点看成链表的表头,其余结点存放有关边的信息。这样的邻接表有两部分组成:表头结点和边表。
特点:
- 顶点按编号顺序将顶点数据存储在一维数组中
- 线性链表存储关联同一顶点的边
- 邻接表是不唯一的,因为存储边的链表中顺序可变
- 若无向图中有n个顶点、e条边,则其邻接表需n个头节点和2e个表结点。适宜存储稀疏图。
邻接表表示有向图:
原理同上,就是顶点对应链表只存放出度的结点(逆邻接表就是存放入度的结点)
特点:
- 顶点的出度就为单链表中的节点个数
- 顶点Vi的入度为整个单链表中邻接点域值是i-1的节点个数
邻接表存储结构:
采用邻接表表示法创建无向图:
该算法的时间复杂度:O(n+e)
邻接表的优点:
- 便于增加和删除顶点
- 便于统计边的数目,按顶点表顺序扫描所有边表可得到边的数目,时间复杂度为O(n+e)
- 空间效率高
缺点:
- 不便于判断顶点之间是否有边
- 不便于计算有向图各个顶点的度(十字链表解决此问题);对于无向图每条边都要存储两遍(邻接多重表解决此问题),删除一条边需要找到此边的两个结点不太方便
区别:
- 对于任一确定的无向图,邻接矩阵是唯一的(行列号与定点编号是一致的),但邻接表不唯一(链接次序与顶点编号无关)
- 邻接矩阵的空间复杂度为O(),而邻接表的空间复杂度为O(n+e)
- 邻接矩阵多用于稠密图;而邻接表多用于稀疏图
十字链表
十字链表是有向图的另一种链式存储结构。可以看作是将有向图的邻接表和逆邻接表结合起来得到的一种链表。在十字链表中,对应于有向图中每一条弧有一个结点,对应于每个顶点也有一个结点。
十字链表是为了更好的计算有向图的出度与入度,所有在顶点结点中多加了个链域,分别指向以该结点为弧头或弧尾的第一个弧结点。
在弧结点中有5个域:其中尾域(tailvex)和头域(headvex)分别指示弧尾和弧头这两个顶点在图中的位置,链域hlink指向弧头相同的下一条弧,而链域tlink 指向弧尾相同的下一条孤,,info域指向该弧的相关信息。(例如权)
有向图的十字链表存储形式:
建立十字链表的时间复杂度和建立邻接表是一样的
邻接多重表
邻接多重图是无向图的另一种链式存储结构。
在邻接多重表中,每一条边用一个结点表示,它由如图所示的6个域组成。其中, mark为标志域,可用以标记该条边是否被搜索过; ivex和 jvex为该边依附的两个顶点在图中的位置; ilink 指向下一条依附于顶点ivex的边; jlink 指向下一条依附于顶点jvex的边,info指向和边相关的各种信息的指针域。
每一个顶点也用一个结点表示,它由如图图所示的两个域组成。其中,data域存储和该顶点相关的信息,firstedge域指示第一条依附于该顶点的边。
无向图的邻接多重存储表示:
图的遍历
从已给的连通图中某一顶点出发,沿着一些边访遍图中所有的顶点,且使每个顶点仅被访问一次,叫做图的遍历,是图的基本运算。
特点:图中可能存在回路,且图的任一顶点都可能与其他结点相通,在访问某个顶点之后可能会沿着某些边又回到了曾经访问过的顶点。
避免重复访问的解决思路:设置辅助数组visited[n],用来标记每个被访问过的顶点。初始状态visited[i]为0,顶点i被访问,改visited[i]为1,防止被多次访问
深度优先搜索(Depth First Search,DFS)
深度优先搜索类似于树的先序遍历
思路:(一条路走到黑,没路了再返回去寻路)
(1)从图中某个顶点v出发,访问v。
(2)找出刚访问过的顶点的第一个未被访问的邻接点,访问该顶点。以该顶点为新顶点,重复此步骤,直至刚访问过的顶点没有未被访问的邻接点为止。
(3)返回前一个访问过的且仍有未被访问的邻接点的顶点,找出该顶点的下一个未被访问的邻接点,访问该顶点。
(4)重复步骤(2)和(3),直至图中所有顶点都被访问过,搜索结束。
深度优先搜索遍历连通图:
深度优先搜索遍历非连通图:
采用邻接矩阵表示图的深度优先搜索遍历
采用邻接表表示图的深度优先搜索遍历
算法分析:
当用邻接矩阵表示图时,查找每个顶点的邻接点的时间复杂度为O() ,其中n为图中顶点数。当以邻接表做存储结构时,深度优先搜索遍历图的时间复杂度为O(n+e),e为图中边数
结论:
- 稠密图适于在邻接矩阵上进行深度遍历
- 稀疏图适于在邻接表上进行深度遍历
广度优先搜索(Breadth First Search,BFS)
广度优先搜索思路:
1.从图中某个顶点v出发,访问v
2.依次访问v的各个未曾访问过的邻接点
3.分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问。重复步骤3,直至图中所有已被访问的顶点的邻接点都被访问到。
广度优先搜索的算法实现:
算法分析:广度优先搜索遍历图的时间复杂度和深度优先搜索遍历相同,即当用邻接矩阵存储时,时间复杂度为O() ;用邻接表存储时,时间复杂度为O(n+e)。两种遍历方法的不同之处仅仅在于对顶点访问的顺序不同。
DFS与BFS算法效率比较:
- 空间复杂度相同,都是O(n)(借用了堆栈或队列)
- 时间复杂度只与存储结构(邻接矩阵或邻接表)有关,而与搜索路径无关。
图的应用
最小生成树
生成树:所有顶点均有边连续在一起,但不存在回路的图
一个图可以有许多棵不同的生成树
生成树所具有的特点:
- 生成树的顶点个数与图的顶点个数相同
- 生成树是图的极小连通子图,去掉一条边则非连通
- 一个有n个顶点的连通图的生成树有n-1条边
- 在生成树中再加一条边必然形成的回路
总:生成树就是把图按照深度优先搜索或者广度优先搜索把图的顶点都遍历一遍,然后把走过的顶点和边保留下来就是一棵生成树(包括深度优先生成树和广度优先生成树)
最小生成树:给定一个网络,在该网的所有生成树中,使得各边权值之和最小的那棵生成树称为该网的最小生成树,也叫最小代价生成树。
普利姆算法
构造过程:就是在一个连通网中,先找到权值最小的一条边,然后把这条边及该边相连的两个顶点加入到最小生成树的边集和顶点集,然后把这两个顶点看成一个整体,继续找由这个整体相连的边中权值最小的,继续加入最小生成树的边集,重复以上操作,直到把所有顶点都加入到最小生成树。(时间复杂度:O()n为顶点数 适用范围:稠密图)
算法描述:
克鲁斯卡尔算法
构造过程:首先就是把连通网中所有的顶点加入到生成树,然后把所有边按权值大小进行排序,从权值小的边开始选择加入到生成树的边集,原则就是不能生成环形成回路。直到边集中有n-1个元素。 (时间复杂度:O(eloge) e为边数适用范围;稀疏图)
最短路径
最短路径通常用于交通网络的问题;交通网络用有向网表示,顶点表示地点,弧表示两个地点有路连通。最短路径与最小生成树不同,路径上不一定有n个结点,也不一定含n-1条边。
单源最短路径(Dijkstra迪杰斯特拉算法):
思路:首先从顶点集中选一个顶点加入S中,然后找该顶点的所有直达边,若不能直达的顶点则记为无穷,从这些边中找出权值最小的,将该顶点加入到S中,继续重复上述步骤;(直达的理解就是两个顶点之间有一条边或者该路径中只包含一个非S集合中顶点)重复以上步骤就可以得到到各个顶点之间的最短路径。(求某个顶点到其他各个顶点的最短路径)
时间复杂度是O() 。
所有顶点间的最短路径(Floyd弗洛伊德算法):
思路:首先初始化一个矩阵,矩阵中如果两个顶点之间是可以直达的就写相对应的权值,若不能直达就写无穷,对角线均为0;然后依次添加一个顶点,继续看两个顶点之间的路径,如果有变小就替换初始值;直到把所有元素都添加完即可。(注意:添加元素之后就是路径中有可以有该元素,不能有没有添加的元素就是直达)
有向无环图应用
有向无环图:一个无环的有向图称作有向无环图,简称DAG图。有向无环图是描述一项工程或系统的进行过程的有效工具。通常把计划、施工过程、生产流程、程序流程等都当成一个工程。
拓扑排序
AOV网:用一个有向图表示一个工程的各子工程及其相互制约的关系,其中以顶点表示活动,弧表示活动之间的优先制约关系,称这种有向图为顶点表示活动的网,简称AOV网(Activity On Vertex network)。
AOV网特点:
- 若<i,j>是网中有向边,则i是j的直接前驱;j是i的直接后继。
- AOV网中不允许有回路,因为如果有回路存在,则表明某项活动以自己为先决条件,显然这是荒谬的
拓扑排序:在AOV网没有回路的前提下,我们将全部活动排列成一个线性序列,使得若AOV网中有弧<i,j>存在,则在这个序列中,i一定排在j的前面,具有这种性质的线性序列称为拓扑有序序列,相应的拓扑有序排序的算法称为拓扑排序。
拓扑排序的过程:
- 在有向图中选一个没有前驱的顶点且输出
- 从图中删除该顶点和所有以它为尾的弧
- 重复上述两步,直至全部顶点均已输出;或者当图中不存在无前驱的顶点为止。
检测AOV网中是否存在环方法:对有向图构造其顶点的拓扑有序序列,若网中所有顶点都在它的拓扑有序序列中,则该AOV网必定不存在环。
算法分析:对有n个顶点和e条边的有向图而言,建立求各顶点入度的时间复杂度为O(e);建立零入度顶点栈的时间复杂度为O(n);在拓扑排序过程中,若有向图无环,则每个顶点进一次栈,出一次栈,入度减1的操作在循环中总执行e次,所以总的时间复杂度为O(n+e)
关键路径
AOE网:用一个有向图表示一个工程的各子工程及其相互相互制约关系,以弧表示活动,以顶点表示活动的开始或结束事件,称这种有向图为边表示活动的网,简称为AOE网(Activity On Edge)
源点:在网中只有一个入度为0的点称为源点
汇点:在网中只有一个出度为0的点称为汇点
关键路径:路径长度最长的路径(关键路径上的活动叫做关键活动)
路径长度:路径上各活动持续时间之和。
要想确定关键路径,首先定义4个描述量:
- 事件vj的最早发生时间:ve(vj);思路就是从源点开始往后计算,计算从该源点到该事件的每条路径所花费时间,然后取其最大值就是最早发生时间
- 事件vj的最迟发生时间:vl(vj);思路就是从汇点开始往前计算,计算从汇点到事件的每条路径所花费时间,然后取其最小值就是最迟发生时间
- 活动ai的最早开始时间:e(i) ;e(i)=ve(j)
- 活动ai的最迟开始时间:l(i);vl(k)-wj.k
l(i)-e(i)——表示完成活动ai的时间余量
关键活动——关键路径上的活动,即l(i)==e(i)
求关键路径步骤:首先计算出上面4个描述量,然后找出关键活动,即找到关键路径