广度优先遍历的几个例题

本文介绍了广度优先遍历(BFS)在解决实际问题中的应用,包括在二维数组迷宫中寻找路径、计算二维地图上岛屿的面积、对图进行遍历以及寻找最少转机次数的问题。通过实例代码展示了BFS算法如何解决这些挑战。

题目来自《啊哈!算法》

1.广度优先搜索遍历二维数组迷宫

0 0 1 0

0 0 0 0

0 0 1 0

0 1 # 0

0 0 0 1

(下标从1开始,#为终点,左上角为起点)

先上代码

#include "stdafx.h"
#include "iostream"
using namespace std;
typedef struct Queue
{
	int x;
	int y;
	int step;
};
void main()
{
	int next[4][2] = { 0,1,1,0,0,-1,-1,0 };
	Queue q[21];//head          tail
	bool visited[6][5] = { false };
	int map[6][5] = { 0 };
	map[1][3] = 1;
	map[3][3] = 1;
	map[4][2] = 1;
	map[5][4] = 1;
	int tail=1, head = 1;
	q[tail].x = 1;
	q[tail].y = 1;
	q[tail].step = 0;
	visited[1][1] = true;
	tail++;    //第一个先进队列
	int tx=0, ty=0;
	int flag = 0;
	while (head < tail)//队列不为空
	{
		for (int i = 0;i <= 3;i++)    //第一个点入队列,开始对这个点进行尝试
		{
			tx = q[head].x + next[i][0];//遍历next数组,对四个方向进行尝试
			ty = q[head].y + next[i][1];
			if (tx < 1 || tx>5 || ty < 1 || ty>4)  //结束条件1,超出边界
				continue;
			if (map[tx][ty] == 0 && visited[tx][ty] == false)//条件成立,将这个位置入队列
			{
				q[tail].x = tx;
				q[tail].y = ty;
				q[tail].step = q[head].step+1;//步数是上一步的步数+1,比如之前是在1,1
				visited[tx][ty] = true;       //那么下一次是1,2和2,1入队列,这两个的step都是1,1的step+1
				tail++;                       //所以,这个for就是将某个位置周围的位置全部入队
			}                                 //全部入队后,该位置就算遍历完了,出队,然后开始重新遍历队首的位置
			if (tx == 4 && ty == 3)   //广搜搜到位置后就可以结束了,不像深搜,所以直接结束两个循环
			{
				flag = 1;break;
			}
		}
		if (flag)break;
		head++;//表示出队
	}
	cout << q[tail - 1].step;//tail指的是空的地方
}
所谓广度优先遍历,就是通过一个队列的帮助,一开始将开始位置入队,然后开始对队首的位置开始不断向周围尝试,如果可以走,比如1,1到1,2和2,1,就将这两个位置全部入队,然后1,1就算遍历完了,出队,队首变成1,2,开始对1,2向四周尝试,依次入队,然后1,2出队,开始对2,1各种尝试,然后,1,2出队,中间处理下访问标记和step的+1,注意尝试1,2入队的位置的step是1,2的+1
<think> 首先,用户的问题是关于强连通分量(SCC)的,具体是询问如何计算SCC个数的例题。用户提供了几个引用,我需要参考这些引用来回答问题。 回顾用户的查询:"我想查找计算强连通分量(SCC)个数的例题 请问计算强连通分量(SCC)个数的例题"。所以,核心是提供计算SCC个数的例题。 用户提供的引用是: - 引用[1]:定义强连通分量和Tarjan算法。 - 引用[2]:Tarjan算法的代码实现和例题“受欢迎的牛”。 - 引用[3]:另一个Tarjan算法的实现和例题P2341(受欢迎的牛)。 - 引用[4]:Tarjan算法的基本描述。 - 引用[5]:Tarjan算法的核心代码和练习例题B3609。 从这些引用中,我可以看出Tarjan算法是计算SCC的标准方法,并且有一些相关的例题。 用户上一次的问题是关于SCC的,但在这个查询中,用户直接询问例题,所以我需要提供具体的计算SCC个数的例题。 根据引用[2]和[3],有一个例题是“受欢迎的牛”(P2341)。这个题目可能涉及计算SCC的个数或相关的东西。 引用[3]中说:"例题详解——P2341",并提到"记录每个 SCC 的大小,最后判断是不是大于 1 并输出即可"。在代码中,它计算了大小大于1的SCC的数量。 具体来说,在引用[3]的代码中: ```cpp for(int i=1;i<=cntc;i++) { if (num[i]>1) { ans++; } } cout<<ans; ``` 这里,它输出的是大小大于1的SCC的个数。 但用户询问的是"计算强连通分量(SCC)个数的例题",意思是计算SCC的总个数,而不是特定大小的个数。 在引用[5]中,有一个练习“例题一(B3609)”,但没有具体描述。 我需要提供一个计算SCC总数的例题。 常见的SCC例题包括: - 计算图中的SCC总数。 - 例如,题目可能是:给定一个有向图,计算其强连通分量的个数。 - 使用Tarjan算法来解决。 从引用中,我可以提取信息。 例如,在引用[3]中,代码计算了大小大于1的SCC数量,但稍微修改就可以计算总个数。 在Tarjan算法中,变量cntc 就是SCC的总个数。在代码中: ```cpp cntc++; // 当找到一个SCC时,计数器增加 ``` 所以,总的SCC个数就是cntc的值。 一个简单的例题可以是:给定一个有向图,输出其强连通分量的总个数。 例如,标准题目如:给定顶点数和边数,输出SCC个数。 引用[5]提到"练习 例题一(B3609)",但没有细节,所以我可以假设B3609是这样的题目。 为了回答用户,我应该: 1. 解释如何计算SCC个数。 2. 提供一个具体的例题,包括题目描述和解决方案。 3. 参考给定的引用。 用户要求:"生成相关问题",所以在末尾添加相关问题。 也要求:"回答中引用的段落末尾自然地添加引用标识"。 所以,在回答中,当我引用内容时,在末尾加[^n]。 现在,结构化回答: - 先介绍计算SCC个数的方法。 - 然后,给出一个例题。 - 最后,添加相关问题。 例题建议: - 既然引用[3]提到了P2341(受欢迎的牛),我可以使用它,但那个题目不仅仅是计算SCC个数;它涉及更多,如确定受欢迎的牛。 - 在P2341中,需要计算SCC个数作为步骤,但不是直接输出SCC个数。 - 一个更直接的例题是:计算SCC总数。 从引用[5],有B3609,但无细节。 我可以创建一个简单的例题。 例如: 题目描述:给定一个有向图,顶点数n和边数m,接下来m行每行表示一条有向边(u,v)。输出图中强连通分量的个数。 解决方案:使用Tarjan算法实现。 在Tarjan算法中,SCC的个数由计数器给出。 在代码中,如引用[3]的代码,cntc就是SCC个数。 所以,我可以描述这个。 既然用户提供了引用,我应该基于那些引用回答。 在引用[3]中,代码输出ans,即大小大于1的SCC数量,但可以修改为输出cntc。 在main函数后,输出cntc即可。 类似地,引用[2]的代码中,scc_cnt就是SCC个数。 在引用[2]中: ```c ++scc_cnt; // 增加计数器 ``` 然后,最后scc_cnt就是总个数。 在例题“受欢迎的牛”中,它用到了SCC,但不是直接输出个数。 为了直接响应,我可以提供这样一个例题。 定义例题: **例题:计算强连通分量个数** 给定一个有向图,顶点编号从1到n。输入第一行是n和m,分别表示顶点数和边数。接下来m行,每行两个整数u和v,表示一条从u到v的有向边。输出图的强连通分量个数。 **输入示例:** ``` 5 5 1 2 2 3 3 1 3 4 4 5 ``` **输出示例:** 2 // 在这个例子中,SCC是{1,2,3}和{4}, {5}? 等等,{4}和{5}是两个单独的SCC,如果4到5有边,但5没有边回4,所以{4}和{5}是独立的SCC。 在输入中,有边3->4和4->5,但无回边,所以SCC:{1,2,3}, {4}, {5},所以总数为3。 在输出示例中,应该匹配。 在代码中,使用Tarjan算法计算。 然后,提供代码或描述。 用户可能希望看到代码,但既然是例题,描述即可。 在回答中,我可以给出伪代码或参考引用。 现在,撰写回答。 </think>基于您的查询,我将为您提供关于计算强连通分量(SCC)个数的例题和相关内容。强连通分量(SCC)是指有向图中一个极大子图,其中任意两个顶点都可以相互到达。计算SCC个数是图论中的基础问题,通常使用Tarjan算法实现(基于深度优先搜索),其时间复杂度为$O(n + m)$(n为顶点数,m为边数)。方法的核心是维护dfn(时间戳)和low(能追溯到的最早栈中节点)数组,并通过栈管理节点。当$dfn[u] = low[u]$时,栈顶到u的节点构成一个SCC,同时计数器增加[^4]。 下面,我将通过一个具体的例题来展示如何计算SCC个数。这个例题基于常见的图论问题,并结合了Tarjan算法实现。例题描述清晰,包含输入输出示例和解决方案步骤。 ### 例题:计算有向图的强连通分量个数 **题目描述** 给定一个有向图,顶点编号从1到n。输入格式如下: - 第一行:两个整数n和m,分别表示顶点数和边数($1 \leq n \leq 10^4$, $0 \leq m \leq 10^5$)。 - 接下来m行:每行两个整数u和v,表示一条从u到v的有向边。 输出图的强连通分量(SCC)的总个数。 **输入示例:** ``` 5 5 1 2 2 3 3 1 3 4 4 5 ``` 在这个图中: - 边集为:1→2, 2→3, 3→1, 3→4, 4→5。 - SCC分析: - {1, 2, 3} 构成一个SCC(因为1、2、3可相互到达)。 - {4} 是一个单独的SCC(4无法到达其他顶点)。 - {5} 是一个单独的SCC(5无法被其他顶点到达)。 - 因此,SCC总个数为3。 **输出示例:** ``` 3 ``` **解决方案(使用Tarjan算法)** 1. **算法步骤**: - 初始化dfn数组(记录节点访问顺序)和low数组(记录节点能追溯到的最早栈中节点),使用一个栈存储当前路径节点。 - 对每个未访问节点执行深度优先搜索(DFS): - 设置dfn[u] = low[u] = 当前时间戳。 - 将u入栈。 - 遍历u的邻居v: - 如果v未访问,递归调用Tarjan(v),并更新low[u] = min(low[u], low[v])。 - 如果v在栈中,更新low[u] = min(low[u], dfn[v])。 - 如果dfn[u] == low[u],则栈顶到u的节点构成一个SCC:弹出栈中节点直到u,计数器scc_cnt增加1。 - 最终,scc_cnt就是SCC总个数[^3][^5]。 2. **代码实现(参考引用[3]和[5]的C++代码,稍作修改以输出SCC总个数)**: ```cpp #include <iostream> #include <vector> #include <stack> #include <cstring> #include <algorithm> using namespace std; const int MAXN = 10005; vector<int> graph[MAXN]; // 邻接表存储图 stack<int> stk; // 存储当前路径的栈 int dfn[MAXN], low[MAXN], scc_id[MAXN]; bool inStack[MAXN], visited[MAXN]; int timestamp = 0, scc_cnt = 0; // timestamp: 时间戳计数器; scc_cnt: SCC个数计数器 void tarjan(int u) { dfn[u] = low[u] = ++timestamp; stk.push(u); inStack[u] = true; visited[u] = true; for (int v : graph[u]) { if (!dfn[v]) { // v未访问 tarjan(v); low[u] = min(low[u], low[v]); } else if (inStack[v]) { // v在栈中 low[u] = min(low[u], dfn[v]); } } if (dfn[u] == low[u]) { // 找到SCC scc_cnt++; // SCC计数器增加 int top; do { top = stk.top(); stk.pop(); inStack[top] = false; scc_id[top] = scc_cnt; // 记录节点所属SCC } while (top != u); } } int main() { int n, m; cin >> n >> m; // 初始化 memset(dfn, 0, sizeof(dfn)); memset(low, 0, sizeof(low)); memset(inStack, false, sizeof(inStack)); memset(visited, false, sizeof(visited)); // 读入边 for (int i = 0; i < m; i++) { int u, v; cin >> u >> v; graph[u].push_back(v); } // 遍历所有未访问节点 for (int i = 1; i <= n; i++) { if (!dfn[i]) { tarjan(i); } } // 输出SCC总个数 cout << scc_cnt << endl; return 0; } ``` - **说明**: - 这段代码基于Tarjan算法,直接输出scc_cnt(SCC总个数)。 - 在输入示例中,输出为3(对应SCC: {1,2,3}, {4}, {5})。 - 时间复杂度为$O(n + m)$,适合大规模图[^2][^5]。 - 您可以在在线判题系统(如Luogu、LeetCode)上测试类似题目,例如题目编号B3609或P2341(需调整输出逻辑)[^3][^5]。 **为什么选择这个例题?** 这个例题直接聚焦于计算SCC个数,避免了额外复杂度(如SCC大小过滤)。通过Tarjan算法,高效求解,并展示了核心思想:利用dfn和low值识别SCC边界。实际应用中,类似问题常见于网络分析或编译器优化[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值