文章目录
- 搜索与图论
-
- [深度优先搜索 DFS](#深度优先搜索 DFS)
-
- [[843. n-皇后问题 - AcWing题库](https://www.acwing.com/problem/content/845/)](#843. n-皇后问题 - AcWing题库)
- [宽度优先搜索 BFS](#宽度优先搜索 BFS)
-
- [[844. 走迷宫 - AcWing题库](https://www.acwing.com/problem/content/description/846/)](#844. 走迷宫 - AcWing题库)
- 树与图的存储
-
- [[846. 树的重心 - AcWing题库](https://www.acwing.com/problem/content/description/848/)](#846. 树的重心 - AcWing题库)
- 树与图的深度优先遍历
- 树与图的宽度优先遍历
-
- [[847. 图中点的层次 - AcWing题库](https://www.acwing.com/problem/content/849/)](#847. 图中点的层次 - AcWing题库)
- 拓扑排序
- 最短路
-
- 建图!!!
- [朴素 Dijkstra](#朴素 Dijkstra)
-
- [[849. Dijkstra求最短路 I - AcWing题库](https://www.acwing.com/problem/content/851/)](#849. Dijkstra求最短路 I - AcWing题库)
- 堆优化Dijkstra
- Bellman_Ford
-
- [[853. 有边数限制的最短路 - AcWing题库](https://www.acwing.com/problem/content/855/)](#853. 有边数限制的最短路 - AcWing题库)
- SPFA
-
- [[851. spfa求最短路 - AcWing题库](https://www.acwing.com/problem/content/853/)](#851. spfa求最短路 - AcWing题库)
- [[852. spfa判断负环 - AcWing题库](https://www.acwing.com/problem/content/854/)](#852. spfa判断负环 - AcWing题库)
- Floyd
-
- [[854. Floyd求最短路 - AcWing题库](https://www.acwing.com/problem/content/856/)](#854. Floyd求最短路 - AcWing题库)
搜索与图论

数据结构 | 空间 | |||||
---|---|---|---|---|---|---|
DFS | 回溯、剪枝 | stack | O(h) | 不具有最短性 | 最优性剪枝,不合法剪枝 | |
BFS | queue | O(2^h) | 最短路 |
深度优先搜索 DFS
DFS是一个执着的人会一直尽可能往回走,走到头再回溯,回溯的过程能往前邹尽可能往前走---yxc
顺序,搜索流程是一个树
843. n-皇后问题 - AcWing题库

搜索顺序:
-
全排列思路:枚举每一行皇后可以放在哪个位置,注意剪枝(提前判断,当前方案不合法直接回溯)O(n*n!)
c++#include <iostream> using namespace std; const int N = 20; int n; char g[N][N]; bool col[N], dg[N], udg[N]; void dfs(int u) { if(u == n) { for(int i = 0; i < n; i ++) puts(g[i]); puts(""); return ; } for(int i = 0; i < n; i ++) { if(!col[i] && !dg[u + i] && !udg[n - u + i]) { g[u][i] = 'Q'; col[i] = dg[u + i] = udg[n - u + i] = true; dfs(u + 1); col[i] = dg[u + i] = udg[n - u + i] = false; g[u][i] = '.'; } } } int main() { cin >> n; for(int i = 0; i < n; i ++) for(int j = 0; j < n; j ++) g[i][j] = '.'; dfs(0); return 0; }
-
每一个位置放和不放O(2n2)
c++#include <iostream> using namespace std; const int N = 20; int n; char g[N][N]; bool col[N], dg[N], udg[N], row[N]; void dfs(int x, int y, int s) { if(y == n) y = 0, x ++; if(x == n) { if(s == n) { for(int i = 0; i < n; i ++) puts(g[i]); puts(""); } return; } //不放皇后 dfs(x, y + 1, s); //放 if(!row[x] && !col[y] && !dg[y + x] && !udg[n + y - x]) { g[x][y] = 'Q'; row[x] = col[y] = dg[y + x] = udg[n + y - x] = true; dfs(x, y + 1, s + 1); row[x] = col[y] = dg[y + x] = udg[n + y - x] = false; g[x][y] = '.'; } } int main() { cin >> n; for(int i = 0; i < n; i ++) for(int j = 0; j < n; j ++) g[i][j] = '.'; dfs(0, 0, 0); return 0; }
宽度优先搜索 BFS
稳重的人,每一次扩展一层
dp可以理解为特殊的最短路,即没有环的最短路
框架:
844. 走迷宫 - AcWing题库
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 110;
int g[N][N], d[N][N];
int dx[] = {-1, 0, 1, 0};
int dy[] = {0, 1, 0, -1};
int n, m;
int bfs()
{
queue<PII> q;
q.push({0, 0});
memset(d, -1, sizeof d);
d[0][0] = 0;
while(!q.empty())
{
auto t = q.front();
q.pop();
for(int i = 0; i < 4; i ++)
{
int x = t.first + dx[i], y = t.second + dy[i];
if(x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1)
{
d[x][y] = d[t.first][t.second] + 1;
q.push({x, y});
}
}
}
return d[n - 1][m - 1];
}
int main()
{
cin >> n >> m;
for(int i = 0; i < n; i ++)
for(int j = 0; j < m; j ++)
cin >> g[i][j];
cout << bfs() << endl;
return 0;
}
prv存储从哪个点过来的
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 110;
int g[N][N], d[N][N];
PII prv[N][N];
int dx[] = {-1, 0, 1, 0};
int dy[] = {0, 1, 0, -1};
int n, m;
int bfs()
{
queue<PII> q;
q.push({0, 0});
memset(d, -1, sizeof d);
d[0][0] = 0;
while(!q.empty())
{
auto t = q.front();
q.pop();
for(int i = 0; i < 4; i ++)
{
int x = t.first + dx[i], y = t.second + dy[i];
if(x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1)
{
d[x][y] = d[t.first][t.second] + 1;
prv[x][y] = t;
q.push({x, y});
}
}
}
int x = n - 1, y = m - 1;
while(x || y)
{
cout << x << " " << y << endl;
auto t = prv[x][y];
x = t.first, y = t.second;
}
return d[n - 1][m - 1];
}
int main()
{
cin >> n >> m;
for(int i = 0; i < n; i ++)
for(int j = 0; j < m; j ++)
cin >> g[i][j];
cout << bfs() << endl;
return 0;
}
树与图的存储
树是无环连通图


-
有向图
-
无向图(特殊有向图)
- 邻接矩阵 g[a][b]
- 邻接表

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = N * 2;
int h[N], e[M], ne[M], idx;
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
int main()
{
memset(h, -1, sizeof h);
}
846. 树的重心 - AcWing题库
重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。

//dfs(u)返回以u为根的树的点的数量
//sum 记录当前u为根的树的大小
// res存删除当前点之后,每个连通块大小的最大值
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = N * 2;
int h[N], e[M], ne[M], idx;
bool st[M];
int n;
int ans = N;//全局答案,最大的最小值
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
//返回以u为根的树的点的数量
int dfs(int u)
{
st[u] = true;//已经被搜
int sum = 1, res = 0;
//sum 记录当前u为根的树的大小
// res存删除当前点之后,每个连通块大小的最大值
for(int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];//j存储当前节点对应图的编号
if(!st[j])
{
int s = dfs(j);//s表示当前子树的大小
res = max(res, s);
sum += s;
}
}
res = max(res, n - sum);
ans = min(ans, res);
return sum;
}
int main()
{
memset(h, -1, sizeof h);
cin >> n;
for(int i = 1; i <= n - 1; i ++)
{
int a, b;
cin >> a >> b;
add(a, b);
add(b, a);
}
dfs(1);//随便挑一个点
cout << ans << endl;
return 0;
}
树与图的深度优先遍历
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = N * 2;
int h[N], e[M], ne[M], idx;
bool st[M];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void dfs(int u)
{
st[u] = true;//已经被搜
for(int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];//j存储当前节点对应图的编号
if(!st[j]) dfs(j);
}
}
int main()
{
memset(h, -1, sizeof h);
dfs(1);//随便挑一个点
}
树与图的宽度优先遍历

847. 图中点的层次 - AcWing题库

拓扑排序
有向图才会有拓扑序列,对于每条边,起点在终点前面
有环一定没有拓扑序
可证明有向无环图一定有拓扑图,所以有向无环图也被称为拓扑图

如何求拓扑序
度:入度和出度

所有当前入度为0的点都可以作为起点
入度为零意味着没有任何一条边指向当前点
宽搜过程

848. 有向图的拓扑序列 - AcWing题库
题目概述
给定一个有向图,包含 n n n 个点和 m m m 条边,可能存在重边和自环。要求输出该图的任意一个拓扑序列,如果不存在拓扑序列(即图中存在环),则输出 − 1 -1 −1。
解题思路
拓扑排序是针对有向无环图(DAG)的一种线性排序方法,使得对于图中的每一条有向边 ( u , v ) (u, v) (u,v), u u u 在排序中总是位于 v v v 的前面。如果图中存在环,则无法进行拓扑排序。
方法步骤
- 计算入度:统计每个节点的入度(即有多少条边指向该节点)。
- 初始化队列:将所有入度为0的节点加入队列。这些节点没有前置依赖,可以直接作为拓扑序列的起始点。
- 处理队列:从队列中取出一个节点,将其加入拓扑序列,并移除所有从该节点出发的边(即减少其邻居节点的入度)。如果邻居节点的入度变为0,则将其加入队列。
- 检查结果:如果拓扑序列包含所有节点,则排序成功;否则,说明图中存在环,无法进行拓扑排序。
代码解析
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = N * 2;
int h[N], e[M], ne[M], idx; // 邻接表存储图
int q[N], d[N]; // q数组模拟队列,d数组存储入度
int n, m;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
bool topsort() {
int hh = 0, tt = -1;
// 将所有入度为0的节点加入队列
for(int i = 1; i <= n; i ++) {
if(!d[i])
q[++ tt] = i;
}
while(hh <= tt) {
int t = q[hh ++]; // 取出队头节点
// 遍历该节点的所有邻居
for(int i = h[t]; i != -1; i = ne[i]) {
int j = e[i];
d[j] --; // 邻居节点的入度减1
if(d[j] == 0) // 如果入度为0,加入队列
q[++ tt] = j;
}
}
// 如果队列中的节点数等于n,说明拓扑排序成功
return tt == n - 1;
}
int main() {
memset(h, -1, sizeof h); // 初始化邻接表
cin >> n >> m;
while(m --) {
int a, b;
cin >> a >> b;
d[b] ++; // 更新节点b的入度
add(a, b); // 添加边a->b
}
if(topsort()) {
for(int i = 0; i < n; i ++)
cout << q[i] << " \n"[i == n];
} else {
cout << -1 << endl;
}
return 0;
}
代码解释
- 邻接表存储图 :使用数组模拟邻接表,
h
数组存储每个节点的头指针,e
和ne
数组分别存储边的终点和下一条边的索引。 - 入度数组
d
:记录每个节点的入度。 add
函数 :添加一条从a
到b
的边,并更新b
的入度。topsort
函数 :- 初始化队列,将所有入度为0的节点加入队列。
- 处理队列中的节点,减少其邻居节点的入度,如果邻居节点入度为0则加入队列。
- 最后检查队列中的节点数是否等于
n
,如果是则说明拓扑排序成功。
- 主函数 :读取输入,构建图,调用
topsort
函数并输出结果。
复杂度分析
- 时间复杂度 : O ( n + m ) O(n + m) O(n+m),每个节点和每条边各处理一次。
- 空间复杂度 : O ( n + m ) O(n + m) O(n+m),存储图结构和队列。
示例输入输出
输入:
3 3
1 2
2 3
1 3
输出:
1 2 3
解释:拓扑序列可以是 1 2 3
或 1 3 2
,代码输出前者。
最短路

建图!!!
考察抽象成图的过程,即建图的能力
朴素 Dijkstra
s表示当前已经确定最短距离的点
-
dist[1] = 0, dist[i] = 0x3f
-
for(int i = 1 ~ n)
-
t <- 不在s中的距离最近的点
-
s <- t
-
用t跟新其他点的距离
-
849. Dijkstra求最短路 I - AcWing题库


堆优化Dijkstra

在一堆数找最小的数,可以用堆 O(1)
在堆中修改一个数,log(n)

Bellman_Ford
如果有负权回路,最短路不一定存在


for n次 (迭代n次)
for 所有边a, b, w //松弛操作
dist[b] = min(dist[b], dist[a] + w);
dist[b] <= dist[a] + w // 三角不等式

bellmanford 可以用来求负环,时间复杂度较高
853. 有边数限制的最短路 - AcWing题库
题目分析
给定一个n个顶点m条边的有向图,图中可能存在重边和自环,边权可能为负数。要求求出从顶点1到顶点n的最多经过k条边的最短路径距离。如果无法到达顶点n,则输出"impossible"。
算法思路
这道题使用Bellman-Ford算法来解决,因为该算法特别适合处理有边数限制的最短路问题。Bellman-Ford算法的基本思想是通过松弛操作逐步逼近最短路径。
关键步骤:
- 初始化距离数组dist,将所有顶点到起点的距离设为无穷大,起点距离设为0。
- 进行k次松弛操作,每次操作遍历所有边,尝试通过该边缩短终点到起点的距离。
- 使用备份数组backup确保每次松弛操作基于上一次迭代的结果,避免"串联更新"。
- 最终检查终点距离,如果仍大于一个较大值(0x3f3f3f3f/2),则认为不可达。
代码解释
#include <bits/stdc++.h>
using namespace std;
const int N = 550, M = 10010; // 定义顶点和边的最大数量
int n, m, k; // n顶点数,m边数,k边数限制
int dist[N], backup[N]; // dist存储距离,backup用于备份
struct Edge {
int a, b, w; // 边的起点、终点和权值
} edges[M];
void bellman_ford() {
memset(dist, 0x3f, sizeof dist); // 初始化距离为无穷大
dist[1] = 0; // 起点距离设为0
for(int i = 0; i < k; i++) { // 进行k次松弛
memcpy(backup, dist, sizeof dist); // 备份当前距离数组
for(int j = 0; j < m; j++) { // 遍历所有边
int a = edges[j].a, b = edges[j].b, w = edges[j].w;
dist[b] = min(dist[b], backup[a] + w); // 松弛操作
}
}
}
int main() {
cin >> n >> m >> k;
for(int i = 0; i < m; i++) {
int a, b, w;
cin >> a >> b >> w;
edges[i] = {a, b, w}; // 存储所有边
}
bellman_ford();
// 判断是否可达,注意不是直接比较0x3f3f3f3f因为有负权边
if(dist[n] > 0x3f3f3f3f / 2) puts("impossible");
else cout << dist[n] << endl;
return 0;
}
复杂度分析
- 时间复杂度:O(k*m),其中k是边数限制,m是总边数。
- 空间复杂度:O(n+m),用于存储距离数组和边集。
注意事项
- 必须使用backup数组备份上一次的dist状态,避免在同一轮松弛中多次更新导致的错误。
- 判断不可达的条件是dist[n] > INF/2,而不是dist[n] == INF,因为有负权边可能导致距离略小于INF。
- 该算法可以检测负权环,但本题有边数限制,所以不需要考虑无限松弛的情况。
SPFA
宽搜优化,队列存所有变小的节点,跟新过谁,再拿他跟新(公司业绩提高才可能加工资


851. spfa求最短路 - AcWing题库
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 2e5 + 10;
int h[N], e[N], ne[N], idx, w[N];
int dist[N];
bool st[N];
int n, m;
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
void spfa()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
queue<int> q;
q.push(1);
st[1] = true;
while(q.size())
{
int t = q.front();
q.pop();
st[t] = false;
for(int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if(dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
if(!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
while (m --)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
spfa();
if(dist[n] == 0x3f3f3f3f) puts("impossible");
else cout << dist[n] << endl;
return 0;
}
852. spfa判断负环 - AcWing题库


#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 2e5 + 10;
int h[N], e[N], ne[N], idx, w[N];
int dist[N];
bool st[N];
int cnt[N];
int n, m;
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
bool spfa()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
queue<int> q;
for(int i = 1; i <= n; i ++)
{
q.push(i);
st[i] = true;
}
while(q.size())
{
int t = q.front();
q.pop();
st[t] = false;
for(int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if(dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1;
if(cnt[j] >= n) return true;
if(!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
return false;
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
while (m --)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
if(spfa()) puts("Yes");
else puts("No");
return 0;
}
Floyd
求多源汇最短路
初始化:邻接矩阵存储图 d[i, j]
for(int k = 1; k <= n; k ++)
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++)
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
854. Floyd求最短路 - AcWing题库
#include <bits/stdc++.h>
using namespace std;
const int N = 220;
int d[N][N];
int n, m, k;
void floyd()
{
for(int k = 1; k <= n; k ++)
{
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= n; j ++)
{
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
}
}
}
int main()
{
cin >> n >> m >> k;
memset(d, 0x3f, sizeof d);
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= n; j ++)
{
if(i == j) d[i][j] = 0;
}
}
for(int i = 1; i <= m; i ++)
{
int a, b, w;
cin >> a >> b >> w;
d[a][b] = min(d[a][b], w);
}
floyd();
while(k --)
{
int a, b;
cin >> a >> b;
if(d[a][b] > 0x3f3f3f3f / 2) puts("impossible");
else cout << d[a][b] << endl;
}
return 0;
}