树与图论
树与图论
1 基础概念 & 性质
1.1 图
- 图由顶点和边组成,一条边连接两个顶点
- 自环:一条边链接相同的两个顶点
- 重边:两条边所链接的两个顶点都相同
- 点和边都可以带权,称为点权、边权
- 点的度数(有向图分入度、出度)
- 稠密图中 \(m\) ~ \(O(n^2)\)(\(m\) 边数,\(n\) 点数)
- 稀疏图中 \(m\) ~ \(O(n)\)
- ~ 表示接近于
1.2 树
- 所有顶点之间相互连通且不存在环
- 一棵树有 \(n\) 个顶点,\(n-1\) 条边
- 两点之间仅存在一条简单路径
1.3 存图方式
1.3.1 邻接矩阵
- \(G[i][j]=w\) 存储 \((i,j)\) 之间有一条边权为 \(w\) 的边
- 适用于稠密图
1.3.2 邻接表
- 用 \(vector\) 存储每个结点的所有出边
- 适用于稀疏图
1.4 树的性质
1.4.1 二叉树的遍历
- 前序遍历:根左右
- 中序遍历:左根右
- 后序遍历:左右根
- 给定二叉树,求遍历顺序时模仿递归函数进行模拟
- 根据分治思想,给定 中序遍历(必须有) 和其他一个遍历,可以倒推出最后一种遍历或原二叉树
1.4.2 树的直径
- 树上的一条路径,满足其边权之和最大
- 如果边权均非负的求法
- 随意选择一个结点进行 DFS,找到距离它最远的结点 \(s\)
- 从 \(s\) 开始进行 DFS,找到距离它最远的结点 \(t\)
- \(s \rightarrow t\) 即为树的直径
- 例题:P1099
- 选择的路径长度越长,偏心距不会上升
- 使用双指针维护尽可能长的路径
2 生成树
2.1 概念
- 在 \(n\) 个结点,\(m\) 条边的图上,选择其中 \(n-1\) 条边构成一棵树,使得所有结点相互连通
- 当生成树所有边权之和最小时,称作最小生成树
- 当生成树所有边权之和最大时,称作最大生成树
2.2 构建生成树
- 一开始有 \(n\) 个独立的结点,每个结点自身构成一个连通块
- 每次插入一条边就会将两个连通块合并
- 判断是否联通,使用并查集确认每次是将两个连通块合并
2.3 构建最小生成树
- 贪心策略(Kruskal 算法):将边权从小到大排序,依次考虑是否选取
- 将边权从小到大排序
- 依次扫描每一条边 \((u,v,w)\)
- 如果 \(u,v\) 属于同一连通块,那么舍弃
- 如果 \(u,v\) 属于不同连通块,那么连边,并查集合并
- 时间复杂度 \(O(m \log m)\)
- 从边出发,适用于稀疏图
- 搜索策略(Prim 算法)
- 建立 \(dis\) 数组表示当前生成树中的点到该点的最短边长
- 首先加入 1 号点
- 更新 \(dis\) 数组
- 从 \(dis\) 数组中选择最小的且未加入生成树的结点加入生成树
- 如果没有构建完成,回到第三步
- 时间复杂度 \(O(n^2)\)
- 从点出发,适用于稠密图
- 例题:P2872
- 结点之间两两连边,边权为点之间的距离
- 对于一定要选的 \(m\) 条边,将边权设置为 \(0\) 即可
- AC 记录
- 例题:P2330
- 让最大边权最小:考察 Kruskal 算法本质
- 学名:最小瓶颈生成树
3 最短路问题
3.1 单源最短路
3.1.1 边权非负 - Dijkstra 算法
3.1.1.1 问题描述
在一张有 \(n\) 个结点 \(m\) 条边的带非负边权的图上,求从起点 \(s\) 到其他结点的最短路径
3.1.1.2 不使用堆优化(稠密图)
- 定义 \(dis\) 数组表示每一个结点到该结点的最短距离
- 每次从未标记的节点中选择距离出发点最近的结点,标记,收录到最优结点集合中
- 计算刚加入结点和临近结点的距离,如果新的距离更优,那么更新答案
- 时间复杂度 \(O(n^2)\) 适用于稠密图
3.1.1.3 使用堆优化(稀疏图)
- 考虑到优化前的第二步可以使用优先队列优化(小根堆)
- 定义小根堆的方式
- 重载运算符
priority_queue<int, vector<int>, greater<int>()> q;
- 时间复杂度 \(O(m \log m)\) 适用于稀疏图
3.1.2 负权图
3.1.2.1 问题描述
基本同上,只是边权可以为负
3.1.2.2 Bellman-ford 算法流程
- 定义 \(dis\) 数组表示每一个结点到该结点的最短距离
- 对每一条边进行松弛操作
- 重复上一步骤 \(n-1\) 次
- 时间复杂度 \(O(nm)\)
3.1.2.3 队列优化(SPFA 算法)
- 定义 \(dis\) 数组表示每一个结点到该结点的最短距离
- 从队列中取出点 \(u\),松弛所有与 \(u\) 相连的边 \(u,v\),若松弛成功且 \(v\) 不在队列中,则将 \(v\) 加入队列中
- 重复上述操作直至队列为空
- 需要注意,SPFA 算法的时间复杂度为 \(O(nm)\)
3.1.3 没有最短路的情况(负环)
- 如果图上存在一个回路(环),它的边权之和为负数,那么无法求得最短路
- 判断是否存在负环:最短路经过不少于 \(n\) 条边
- 不要把 SPFA 的队列改成栈(奇技淫巧)
- 例题:P1629
- 从邮局跑 Dijkstra 求得邮局到所有结点的最短路
- 从其他结点返回邮局:反向建立所有边的最短路
3.2 多源最短路(出发点不固定)
3.2.1 问题描述
- 求出每一对结点 \((i,j)\) 之间的距离
3.2.2 Floyd 算法
- 基于动态规划思想
- 定义 \(f[k][i][j]\) 表示经过若干编号不超过 \(k\) 的结点,从结点 \(i\) 到结点 \(j\) 的最短路
- 转移
- 经过若干个编号不超过 \(k-1\) 的结点从 \(i\) 到 \(j\)
- 从结点 \(i\) 到 \(k\) 再到 \(j\)
-
\[f[k][i][j]=\min(f[k-1][i][j],f[k-1][i][k]+f[k-1][k][j]) \]
- 枚举顺序:\(k\rightarrow i\rightarrow j\)
- 优化掉数组第一维:$$f[i][j]=\min(f[i][j],f[i][k]+f[k][j])$$
- 例题:P6464
- 使用 Floyd 求出全源最短路
- 枚举一对结点 \((i,j)\) 造传送门(边权为 \(0\) 的边),此时结点 \(a,b\) 的距离可以表示为 $$d(a,b)=\min(d(a,b),d(a,i)+d(j,b),d(a,j)+d(i,b))$$
- 时间复杂度 \(O(n^4)\)
- 例题:P8794
- 二分 + Floyd
- 随着时间的流逝,指标 \(P\) 一定单调不增,所以可以用二分答案来计算日期
4 拓扑排序
4.1 问题描述
对有向无环图(\(DAG\))上的所有结点排序,满足排在前面的结点不能依赖与排在后面的结点
4.2 一般方法
- 将所有入度为 \(0\) 的结点插入队列
- 取出队首元素 \(u\),将其计入答案
- 删除与 \(u\) 相连的所有边 \((u,v)\),若此时 \(v\) 的入度为 \(0\) 则将其插入队列
- 若队列非空则继续进行第二第三步