《图论算法入门:掌握DFS和BFS,理解图与树的遍历》

《图论算法入门:掌握DFS和BFS,理解图与树的遍历》
  

🎬 博主名称个人主页

🔥 个人专栏《算法通关》《Java讲解》

⛺️心简单,世界就简单

目录

序言

DFS

全排列问题

剪枝操作---n皇后问题

BFS

树与图的深度优先遍历

树,图的存储

遍历树,图

树与图的宽度优先遍历


序言

到图论这章节了,先讲讲DFS,BFS,然后讲树和图咋存储,还有树和图的DFS以及BFS,

DFS

dfs是一个执着的人(可爱捏),他一直搜索到叶子节点,然后才会回头去看别的路,然后继续一条路走到头

从数据结构来看,我们的dfs用的是

从空间来看,我们dfs空间使用是与高度成正比的O( h )

我们dfs搜索是一条路走到头,所以我们dfs不具有最短路的性质

我们来看个最经典的题,

全排列问题

我们从0开始出发,然后往下搜,当搜到n的话就说明我们搜完了输出一下就行(用path记录搜索的路径),当搜完之后,我们肯定要恢复原状,所以把st给回复,path不用是因为,下次直接就覆盖了,不用再path[ i ] = 0;·,,这里的u是层数,我们一层选一个数嘛,到第n层了,那不就是选够n个数了。

#include <iostream> using namespace std; const int N = 10; int n; int path[N]; bool st[N]; void dfs(int u){ if(u == n){ for(int i = 0; i < n; i ++) printf("%d" , path[i]); puts(""); return ; } for(int i = 1; i <= n; i ++){ if(!st[i]){ path[u] = i; st[i] = 1; dfs(u + 1); //恢复现场 st[i] = false; } } } int main(){ cin >> n; dfs(0); }

我们dfs通常还会有剪枝操作,这里我们拿八皇后问题来讲解

剪枝操作---n皇后问题

序言注意的是,对角线这里

//正对角线 y = x + b; b = x - y; 由于可能为负值,我们加个n 变为x - y +n就行
//反对角线 y = -x + b; b = x + y; 

我们这个方式是什么呢,是枚举每一行的位置,看皇后能不能在这

#include <iostream> using namespace std; const int N = 10; int n; char g[N][N]; bool col[N], dg[N], udg[N];//列,对角线,反对角线 //正对角线 y = x + b; b = x - y; 由于可能为负值,我们加个n //反对角线 y = -x + b; b = x + y; 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 + i - u] = true; dfs(u + 1); //恢复现场 g[u][i] = '.'; col[i] = dg[u + i] = udg[n + i - u] = false; } } } int main(){ cin >> n; for(int i = 0; i < n; i++){ for(int j = 0; j < n; j ++){ g[i][j] = '.'; } } dfs(0); }

BFS

是一个眼观六路,耳听八方的人,他搜索会一层一层搜索,走一次看看哪个好走,再继续搜

从数据结构来看,我们bfs用的是队列

从空间来看,我们bfs空间使用是O( 2^h )

我们BFS搜索时一层一层搜索,每次搜的都是离自己最近的路,所以我们BFS具有一种最短路的性质。

宽搜是有一个固定框架的, 就是建一个队列,然后每次搜到就扔进去,然后取出第一个元素,这个走迷宫也相当于模板了,看看就行,然后我们加了个输出的路径,用prev数组存取每个点的前一步点

#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N = 1e2+ 10; typedef pair<int, int> PII; int n, m; int g[N][N]; int d[N][N];//每个点到起点的距离 PII q[N * N], Prev[N][N]; int bfs(){ int hh = 0, tt = 0; q[0] = {0, 0}; memset(d, -1, sizeof(d)); d[0][0] = 0; int dx[4] = {-1, 0, 1, 0}; int dy[4] = {0, 1, 0, -1}; while(hh <= tt){ auto t = q[hh++];//取出第一个点,然后 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; Prev[x][y]= t;//记录一下每个点的前一个点 q[ ++tt] = {x, y};//把这个点扔进u低劣里 } } } // int x = n - 1, y = m - 1; // while(x || y){ // cout << x << ' ' << y <<endl; // auto t = Prev[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(); }

树与图的深度优先遍历

先说树的存储,树是一种特殊的图,他是无环图,那我们就直说图的遍历就行了

树,图的存储

图分为有向图和无向图

无向图就是说如果a和b有之间有边,那就是a能走到b,b也能走向a

有向图就是,说有一条边的话,只能从一个点到另一个点,是有方向的

无向图就是特殊的有向图,我们只讲有向图就行,

有向图可以用邻接表,邻接矩阵来存,我们不常用邻接矩阵,他比较浪费空间,适合稠密的图,稀疏的话就很浪费空间,所以我们一般都用邻接表

邻接表:就是每个点上都有一个单链表



我们一般都用静态链表,也是常说的链式前向星遍历树,图

我们去dfs遍历这个图,然后得到以每个节点为根节点,求去掉这个根节点后,他的子树里最大的节点数量为多少,然后我们求出这个最大值,然后用一个全局变量存储,然后和每一个去掉的节点后求得的这个值进行min比较,具体去看代码吧,看代码来思考


