目录
物理结构
邻接矩阵
矩阵压缩
关联矩阵
邻接表
邻接多重表
图搜索
广度优先搜索BFS
边分类
连通域分解
无权最短路径
深度优先搜索DFS
边分类
双连通分量
优先级优先搜索PFS
单源最短路径问题
Dijkstra算法
Bellman-Ford算法
所有结点对最短路径问题
Floyd-Warshall算法
拓扑排序
并查集
最小生成树
Prim算法
Kruskal算法
G=(V,E),|V|=n,|E|=e
有向无环图DAG:Directed Acyclic Graph
Euler环路:经过图中各边一次且恰好一次的环路
Hamilton环路:经过图中各顶点一次且恰好一次的环路
Euler公式:n-e+f-c=1,其中n、e、f和c分别为平面图的顶点、边、面和连通域的数目
物理结构
邻接矩阵
一个n阶方阵A,E中有一条边(u,v)当且仅当A[u][v]=1
O(n^2)空间
矩阵压缩
(联系数值代数中的相关压缩技巧)
关联矩阵
一个n×e阶矩阵B,E中有一条边i从顶点u出发当且仅当B[u][i]=-1,E中有一条边j从顶点v进入当且仅当B[v][j]=1
- 对于稠密图,邻接矩阵优
- 对于稀疏图,关联矩阵优
(《算法导论》练习22.1-7)
对于无向图,BB^T的非对角元与A相同,对角元是对应顶点的度数
对于有向图,BB^T表明两个顶点间有多少条有向边
O(ne)空间
邻接表
每个顶点引出一个List,存放从自身出发的各边及权
O(n+e)空间
邻接矩阵 | 邻接表 | |
适用场合 | 经常检测边的存在 经常做边的插入/删除 图的规模固定 稠密图 | 经常计算顶点的度数 顶点数目不确定 经常做遍历 稀疏图 |
邻接多重表
图搜索
顶点的活跃期:[dTime,fTime]
- dTime:discovered time,顶点被发现的时刻
- fTime:finished time,顶点完成访问的时刻
前沿集:在所有已访问到的顶点中,仍有邻居尚未访问者
广度优先搜索BFS
template<typename Tv, typename Te> void Graph<Tv, Te>::BFS(Rank v, Rank& dClock) {
Queue<Rank> Q;
status(v) = DISCOVERED;
Q.enqueue(v);
dTime(v) = dClock++; //起点
for (Rank fClock = 0; !Q.empty(); ) { //在Q变空之前,反复地
if (dTime(v) < dTime(Q.front())) //dTime的增加,意味着开启新的一代,因此
dClock++; //dTime递增
fClock = 0; //fTime复位
v = Q.dequeue(); //取出队首顶点v,并
for (Rank u = firstNbr(v); -1 != u; u = nextNbr(v, u)) { //考查v的每一个邻居u
if (UNDISCOVERED == status(u)) { //若u尚未被发现,则发现之
status(u) = DISCOVERED; //发现该顶点
Q.enqueue(u);
dTime(u) = dClock;
type(v, u) = TREE;
parent(u) = v; //引入树边,拓展BFS树
} else //若u已被发现(正在队列中),或者甚至已访问完毕(已出队列),则
type(v, u) = CROSS; //将(v, u)归类于跨边
}
status(v) = VISITED;
fTime(v) = fClock++; //至此,v访问完毕
}
}
}
辅助队列Q
- 从左到右到起点s的距离单调非降
- 首末顶点到起点s的距离至多差1
- 相邻顶点到起点s的距离至多差1
边分类
树边端点到起点s的距离差1
横向边端点到起点s的距离至多差1
空间复杂度O(n+e)
- 队列长度O(n)
- 边和顶点的状态O(n)+O(e)
时间复杂度O(n+e)
连通域分解
无权最短路径
【2014-THU-Fin】在无向连通图G中选定一个顶点 s,并将各顶点v到s的距离记作dist(v)(特别地,dist(s)=0)。于是在G.BFS(s)过程中,若辅助队列为Q,则dist(Q.front())+1≥dist(Q.rear())始终成立。(√)
深度优先搜索DFS
template<typename Tv, typename Te> void Graph<Tv, Te>::DFS(Rank v, Rank& clock) {
dTime(v) = ++clock;
status(v) = DISCOVERED; //发现当前顶点v
for (Rank u = firstNbr(v); -1 != u; u = nextNbr(v, u)) {//考察v的每一邻居u
switch (status(u)) { //并视其状态分别处理
case UNDISCOVERED: { //u尚未发现,意味着支撑树可在此拓展
type(v, u) = TREE;
parent(u) = v;
DFS(u, clock);
break;
} case DISCOVERED: { //u已被发现但尚未访问完毕,应属被后代指向的祖先
type(v, u) = BACKWARD;
break;
} default: { //u已访问完毕(VISITED,有向图),则视承袭关系分为前向边或跨边
type(v, u) = dTime(v) < dTime(u) ? FORWARD : CROSS;
break;
}
}
}
status(v) = VISITED;
fTime(v) = ++clock; //至此,当前顶点v方告访问完毕
}
对应迭代改写
辅助栈S
深度不小于DFS树的最大深度,即O(n)
边分类
- 树边(u,v):status(v) = UNDISCOVERED
- 后向边(u,v):status(v) = DISCOVERED
- 后向边数<=图中回路数
- 一条后向边可能划分出多条回路
- 多条回路可能共用一条后向边
- 有后向边yi'di
- 后向边数<=图中回路数
- 前向边(u,v):status(v) = VISITED,dTime(u) < dTime(v)
- 在当前DFS树中u是v的祖先
- 横向边(u,v):status(v) = VISITED,dTime(u) > dTime(v)
- u在v所在分支被访问完后到达,表明边已经跨分支,不在DFS树中
空间复杂度O(n+e)
- 队列长度O(n)
- 边和顶点的状态O(n)+O(e)
时间复杂度O(n+e)
(《算法导论》练习22.3-8)
(《算法导论》练习22.3-9)
双连通分量
(《算法导论》练习22-2)
【2010-THU-Fin】某有向图的邻接矩阵如下,现从顶点1出发做DFS遍历,遇多顶点歧义时编号小者优先。试在表标出各边的分类结果(树边T,前向边F,后向边B,跨边C)
1 2 3 4 1 2 3 4
【2014-THU-Fin】我们知道,因同一顶点的邻居被枚举的次序不同,同一有向图 G 所对应的 DFS 森林未必唯一。然而只要起始于 G 中某顶点 s 的某次 DFS 所生成的是一棵树,则起始于 s 的任何一次 DFS 都将生成一棵树。()
【2014-THU-Fin】有向图的DFS不仅起点任意,而且每一步迭代往往都会有多个顶点可供选择,故所生成的DFS森林并不唯一确定,且其中所含()的数量也可能不同。
A. 树边
B. 前向边
C. 后向边
D. 跨越边
E. 以上皆非
【2016-THU-Fin】如果把朋友圈视为一无向图,那么即使A君看不到你给B点的赞,你们仍可能属于同一个双联通分量。(√)
【2014,2016-THU-Fin】在有向图G中,存在一条自顶点v通向u的路径,且在某次DFS中有dTime[v]<dTime[u],则在这次DFS所生成的DFS森林中,v必是u的祖先。(×)
优先级优先搜索PFS
【2016-THU-Fin】在图的优先级搜索过程中,每次可能调用多次prioUpdater,但累计调用次数仍为O(e)。()
单源最短路径问题
给定根节点的PFS树(SPT,Shortest Path Tree)
- 根节点到其他节点的单源最短路径
必须无负权回路
Dijkstra算法
无负权边的单源最短路径问题
基于命题:最优路径的前缀一定也是最优前缀
- 若存在负权边,随着边的加入,路径长度不一定是单调递增的,局部最优不能保证全局最优
- 选定起点s,记为T1
- 对于任意Tk,在所有与Tk邻接的顶点中选择优先级最高的顶点加入顶点集,对应边加入边集,记为Tk+1
- 优先级=顶点到树顶点的最小距离+s到接应顶点的最小距离
- 只有邻接顶点被加入了顶点集,自身优先级才会更新
- Tn即为PFS树
(《算法导论》练习24.3-2)
Bellman-Ford算法
PFS唯一性问题
所有结点对最短路径问题
Floyd-Warshall算法
拓扑排序
-
DAG:有极大元的偏序集
-
Zorn引理:若偏序集S的任何链都有上界,则S有极大元
-
-
拓扑排序:全序集
顺序输出零入度顶点
逆序输出零出度顶点
并查集
最小生成树
(《数据结构与算法分析 Java语言描述》练习9.17)
Cayley公式
- Prim算法:顶点视角,扩张最小生成树的顶点
- Kruskal算法:边视角,扩张最小生成树的边
(《算法导论》练习23.1-1)
(《算法导论》练习23.1-4)
因轻量级边仅是横跨切割的所有边中的极小边而非最小
(《算法导论》练习23.1-6)
Prim算法
Kruskal算法
最小生成树唯一性问题
- 由Kruskal算法正确性,如果各边权值互异,那么最小生成树是唯一的
- 由《算法导论》练习23.1-6,如果Prim算法执行中没有选择,那么最小生成树是唯一的
- 如果各边权值不互异,那么最小生成树可能不唯一
- 两个算法执行出的结果也不唯一
- Prim算法适合稀疏图
- Kruskal算法适合稠密图