【算法 02】一题学习BFS和DFS算法

ops/2024/10/19 9:34:09/

BFSDFS_0">一题学习BFS和DFS算法


请添加图片描述

洛谷题目解析:【深基18.例3】查找文献

题目背景

小K热衷于在洛谷博客上阅读文章并探索其中的知识。每篇文章都可能包含指向其他博客文章的参考文献链接。小K的求知欲非常强,如果他阅读了某篇文章,他一定会去查看这篇文章的所有参考文献(但前提是这些参考文献他之前还没看过)。现在,我们帮助小K设计一种方法,确保他能不重复、不遗漏地阅读完所有他能看到的文章。

题目描述

洛谷博客共有n(n≤105)篇文章(编号为1到n)和m(m≤106)条参考文献引用关系。小K已经打开了编号为1的文章。我们需要通过深度优先搜索(DFS)和广度优先搜索(BFS)两种方式,来模拟小K阅读文章的过程,并输出遍历的结果。

输入输出格式

输入格式

  • 第一行包含两个整数nm,分别表示文章总数和参考文献关系数。
  • 接下来m行,每行包含两个整数X,Y,表示文章X有参考文献Y

输出格式

  • 第一行输出DFS遍历的结果。
  • 第二行输出BFS遍历的结果。

样例

样例输入

8 9  
1 2  
1 3  
1 4  
2 5  
2 6  
3 7  
4 7  
4 8  
7 8

样例输出

1 2 5 6 3 7 8 4  
1 2 3 4 5 6 7 8

解题思路

深度优先搜索(DFS)

DFS是一种用于遍历或搜索树或图的算法。它从根节点开始,尽可能深地搜索树的分支,直到达到叶子节点,然后回溯到前一个节点,继续探索尚未探索的分支。在DFS中,我们使用递归来实现这一过程。

BFS_57">广度优先搜索(BFS

BFS也是一种遍历或搜索图或树的算法。它从根节点开始,首先访问根节点的所有相邻节点,然后对每个相邻节点,再访问它们的所有未被访问的相邻节点,以此类推,直到所有节点都被访问。在BFS中,我们通常使用队列来实现这一过程。

代码实现

以下是C++的实现代码,包含了DFS和BFS的具体实现:

#include<iostream>  
#include<vector>  
#include<queue>  
#include<algorithm>  
using namespace std;  const int MAXN = 100010;  
vector<int> graph[MAXN];  
bool visitedDFS[MAXN], visitedBFS[MAXN];  // DFS实现  
void dfs(int node) {  if (visitedDFS[node]) return;  visitedDFS[node] = true;  cout << node << " ";  for (int neighbor : graph[node]) {  dfs(neighbor);  }  
}  // BFS实现  
void bfs(int start) {  queue<int> q;  q.push(start);  visitedBFS[start] = true;  while (!q.empty()) {  int current = q.front();  q.pop();  cout << current << " ";  for (int neighbor : graph[current]) {  if (!visitedBFS[neighbor]) {  q.push(neighbor);  visitedBFS[neighbor] = true;  }  }  }  
}  int main() {  int n, m;  cin >> n >> m;  for (int i = 0; i < m; i++) {  int from, to;  cin >> from >> to;  graph[from].push_back(to);  }  // 由于题目要求先访问编号较小的文章,我们不需要对邻接表排序  dfs(1);  cout << endl;  fill(visitedBFS, visitedBFS + MAXN, false); // 清除BFS的访问标记  bfs(1);  return 0;  
}

当然,以下是这段代码中算法部分的详细讲解:

1. 图的表示

代码使用了一个邻接表(vector<int> g[MAX];)来表示图。g[i] 是一个存储了所有与节点 i 相邻的节点编号的 vector。这种表示法适用于稀疏图,因为它只存储实际存在的边,而不是为每对可能的节点都保留一个空间(如邻接矩阵)。

2. 深度优先搜索(DFS)

dfs 函数实现了深度优先搜索算法。它从给定的起始节点 u 开始,递归地探索图:

  • 首先检查节点 u 是否已经被访问过(通过 used[u] 数组)。如果是,则直接返回,避免重复访问。
  • 将节点 u 标记为已访问(used[u] = 1;)。
  • 输出节点 u 的编号。
  • 然后,对于节点 u 的每一个邻接节点 e,递归调用 dfs(e)

BFS_136">3. 广度优先搜索(BFS

bfs 函数实现了广度优先搜索算法。它从给定的起始节点 u 开始,逐层遍历图:

  • 使用一个队列 q 来存储待访问的节点。首先将起始节点 u 加入队列。
  • 当队列不为空时,循环执行以下操作:
    • 从队列中取出一个节点 uq.front()),然后将其从队列中移除(q.pop())。
    • 检查节点 u 是否已经被访问过(通过 used2[u] 数组)。如果是,则跳过当前循环的剩余部分,继续处理下一个节点。
    • 输出节点 u 的编号。
    • 将节点 u 标记为已访问(used2[u] = 1;)。
    • 对于节点 u 的每一个邻接节点 e,将其加入队列 q 中以便后续访问。