树与图的宽度优先遍历

这个直接找个题看就行了

#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N = 1e5 +10; int n, m, q[N], d[N]; //d记录每个节点到节点1的距离 int e[N], h[N], idx, ne[N]; void add(int a, int b){ e[idx] = b; ne[idx] = h[a]; h[a] = idx++; } int bfs(){ int hh = 0, tt = 0; q[0] = 1; memset(d, -1, sizeof d); d[1] = 0; while(hh <= tt){ int t = q[hh++]; for(int i = h[t]; i != -1; i = ne[i]){ int j = e[i]; if(d[j] == -1){ d[j] = d[t] + 1; q[ ++tt] = j; } } } return d[n]; } int main(){ cin >> n >> m; memset(h, -1, sizeof h); for(int i = 0; i < m; i++){ int a, b; cin >> a >> b; add(a, b); } cout<< bfs(); } 

Read more

一序平衡,括号归真:单括号匹配算法的优雅美学

一序平衡,括号归真:单括号匹配算法的优雅美学

一序平衡,括号归真:单括号匹配算法的优雅美学 * 一、问题本源:何为合法的单括号序列? * 🔹 核心判定准则 * 举个直观例子 * 二、思维演进:从朴素思路到极致优化 * 1. 原始思路:双计数器法(直观但冗余) * 2. 优雅优化:单计数器法(平衡值思想) * 三、图形化拆解:算法执行全过程 * 案例1:合法序列 `( () () )` * 案例2:非法序列 `( ) )` * 四、算法原理深度讲解 * 五、C++ 关键代码实现与讲解 * 代码核心亮点 * 六、算法思维的启示 * 七、结语 算法与数据结构片头 在算法的星河里,单括号匹配问题如一颗温润的贝珠,以极简的形态承载着最纯粹的算法思维。它剥离了复杂的类型干扰,只保留一对小括号 () 的序列判断,却能让我们窥见「平衡思想」与「代码优化」的本质——这是编译器语法校验、表达式合法性判断的底层基石,

By Ne0inhk
【DeepSeek】一文详解GRPO算法——为什么能减少大模型训练资源?

【DeepSeek】一文详解GRPO算法——为什么能减少大模型训练资源?

GRPO,一种新的强化学习方法,是DeepSeek R1使用到的训练方法。 今天的这篇博客文章,笔者会从零开始,层层递进地为各位介绍一种在强化学习中极具实用价值的技术——GRPO(Group Relative Policy Optimization)。如果你是第一次听说这个概念,也不必慌张,笔者会带领你从最基础的强化学习背景知识讲起,一步步剖析其来龙去脉,然后再结合实例讲解 GRPO 在实际应用中的思路和操作示例,最后再和其他近似方法对比,看看它和当下主流的 PPO(近端策略优化)等方法究竟有何区别与联系。 强烈推荐看完此帖子后再阅读另一帖——适当练习,强化记忆:【DeepSeek】大模型强化学习训练GRPO算法,你学会了吗? GRPO原论文链接:https://arxiv.org/abs/2402.03300 GRPO中译文链接:https://blog.ZEEKLOG.net/qq_38961840/article/details/145384346 为什么需要关注强化学习与策略优化? 在正式开始介绍 GRPO

By Ne0inhk
【Linux系统】解明进程优先级与切换调度O(1)算法

【Linux系统】解明进程优先级与切换调度O(1)算法

各位读者大佬好,我是落羽!一个坚持不断学习进步的学生。 如果您觉得我的文章还不错,欢迎多多互三分享交流,一起学习进步! 也欢迎关注我的blog主页:落羽的落羽 文章目录 * 一、进程优先级的概念 * 二、查看优先级信息 * 1. PRI 与 NI 的理解 * 2. 修改nice值 * 三、进程调度切换 * 1. list_head 与 prio_array 结构 * 2. 活跃140队列与过期140队列 * 四、补充概念:竞争、独立、并行、并发 一、进程优先级的概念 CPU的资源是有限的,所以CPU的运行队列中的所有进程是不可能同时得到资源的。这就是为什么运行队列是一个“队列”,而CPU分配资源的先后顺序,就是指进程的优先级。 二、查看优先级信息 使用ps -l命令,可以查看系统中更详细的进程信息:

By Ne0inhk
【优选算法篇】化繁为简,见素抱朴:从乱象中重构秩序的艺术

【优选算法篇】化繁为简,见素抱朴:从乱象中重构秩序的艺术

文章目录 * 分治专题(一):快速排序核心思想与应用 * 前言 * 第一章:快速排序的应用与延展 * 1.1 颜色分类(medium) * 解法(分治思想 - 三指针法实现三分) * C++ 代码实现 * 易错点提示 * 时间复杂度和空间复杂度 * 1.2 快速排序(medium) * 解法(数组分三块 + 随机基准元素的快速排序) * C++ 代码实现 * 易错点提示 * 时间复杂度和空间复杂度 * 1.3 快速选择算法(medium) * 解法(快速选择算法) * C++ 代码实现 * 易错点提示 * 时间复杂度和空间复杂度 * 1.4 最小的 k 个数(medium) * 解法(快速选择算法) * C++ 代码实现

By Ne0inhk