图论概念简述
图
图(graph)是一个二元组, 其中非空, 称为点集, 称为边集.
当与均为有限集合时称为有限图, 否则称为无限图.
图可以被简单分类为无向图, 有向图和混合图.
每条边可以赋权.
图的顶点数量被称为图的阶.
相邻
无向图中, 若存在连边, 则称或与是关联的(或相邻的), 与是相邻的. 的邻居(邻域)指的是其所有相邻顶点组成的集合, 记作. 对于点集来说, 其邻居(邻域)指的是其所有顶点的邻居取并集得到的集合, 记作.
简单图
自环: 对于边, 若则称是一个自环.
重边: 若图中存在两条相同的边, 则称它们为一组重边.
无自环和重边的图被称为简单图. 否则被称为多重图.
具有至少两个顶点的简单无向图中一定存在度相同的点.
度数
与顶点关联的边数称为顶点的度, 记作, 特别地, 每个自环会对度数产生的贡献, 而非.
对于无向简单图, 有.
若, 则称为孤立点; 若, 则称为叶节点/悬挂点. 若为偶数, 则称为偶点, 若为奇数, 则称为奇点.
若与其他所有顶点相连, 则称为支配点. 在无向简单图中支配点的度数为.
握手定理(图论基本定理): 对于无向图, 所有顶点的度数之和等于两倍边的数目.
推论: 任意图中奇点有偶数个.
图中所有节点度数的最小值称为图的最小度, 记作; 所有节点度数的最大值称为图的最大度, 记作.
在有向图中, 出边数量被称为出度, 记作, 入边数量被称为入度, 记作. 显然有.
对于任何有向图, 有.
若图中所有节点的度数均为, 则称为k-正则图.
如果给定序列, 可以找到某个图使得以为度数列, 则称是可图化的; 如果能找到简单图以其为度数列, 则称是可简单图化的.
路径
途径(walk): 连接一连串顶点的边的序列. 边的数量称作途径的长度.
迹(trail): 若途径中不存在相同的边, 则称为迹.
路径(path)或称简单路径(simple path): 若迹中连接的点序列两两不同, 则称为路径.
回路(circuit): 若迹起点与终点相同, 则称为回路.
环(cycle)或称简单回路(simple circuit): 若回路删去起点和终点后形成一条路径, 则称该回路为一个环.
子图
若图满足, , 则称是的子图, 记作.
若对于, 满足, 只要, 就有, 则称是的导出子图/诱导子图(induced subgraph). 容易发现一个图的导出子图只由点集决定, 因此的点集为的导出子图可以记作.
若对于, , 则称是的生成子图(spanning subgraph).
若无向图的某个生成子图为k-正则图, 则称是的一个k-因子.
对于有向图的某个导出子图, 若中所有点在中的出边都被保留到了中, 则称是的一个闭合子图.
连通
对于无向图: 若存在点到点的途径, 则称与连通. 由定义, 一个点与自己是连通的. 若无向图中任意两个顶点均连通, 则称是连通图, 具有连通性. 极大连通子图称为连通分量.
对于有向图: 若存在到的途径, 则称可达; 若节点两两互相可达, 则称该图为强连通图; 若将有向边替换为无向边后形成连通图, 则称该图为弱连通图. 类似地可以定义强连通分量和弱连通分量的概念.
建图
邻接表
使用结构体定义边, 使用的时候直接结构化绑定auto [y, dy] : adj[x]即可.
struct Edge { int to; int w;};// 带权图vector<Edge> adj[N];inline void buildgraph(int n) { for (int i = 0; i <= n; i++) adj[i].clear();}遍历图
DFS
// vector<Edge> adj[N];auto dfs = [&](int s) -> void { stack<int> st; vector<bool> vis(n + 1, 0); st.push(s); while (!st.empty()) { int x = st.top(); st.pop(); if (vis[x]) continue; vis[x] = 1; for (const auto& [y, dy] : adj[x]) { if (!vis[y]) st.push(y); } }};BFS
// vector<Edge> adj[N];auto bfs = [&](int s) -> void { queue<int> q; vector<bool> vis(n + 1, 0); q.push(s); while (!q.empty()) { int x = q.front(); q.pop(); for (const auto& [y, dy] : adj[x]) { if (!vis[y]) { q.push(y); vis[y] = 1; } } }};拓扑排序
得到有向无环图(DAG)的拓扑序列. 能够判断环. 进一步地, 拓扑排序可以用于解决有向无环图上的类DP问题. 如果某节点的状态只由其前驱节点决定, 那么就可以在拓扑排序中使用dp进行状态转移.
// vector<int> adj[N];int ind[N];vector<int> topseq;auto toposort = [&]() { queue<int> q; for (int i = 1; i <= n; i++) { if (ind[i] == 0) { q.push(i); } }
while (!q.empty()) { int x = q.front(); q.pop(); topseq.push_back(x); for (const auto& y : adj[x]) { // dp[y] = f(dp[x]); ind[y]--; if (ind[y] == 0) q.push(y); } } return topseq.size() == n; // 拓扑排序是否成功, 不成功则说明有环.};最短路问题
全源最短路
Floyd算法
基于简单的dp思想: x到y的最短路 = min(x经过k到y的最短路, x不经过k到y的最短路). 于是枚举每个k即可. 复杂度. 可以处理负权边, 不能处理负环.
ll f[N][N];auto floyd = [&](int n) -> void { // 初始化 for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { f[i][j] = INFLL; } f[i][i] = 0; for (const auto& [y, dy] : adj[i]) { f[i][y] = dy; } }
// dp for (int k = 1; k <= n; k++) { for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { f[i][j] = min(f[i][j], f[i][k] + f[k][j]); } } }};单源最短路
正权图dijkstra
// const ll INFLL = 1e18;// using ll = long long;// vector<Edge> adj[N];// vector<ll> dis;
auto dijkstra = [&](int s = 1) -> void { dis.assign(n + 1, INFLL); using pli = pair<ll, int>; priority_queue<pli, vector<pli>, greater<pli>> q; q.emplace(0, s); dis[s] = 0; while (!q.empty()) { auto [dx, x] = q.top(); q.pop(); if (dx > dis[x]) continue; // lazy deletion for (const auto& [y, dy] : adj[x]) { if (dis[x] + dy < dis[y]) { dis[y] = dis[x] + dy; q.emplace(dis[y], y); } } }};欧拉图
得名自哥尼斯堡七桥问题的解决者欧拉.
欧拉通路: 通过图中每条边恰好一次的迹(trail). 欧拉回路: 通过图中每条边恰好一次且起点等于终点的迹.
如果一个图中存在欧拉回路, 则称该图为欧拉图. 如果一个图中不存在欧拉回路但存在欧拉通路, 则称该图为半欧拉图.
欧拉图的性质对于连通图, 以下几个性质是等价的:
- 是欧拉图;
- 的所有顶点度数都是偶数;
- 可以被分解为若干条不共边回路的并.
trick合集
- 对于网格图来说, 确定某点(x, y)的方法不一定需要直接知晓x和y这两个数. 假如我们可以知道(x + y, x - y)(即在主对角线上的坐标和副对角线上的坐标), 那么也可以确定唯一的一个点. 进一步说, 对于任意两个垂直的坐标轴y = -kx 和 y = 1 / k * x, 通过(x + ky, kx - y)可以确定唯一的一个点. 更进一步说, 两个坐标轴不需要垂直, 只需要保证两个坐标轴不平行, 都可以确定唯一一个点, 即(ax + by, cx + dy)其中ad != bc(行列式不为0), 即可确定唯一一个坐标.
- 曼哈顿距离的本质是一个斜放的正方形, 于是我们常可以根据曼哈顿距离确定主对角线上的坐标和副对角线上的坐标, 从而确定原点坐标. 2153B