4. 输入与输出

  • 输入部分首先读取两个整数 ab,其中 a 表示图中节点的数量(尽管 g 数组的大小已经通过 MAX 定义为 100010,但 a 可能用于后续验证或处理),b 表示边的数量。
  • 然后,读入 b 条边,每条边由两个整数 cd 表示,意味着存在一条从节点 c 到节点 d 的边。这些边被添加到邻接表中。
  • 在遍历所有边之后,对每个节点的邻接节点进行排序(这可能是为了某种特定的输出顺序或后续处理的需要)。
  • 最后,从节点 1 开始分别执行 DFS 和 BFS,并输出访问节点的顺序。DFS 的输出将展示从节点 1 开始的一条深度优先遍历路径,而 BFS 的输出将展示从节点 1 开始逐层向外扩展的广度优先遍历路径。

5. 注意事项

  • usedused2 数组分别用于 DFS 和 BFS 的访问标记,以避免重复访问节点。

  • 在实际应用中,如果图是有向的,则这种表示和遍历方法是有效的。如果图是无向的,那么可能需要在添加边时同时添加两个方向的边(即 g[c].push_back(d);g[d].push_back(c);)。

  • 排序邻接节点可能是为了特定的输出要求或算法优化,但在某些情况下可能不是必需的。

  • 题目中提到“不保证编号为1的文章没有被其他文章引用”,但在这个问题中,我们是从编号为1的文章开始遍历,所以这一点不影响我们的遍历过程。

  • 在DFS中,我们使用了递归函数来深入探索每一个分支。当遇到已经访问过的节点时,我们会直接返回,避免重复遍历。

  • BFS中,我们使用了队列来存储待访问的节点。每次从队列中取出一个节点进行访问,并将其所有未访问的邻接节点加入队列中。这样可以保证我们总是先访问离起始节点最近的节点。

  • 注意,在每次使用完DFS或BFS后,我们都需要重置访问标记数组,以便下一次遍历能够正确进行。但在本题中,由于我们只进行了一次DFS和一次BFS,所以只需在BFS前重置BFS的访问标记数组即可。

  • 最后,由于题目要求输出编号较小的文章在前,而我们在读入边时,默认将每个节点的邻接节点按照输入顺序存储在了邻接表中。由于C++的vector在遍历时会按照元素被添加的顺序进行,所以自然满足了这个要求,我们无需对邻接表进行额外的排序操作。

    总结

    通过这道题,我们复习了图的深度优先搜索(DFS)和广度优先搜索(BFS)两种遍历算法,并了解了它们在实际问题中的应用。同时,我们也学会了如何使用邻接表来表示图,并如何通过递归和队列来实现DFS和BFS的遍历过程。希望这次解析能够帮助大家更好地理解和掌握这两种重要的图遍历算法


http://www.ppmy.cn/ops/90090.html

相关文章

【LLM】-13-部署Xinference平台

目录 1、部署 2、启动 3、添加自定义模型 4、启动自定义模型 5、使用命令行启动 5.1、安装 xinference 插件 5.2、启动自定义模型 5.3、注销模型 6、使用代码方式启动 6.1、代码 6.2、自定义配置文件说明 Xorbits Inference (Xinference) 是一个开源平台&#xff0c…

LeetCode 第136场双周赛个人题解

Q1. 求出胜利玩家的数目 原题链接 Q1. 求出胜利玩家的数目 思路分析 直接模拟 时间复杂度&#xff1a;O(N) AC代码 class Solution { public:int winningPlayerCount(int n, vector<vector<int>>& pick) {unordered_map<int, unordered_map<int, …

论文笔记:InternImage—基于可变形卷积的视觉大模型,超越ViT视觉大模型,COCO 新纪录 64.5 mAP!

文章信息 Title&#xff1a;InternImage: Exploring Large-Scale Vision Foundation Models with Deformable ConvolutionsPaper Link&#xff1a;https://arxiv.org/abs/2211.05778 Code Link&#xff1a;https://github.com/OpenGVLab/InternImage 写在前面 拿到文章之后先看…

Golang | Leetcode Golang题解之第313题超级丑数

题目&#xff1a; 题解&#xff1a; func nthSuperUglyNumber(n int, primes []int) int {dp : make([]int, n1)m : len(primes)pointers : make([]int, m)nums : make([]int, m)for i : range nums {nums[i] 1}for i : 1; i < n; i {minNum : math.MaxInt64for j : range…

ubantu-elasticsearch

在Ubuntu上安装Elasticsearch的步骤如下&#xff1a; 1.导入Elasticsearch公钥&#xff1a; wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add - 2.添加elasticsearch到APT源列表 echo "deb https://artifacts.elastic.co/packages/7…

淘客返利系统中的负载均衡与流量控制策略

淘客返利系统中的负载均衡与流量控制策略 大家好&#xff0c;我是微赚淘客返利系统3.0的小编&#xff0c;是个冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;在现代互联网应用中&#xff0c;负载均衡与流量控制是保证系统高可用性和稳定性的关键策略。本文将详细介…

shell脚本示例

当然&#xff01;Shell脚本中有许多常用的语句和命令&#xff0c;以下是一些基础的Shell语句和命令&#xff0c;希望能帮到你&#xff1a; 期待您的关注 目录 1. 赋值语句&#xff1a; 2. 条件语句&#xff1a; 3. 循环语句&#xff1a; 1&#xff09;for循环 2&#xff09;…

python中的log怎么打印日志

python中的log怎么打印日志 在Python中&#xff0c;打印日志通常是通过使用logging模块来完成的。logging模块提供了灵活的日志系统&#xff0c;允许你控制日志信息的输出位置&#xff08;例如&#xff0c;控制台、文件、网络等&#xff09;&#xff0c;以及日志的级别&#x…