图论
1. 建图
分为邻接表 和 邻接矩阵两种 邻接表用的比较多
2. 拓扑排序
拓扑排序就是统计每个点的入度,入度为0的一定在前面, 遍历每个入度为0的点,把他的邻接点的入度减1, 重复这个操作就得到拓扑排序的序列了;
此外,只有有向无环图才有拓扑排序 所以也是判断有没有环的一种方式;
3. 最短路问题
最短路问题要写的可就多了 也是趁这机会好好复习一下吧
最短路问题主要记的有以下几个方法:
堆优化dijkstra, Bellman-Ford, SPFA, Floyd; 其实还有双端队列BFS(0 - 1 边权的最短路问题), 等会也写上吧
1. 堆优化版dijkstra: 适用于正(其实是非负)边权
记住dijkstra是出栈(出队)时确定最小距离就好了
2. Bellman-Ford
现在看来, Bellman-Ford 算法最适合解决的问题是有边数限制的最短路问题 剩下比如有负权边的最短路问题,负环的判断等等都可以用SPFA来解决
3. SPFA:(正负都可以)
spfa 的代码其实和堆优化dijkstra很像 因为都是用堆去优化那个找当前最小距离的过程;
这两个的判断条件不一样 要记清:
dijkstra 是出队就保证最短距离
但是spfa是出队都没有办法保证是最短距离,只要更新就要入栈,st数组的意义在于如果栈中有这个元素就不必再次加入了(因为肯定是要重算)
1. SPFA 求最短路
2. SPFA 判断是否存在负环
相较于求最短路 额外围护一个cnt数组,这个数组记录最短路径的节点数量 只要超过 n + 1 则证明必有负环存在
注意这里要把所有点都加入队列,dist清空or not 都无所谓 避免起点与负环不连通-> 找不到
找正环 : 把求最短路变成求最长路即可!
常用于0-1分数规划问题
除了这个之外还有个额外的trick,如果超时了的话 可以按照spfa出栈元素的次数判断是否存在负环 如果出栈元素的次数大于一个经验值(可以试一试 2*n 3*n)就可以当成有负环了
4. floyd: 多源汇最短路
基于动态规划 记住那三重循环的顺序 先遍历的是中间点k
同样的 可以将floyd退化为bool值 这样直接判断是否连通
5. 双端队列BFS: (只有0-1边权的最短路问题)
对于只有0 - 1 两种边权的最短路问题,可以用双端队列广搜来实现 不必维护一个heap;
4.最小生成树问题
最小生成树就记一个Kruskal算法就好;
Kruskal 算法
Kruskal算法基于的是并查集,整体思路很简单, 将所有边按权重从小到大排序,枚举所有边(a, b, w) 如果 a b尚未连通,就将这条边加入集合中 并统计权重就好了;
对于Kruskal算法而言 可以只求前一半 - 最小生成森林 / 后一半 - 缩点的最小生成树 都是可以的;
有这样两个性质: - 最小生成树的边数一定是n - 1 - 得到的解不仅是边权和最小,最大边权也最小
5. 二分图
二分图这一块有判断是否是二分图(染色法) 和 二分图的最大匹配(匈牙利算法)两块;
具体细分而言
匈牙利算法: 匹配 最大匹配 匹配点 增广路径
最小点覆盖 最大独立集 最小路径点覆盖 最小路径重复点覆盖问题
公式:最大匹配数 = 最小点覆盖 = 总点数 - 最大独立集 = 总点数 - 最小路径覆盖
1. 染色法判断二分图
一个图是二分图 等价于 图中没有奇数环
染色法判断二分图的实质就是DFS;一个边连接的两个点不会是同一个颜色
0 - 未染色 1 - 颜色1 2 - 颜色2
2. 二分图的最大匹配-匈牙利算法
这个要好好背一下;
3. 最小路径重复点覆盖问题
方法:
求原图的传递闭包
原图的最小路径重复点覆盖 = 新图的最小路径覆盖 = 新图的 n - 最大匹配
6. 差分优化
...
7. 最近公共祖先(LCA)
LCA问题:在一个有根树中,最近的公共祖先(自己也算)
1. 倍增法
方法如下:
预处理:
fa[i, j]表示从i开始 向上走2^j步所到达的节点预处理:
depth[i]表示i节点所在的深度查询: 求
lca(a, b)如果
a的深度比b大 -> 先跳到同一层保证在同一层下,共同向上跳,直到跳到
LCA的下面一层
大概的框架就是这样,此外在代码实现上还有许多细节(比如哨兵节点0的应用等等)
depth显然用dfs bfs都可以,而fa[i,j] 可以用递推求解 fa[i, j] = fa[fa[i, j - 1], j - 1] (爸爸的爸爸是爷爷)
倍增法求LCA的复杂度:预处理o(nlogn) 查询一次:o(logn)
2. Tarjan 离线求LCA
这一定要上图了 
8. 连通分量
1. 有向图的强连通分量
tarjan缩点
此外 还有些额外的结论
id的倒序 就是DAG的拓扑排序
设DAG的入度为0点数量为p 出度为0点的数量为q 则使得整个图为强连通量 最少需要添加的边数为
max(p, q)
2. 无向图的双连通分量
无向图的双连通分量分为两种:
边的双连通分量 e_dcc: 不含桥的连通分量
点的双连通分量 v_dcc: 不含割点的连通块
1. 边的双连通分量问题
和有向图的强连通分量求法类似,也是引入一个时间戳的概念 维护dfn 和 low两个时间戳数组
如何找到桥? x -> y dfn[x] < low[y]
如何找到所有边的双连通分量? 用一个栈帮助找到连通分量内的所有点
桥(割边) 对于一个无向连通图来说, 如果我们删除一条边之后, 图变得不再连通了, 那么我们九讲这条边称为桥.
定义 一个极大的(图中不存在任何一个非连通块中的点加入连通块之后, 该连通块依然是连通块)不包含桥的连通块
性质 对于一个边连通分量来说, 我们不管删除连通分量的哪一条边, 它都是连通的. 对于一个边连通分量来说, 任意两点之间至少包含两条不相交的路径(两条路径上无公共边) 这是一个充要条件
tarjan算法求边双连通分量
无向图不存在横叉边
因为双向不存在后面能到前面, 前面不能到后面的情况, 那么如果存在后面连到前面的一条边, 那么dfs序遍历的时候, 我们就已经将后面的点遍历到了, 就不可能再后面再遍历到后面那个点了.
时间戳 dfn[u]: 遍历到当前节点的编号 low[u]: 不经过走过来的那条边(父边)能走到的最小编号节点
判断桥 对于x, y之间的一条边, 如果low[y]能到x的祖先节点, 那么就不是桥, 若low[y] = y, 即是桥 dfn[x]<low[y](严格小于)
找e-dcc代码思路:
将所有的桥删掉剩下的都是边连通分量了
用栈存储搜到的所有点, 当搜到一个点的dfn[x] == low[x], 即这个点上面的一条边就为桥, 将栈中所有点标记即可
找e-dcc代码模板:
2. 点的双连通分量问题
点双连通分量
割点 每一个割点都至少属于两个连通分量 对于一个无向连通图来说, 如果我们删除某个点之后, 该连通图不连通了, 那么我们就称其为该联通图的割点.
定义 一个极大的不包含割点的连通块 tarjan算法求点双连通分量
时间戳 dfn[u]: 意义同边双连通分量 low[u]: 每个顶点在不经过父节点的情况下能到的最小编号顶点, 如果low[u]大于等于dfn[father] (就是不能到已经遍历过的点, 即father的祖先节点), father为割点.
判断(找)割点
9. 欧拉回路与欧拉路径
算法会简单一些hhh
背景:哥尼斯堡七桥问题 / 一笔画问题
欧拉路径 给定一张无向图,若存在一条从节点 S 到节点 T 的路径,恰好不重不漏地经过每条边一次(可以重复经过图中的节点),则称该路径为 S 到 T 的欧拉路。
欧拉回路 若存在一条从节点 S 出发的路径,恰好不重不漏地经过每条边一次(可以重复经过图中的节点),最终回到起点 S,则称该路径为欧拉回路。
定义一下:
入度(din):进入该点的边的数量。
出度(dout):走出该点的边的数量。
度(deg):每个点的 入度 + 出度。
奇结点(奇顶点):连接该结点的边的数量为奇数
偶结点(偶顶点):连接该结点的边的数量为偶数
判断依据:
对于无向图,所有边都是连通的
存在欧拉路径的充分必要条件:度数为奇数的点只能有
0 or 2个存在欧拉回路的充分必要条件:度数为奇数的点只能有
0个
对于有向图,所有边都是连通的
存在欧拉路径的充分必要条件:
要么所有点的出度等于入度
要么除了两个点之外,所有点的出度等于入度,并且满足剩下的两个点 :一个出度比入度多1(起点)一个入度比出度多1(终点)
存在欧拉回路的充分必要条件:所有点的出度均等于入度
求欧拉回路 注意这里的删边优化
dfs搜索
在递归搜索结束后加入答案序列
最后的欧拉回路是答案序列的倒序
输出字典序最小的路径 : 按照字典序来搜
Last updated