当前位置: 首页 > news >正文

搜索与图论

文章目录

  • 搜索与图论
    • 深度优先搜索 DFS
      • [843. n-皇后问题 - AcWing题库](https://www.acwing.com/problem/content/845/)
    • 宽度优先搜索 BFS
      • [844. 走迷宫 - AcWing题库](https://www.acwing.com/problem/content/description/846/)
    • 树与图的存储
      • [846. 树的重心 - AcWing题库](https://www.acwing.com/problem/content/description/848/)
    • 树与图的深度优先遍历
    • 树与图的宽度优先遍历
      • [847. 图中点的层次 - AcWing题库](https://www.acwing.com/problem/content/849/)
    • 拓扑排序
      • 如何求拓扑序
        • 度:入度和出度
      • [848. 有向图的拓扑序列 - AcWing题库](https://www.acwing.com/problem/content/850/)
      • 题目概述
      • 解题思路
        • 方法步骤
      • 代码解析
      • 代码解释
      • 复杂度分析
      • 示例输入输出
    • 最短路
      • 建图!!!
      • 朴素 Dijkstra
        • [849. Dijkstra求最短路 I - AcWing题库](https://www.acwing.com/problem/content/851/)
      • 堆优化Dijkstra
      • Bellman_Ford
        • [853. 有边数限制的最短路 - AcWing题库](https://www.acwing.com/problem/content/855/)
          • 题目分析
          • 算法思路
          • 关键步骤:
          • 代码解释
          • 复杂度分析
          • 注意事项
      • SPFA
        • [851. spfa求最短路 - AcWing题库](https://www.acwing.com/problem/content/853/)
        • [852. spfa判断负环 - AcWing题库](https://www.acwing.com/problem/content/854/)
      • Floyd
        • [854. Floyd求最短路 - AcWing题库](https://www.acwing.com/problem/content/856/)

搜索与图论

image-20250427210949450

数据结构空间
DFS回溯、剪枝stackO(h)不具有最短性最优性剪枝,不合法剪枝
BFSqueueO(2^h)最短路

深度优先搜索 DFS

DFS是一个执着的人会一直尽可能往回走,走到头再回溯,回溯的过程能往前邹尽可能往前走—yxc

顺序,搜索流程是一个树

843. n-皇后问题 - AcWing题库

image-20250427214058154

搜索顺序:

  1. 全排列思路:枚举每一行皇后可以放在哪个位置,注意剪枝(提前判断,当前方案不合法直接回溯)O(n*n!)

    #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;
    }
    
  2. 每一个位置放和不放O(2n2)

    image-20250427215507505

    #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可以理解为特殊的最短路,即没有环的最短路

框架: image-20250427221459953

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;
}

树与图的存储

树是无环连通图

image-20250427231311814

image-20250427231615423

  • 有向图

  • 无向图(特殊有向图)

  1. 邻接矩阵 g[a][b]
  2. 邻接表

image-20250428212953701

#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题库

重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。

image-20250428215707250

//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);//随便挑一个点
}

树与图的宽度优先遍历

image-20250428214247480

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

image-20250428222101708

拓扑排序

有向图才会有拓扑序列,对于每条边,起点在终点前面

有环一定没有拓扑序

可证明有向无环图一定有拓扑图,所以有向无环图也被称为拓扑图

image-20250428223147275

如何求拓扑序

度:入度和出度

image-20250428223409112

所有当前入度为0的点都可以作为起点

入度为零意味着没有任何一条边指向当前点

宽搜过程

image-20250428223828877

848. 有向图的拓扑序列 - AcWing题库

题目概述

给定一个有向图,包含 n n n 个点和 m m m 条边,可能存在重边和自环。要求输出该图的任意一个拓扑序列,如果不存在拓扑序列(即图中存在环),则输出 − 1 -1 1

解题思路

拓扑排序是针对有向无环图(DAG)的一种线性排序方法,使得对于图中的每一条有向边 ( u , v ) (u, v) (u,v) u u u 在排序中总是位于 v v v 的前面。如果图中存在环,则无法进行拓扑排序。

方法步骤
  1. 计算入度:统计每个节点的入度(即有多少条边指向该节点)。
  2. 初始化队列:将所有入度为0的节点加入队列。这些节点没有前置依赖,可以直接作为拓扑序列的起始点。
  3. 处理队列:从队列中取出一个节点,将其加入拓扑序列,并移除所有从该节点出发的边(即减少其邻居节点的入度)。如果邻居节点的入度变为0,则将其加入队列。
  4. 检查结果:如果拓扑序列包含所有节点,则排序成功;否则,说明图中存在环,无法进行拓扑排序。

代码解析

#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] --; // 邻居节点的入度减1if(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;
}

代码解释

  1. 邻接表存储图:使用数组模拟邻接表,h数组存储每个节点的头指针,ene数组分别存储边的终点和下一条边的索引。
  2. 入度数组d:记录每个节点的入度。
  3. add函数:添加一条从ab的边,并更新b的入度。
  4. topsort函数
    • 初始化队列,将所有入度为0的节点加入队列。
    • 处理队列中的节点,减少其邻居节点的入度,如果邻居节点入度为0则加入队列。
    • 最后检查队列中的节点数是否等于n,如果是则说明拓扑排序成功。
  5. 主函数:读取输入,构建图,调用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 31 3 2,代码输出前者。

最短路

image-20250510171005642

建图!!!

考察抽象成图的过程,即建图的能力

朴素 Dijkstra

s表示当前已经确定最短距离的点

  1. dist[1] = 0, dist[i] = 0x3f

  2. for(int i = 1 ~ n)

    1. t <- 不在s中的距离最近的点

    2. s <- t

    3. 用t跟新其他点的距离

      image-20250510171348516

849. Dijkstra求最短路 I - AcWing题库

image-20250510171011639

image-20250510171021620

堆优化Dijkstra

image-20250510113456269

在一堆数找最小的数,可以用堆 O(1)

在堆中修改一个数,log(n)

image-20250510113615815

Bellman_Ford

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

image-20250510155820616

image-20250510155458186

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

image-20250510155942563

bellmanford 可以用来求负环,时间复杂度较高

853. 有边数限制的最短路 - AcWing题库
题目分析

给定一个n个顶点m条边的有向图,图中可能存在重边和自环,边权可能为负数。要求求出从顶点1到顶点n的最多经过k条边的最短路径距离。如果无法到达顶点n,则输出"impossible"。

算法思路

这道题使用Bellman-Ford算法来解决,因为该算法特别适合处理有边数限制的最短路问题。Bellman-Ford算法的基本思想是通过松弛操作逐步逼近最短路径。

关键步骤:
  1. 初始化距离数组dist,将所有顶点到起点的距离设为无穷大,起点距离设为0。
  2. 进行k次松弛操作,每次操作遍历所有边,尝试通过该边缩短终点到起点的距离。
  3. 使用备份数组backup确保每次松弛操作基于上一次迭代的结果,避免"串联更新"。
  4. 最终检查终点距离,如果仍大于一个较大值(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; // 起点距离设为0for(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),用于存储距离数组和边集。
注意事项
  1. 必须使用backup数组备份上一次的dist状态,避免在同一轮松弛中多次更新导致的错误。
  2. 判断不可达的条件是dist[n] > INF/2,而不是dist[n] == INF,因为有负权边可能导致距离略小于INF。
  3. 该算法可以检测负权环,但本题有边数限制,所以不需要考虑无限松弛的情况。

SPFA

宽搜优化,队列存所有变小的节点,跟新过谁,再拿他跟新(公司业绩提高才可能加工资

image-20250510162608789

image-20250510162925036

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题库

image-20250510164737333

image-20250510164832079

#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;
}

相关文章:

  • 用java+vert.x开发的内网穿透工具jrp-nat
  • MySQL 从入门到精通(三):日志管理详解 —— 从排错到恢复的核心利器
  • 互联网大厂Java求职面试:AI集成场景下的技术挑战与架构设计
  • 进程间通信--管道【Linux操作系统】
  • Docker、Docker-compose、K8s、Docker swarm之间的区别
  • Linux基本指令(一)
  • LeetCode LCR 007. 三数之和 (Java)
  • 服饰行业的转型“助推器”来了
  • 基于强化学习 Q-learning 算法求解城市场景下无人机三维路径规划研究,提供完整MATLAB代码
  • 8.3.监控与日志体系
  • Helix:一种用于通用人形控制的视觉语言行动模型
  • HunyuanCustom:文生视频框架论文速读
  • 【Java ee初阶】网络编程 TCP
  • AI时代的数据可视化:未来已来
  • 【Debian】关于LubanCat-RK3588s开发板安装Debian的一些事
  • 【Day 24】HarmonyOS端云一体化开发:云函数
  • 嵌入式与物联网:C 语言在边缘计算时代的破局之道
  • 【Java ee初阶】网络编程 UDP socket
  • macOS 15.4.1 Chrome不能访问本地网络
  • 比 Mac 便笺更好用更好看的便利贴
  • 中国海外发展:今年前4个月销售665.8亿元,花费305亿元拿地
  • 新城市志|上海再攻坚,营商环境没有最好只有更好
  • 海关总署统计分析司司长:4月进出口增速较一季度加快4.3个百分点
  • 北上广深均宣布下调个人住房公积金贷款利率
  • 国家发改委:目前有的核电项目民间资本参股比例已经达到20%
  • 吴清稳市场稳预期发布会十要点:谈平准基金、股市稳定、公募改革和巴菲特