图
概念
- 图 G G G由顶点集V和边集E组成, G = ( V , E ) G=(V,E) G=(V,E), V ( G ) V(G) V(G)为图 G G G顶点的非空集合, V ( E ) V(E) V(E)为图 G G G边的非空结合。 ∣ V ∣ |V| ∣V∣表示顶点个数(称为 G G G的阶), ∣ E ∣ |E| ∣E∣表示边条数。
- 图不可以为空,V 一定是非空,但 E 可以为空(可以没有边)。
- 无向图:边没有方向,简称边。(v,w) = (w,v)
-
顶点的度:该顶点连接了多少条边,记作: T D ( v ) TD(v) TD(v)。
度之和: ∑ i = 1 n T D ( v i ) = 2 e ( n 个顶点, e 条边 ) 度之和:\sum^n_{i=1}TD(v_i)=2e~~~~~~~_{(n个顶点,e条边)} 度之和:i=1∑nTD(vi)=2e (n个顶点,e条边)
-
路径:顶点到另一个顶点可以经过的一系列的其他顶点。
-
回路:走了一个环。(简单回路:所经回路没有重复出现的点)
-
简单路径:顶点不重复出现的路径。
-
简单回路:除第一个顶点和最后一个顶点外,其余顶点不重复出现的回路。
-
路径长度:总共经过几条边。
-
顶点之间距离:最短路径长度。若两个顶点之间没有路径则记为 ∞ ~\infty ∞。
-
连通:有路径则有连通。
-
- 有向图:边有方向,简称弧,箭头方向为弧头,箭尾为弧尾。(等于是向量)<v,w> ≠ <w,v>
-
顶点的度:入度和出度之和,记作: T D ( v ) = I D ( v ) + O D ( v ) TD(v)=ID(v) + OD(v) TD(v)=ID(v)+OD(v)
- 入度:顶点上有多少个箭头,记作: I D ( v ) ID(v) ID(v)。
- 出度:顶点上有多少个箭尾,记作: O D ( v ) OD(v) OD(v)。
度之和: ∑ i = 1 n I D ( v i ) + ∑ i = 1 n O D ( v i ) = 2 e ( n 个顶点, e 条边 ) 度之和:\sum^n_{i=1}ID(v_i)+\sum^n_{i=1}OD(v_i)=2e~_{(n个顶点,e条边)} 度之和:i=1∑nID(vi)+i=1∑nOD(vi)=2e (n个顶点,e条边)
-
路径:必须按照箭头方向走,其所经过的其他顶点。
-
回路:和无向图一样
-
简单路径:和无向图一样
-
路径长度:和无向图一样
-
顶点距离:和无向图一样
-
连通:两顶点间有双向箭头连接,为强连通图。
-
- 简单图:不存在重复边,不存在自己到自己的边。
- 多重图:存在重复边,存在自己到自己的边。
- 连通图:
- 无向图:全部顶点都连通。(都有边相连,至少有 n − 1 n-1 n−1 条边)(非连通图至多有 C n − 1 2 C_{n-1}^2 Cn−12 条边,多一条都是连通图)
- 有向图:任意顶点强连通,为强连通图。(强连通图至少有 n 条边(回路))
- 子图:从原图中截取出来的,记作
G
′
=
(
V
′
,
E
′
)
G'= ( V', E')
G′=(V′,E′)。
- 生成子图:包含原图所有顶点,但不一定包含所有边,记作 V ( G ′ ) = V ( G ) V( G') = V( G ) V(G′)=V(G)。
- 连通分量:
- 无向图:所有子图中的最大子图就是这个原图的连通分量。(子图包含的尽可能多)
- 有向图:所有子图中的最大强连通子图就是这个原图的强连通分量。
- 连通图的生成树:
- 无向图:包含所有顶点,但在原图的基础上去掉所有无影响的边。(极小连通子图)(生成树可能有多个)(例如:各种修路方案)
- 生成森林:在非连通图中,所有连通分量的生成树构成原图的连通森林。
- 带权图:边带上权值,其图也可称为 网。带权路径长度,路径上所有边之和。
- 无向完全图:每两个顶点之间都有一条边连接,最多有 C n 2 C_n^2 Cn2 条边。
- 有向完全图:每两个顶点之间都有两条箭头连接,最多有 2 C n 2 2C_n^2 2Cn2 条边。
- 稀疏图:边很少的图,反之为稠密图。
- 树:= 生成树
- 有向树:只有一个顶点出度为 0 (根节点),其余所有顶点的入度为 1 。(非强连通图)
存储结构
-
邻接矩阵法:(顺序存储)
-
无向图:矩阵中 1 表示两顶点相互连接,0 表示不连接。
-
有向图:矩阵中 1 表示有箭头指向,0表示没有箭头指向。(先行后列)
-
性能:空间复杂度 O ( ∣ V ∣ 2 ) O(|V|^2) O(∣V∣2) ,无向图为对称矩阵,可以采用压缩矩阵存储。
-
适用范围:适合存储稠密图。(空间复杂度高)
-
表示方式:唯一
-
性质:若图的邻接矩阵为 A ,则 A n A^n An 的的元素 A n [ i ] [ j ] A^n[i][j] An[i][j] 表示由顶点 i i i 到顶点 j j j 的长度为 n n n 的路径数目。(路径长度为几就是几个矩阵相乘)
-
-
邻接表法:(顺序 + 链式存储)
-
存储结构:
#define MAX_VERTEX_NUM 20//最大顶点个数 #define VertexType int//顶点数据的类型 #define InfoType int//图中弧或者边包含的信息的类型 typedef struct ArcNode{ int adjvex;//邻接点在数组中的位置下标 struct ArcNode * nextarc;//指向下一个邻接点的指针 InfoType * info;//信息域 }ArcNode; typedef struct VNode{ VertexType data;//顶点的数据域 ArcNode * firstarc;//指向邻接点的指针 }VNode,AdjList[MAX_VERTEX_NUM];//存储各链表头结点的数组 typedef struct { AdjList vertices;//图中顶点的数组 int vexnum,arcnum;//记录图中顶点数和边或弧数 int kind;//记录图的种类 }ALGraph;
-
空间复杂度:
- 无向图: O ( 2 e + v ) = O ( e + v ) O(2e+v)=O(e+v) O(2e+v)=O(e+v)
- 有向图: O ( e + v ) O(e+v) O(e+v)
-
适用范围:适用于存储稀疏图。
-
表示方式:不唯一(各个边在孩子中出现的顺序不唯一)
-
-
十字链表:(存储有向图)
-
空间复杂度: O ( e + v ) O(e+v) O(e+v)
-
-
邻接多重表:(存储无向图)
-
空间复杂度: O ( e + v ) O(e+v) O(e+v)
-
-
发展过程:
- 邻接矩阵:可以简单明了的反映各个顶点和边之间的关系,但对于稀疏图来说浪费空间 ⇒
- 邻接表:克服了邻接矩阵的劣势,但对于有(无)向图来说查找存在劣势 ⇒
- 邻接多重表:克服了邻接表不能高效查找无向图顶点之间是否存在边的问题;
- 十字链表法:克服了邻接表不能高效查找有向图顶点入度的问题。
邻接矩阵 | 邻接表 | 十字链表 | 邻接多重表 | |
---|---|---|---|---|
空间复杂度 | O( e^2 ) | 无向图:O( v + 2e )有向图:O( v + e ) | O( v + e ) | O( v + e ) |
适用范围 | 稠密图 | 稀疏图 | 有向图 | 无向图 |
表示方式 | 唯一 | 不唯一 | 不唯一 | 不唯一 |
计算度、出度、入度 | 必须遍历对应行、列 | 有向图度、入度不方便,其余方便 | 方便 | 方便 |
找相邻边 | 必须遍历对应行、列 | 有向图找顶点“入边”不方便,其余方便 | 方便 | 方便 |
基本操作 ( 对于邻接表和邻接矩阵 ) _{(对于邻接表和邻接矩阵)} (对于邻接表和邻接矩阵)
- 增删改查…
- 广度优先遍历(BFS):队列实现(最坏空间复杂度:
O
(
∣
V
∣
)
O(|V|)
O(∣V∣) )(树的层次遍历)
-
如果是连通图:
- 邻接矩阵法:输出的元素序列与矩阵中元素排列顺序相同。时间复杂度: O ( v 2 ) O(v^2) O(v2)
- 邻接表法:输出的元素序列与链元素的排列顺序相同。时间复杂度: O ( v + e ) O(v+e) O(v+e)
-
对于邻接矩阵遍历序列唯一,邻接表遍历数列不唯一。
-
如果是非连通图:需多次调用算法。
-
广度优先生成树:通过广度优先遍历生成的树。(邻接表访问顺序不同,生成树不同)
-
广度优先生成森林:遍历非连通图生成。
-
- 深度优先遍历(DFS):用栈实现(树的先序遍历)
-
最坏空间复杂度: O ( ∣ V ∣ ) O(|V|) O(∣V∣),主要来自递归栈
-
时间复杂度:和广度一样。
-
深度优先生成树:访问序列不唯一,树不唯一。
-
深度优先生产森林:树不唯一,森林不唯一。
-
和广度一样,有向图调用算法次数一样的,只能向箭头方向访问。
-
应用
-
最小生成树(MST):
- 对象:带权无向图
- Prim算法:从某一个顶点开始构造树,每次选择代价最小的新顶点加入。(适合用于边稠密图)(可能存在多个最小生成树)
- 时间复杂度:只与顶点个数有关, O ( V 2 ) O(V^2) O(V2)
- 算法实现:挑选一个节点加入树,每次循环扫描所有节点找到距离短的,然后加入树;之后跳到新加入的节点继续循环扫描。
- Kruskal算法:每次挑选权值最小的边,让两顶点加入。(适合用于边稀疏图)
- 时间复杂度: O ( E log 2 E ) O(E\log_2E) O(Elog2E)
- 算法实现:先将所有边按权值排序,依次查找排序表,先查询边两顶点是否属于同一集合,如果不是则将两顶点加入同一集合,如果是则跳过;依次重复。(需要用到并查集,每次判定以两个顶点为根的两个集合是否元素一样,是则为同一)(总共循环 e 轮,每轮都用并查集判断 log 2 e \log_2e log2e)
- 若图的各边权值均不相同,则 MST 唯一。
- 所有 MST 的总代价一定唯一。
- 权值问题:
- 所有权值最小的边不一定出现在所有 MST 中;
- 权值最小的边一定出现在某一个 MST 中。
-
最短路径:
-
BFS算法:广度优先求无向图的最短路径。(只能用于每条边权重都一样的图)
-
Dijkstra算法:求无向图和有向图的一个节点到其他各点的最短路径。(时间复杂度: O ( v 2 ) O(v^2) O(v2))(不适合带负权值的图)(DFS搜索)
-
Floyd算法:求无向图和有向图的各个点到各个点的最短路径。(时间复杂度: O ( v 3 ) O(v^3) O(v3),空间复杂度: O ( v 2 ) O(v^2) O(v2))(可用于带负权的图)(但不能解决带负权回路的图)
//需要两个矩阵,一个为邻接矩阵,一个为路径矩阵 for (int i=0;i<MAXSIZE;i++){ //每一个节点都作为一次中转节点,总共有i个节点 for (int j=0;j<MAXSIZE;j++){ //j为行号,k为列号 for (int k=0;k<MAXSIZE;k++){ if (linjie[j][k]>linjie[i][k]+linjie[j][i]){//以Vi为中转点的路径更短 linjie[j][k]=linjie[i][k]+linjie[j][i];//更新最短路径 lujin[j][k]=i; //更新路由表(中转点) } } } }
BFS算法 Dijkstra算法 Floyd算法 无权图 √ √ √ 带权图 × √ √ 带负权图 × × √ 带负权图且回路 × × × 时间复杂度 矩阵 : O ( v 2 ) 表 : O ( v + e ) 矩阵:O( v^2 )~~~表:O( v+e ) 矩阵:O(v2) 表:O(v+e) O ( v 2 ) O( v^2 ) O(v2) O ( v 3 ) O( v^3 ) O(v3) 适用于 无权图或所有路径均相等的有权图的单源最短路径 带权图单源最短路径(用于求各个顶点间最短路径时,时间复杂度为 O ( v 3 ) O( v^3 ) O(v3)) 带权图各顶点间最短路径 -
有向无环图(DAG图)
- 有向图中不存在回路。(王道 6.4_5)
- DAG描述算数表达式:将表达式化成二叉树,合并算数表达式中的 ”相同“ 的节点。
拓扑排序
- AOV网(DAG图):表示一个工程,有向边 < v i , v j > <v_i,v_j> <vi,vj>表示活动 v i v_i vi 先于 v j v_j vj 进行。(vertex表示活动)
- 拓扑排序:从 AOV 网中找到做事的顺序,进行排序。(必须没有回路)
- 找到图中没有入度的点加入队列,
- 将当前点和当前点的出边全部删除,然后继续执行 a ,直到 AOV 为空。
- 序列可能不唯一。
- 逆拓扑排序:(DFS实现)
- 找到当前图中出度为 0 的点加入队列,
- 将当前点和当前节点的入边全部删除,然后继续执行 a ,直到 AOV 为空。
- 序列可能不唯一。
关键路径
-
AOE 网:带权有向图中,顶点表示事件,边表示执行事件所需的代价。(用edge表示活动)
-
在 AOE 网中,有些活动可以并行,但每个顶点的事件必须要等到它的入边的活动完成之后才能进行。
-
在 AOE 网中,有且自由一个开始顶点(源点,入度为0),有且自由一个结束顶点(汇点,出度为0)。
-
关键路径,关键活动:(源点到终点的最长路径)
关键路径长度是整个工程所能完成的最短时间。
-
时间余量:表示在不影响整个工程总时间的情况下,活动允许被延后执行的时间。时间余量为 0 的活动为关键活动。
-
压缩现有关键活动,可能会改变关键路径。
-
可能存在多条关键路径。