BFS 广度搜索 入门典例 -- 凌宸1642

BFS 广度搜索 入门典例 -- 凌宸1642

广度优先搜索 是以 广度第一关键词 ,当碰到岔路口时,总是先依次访问从该岔道口能 直接到达所有结点 ,然后再按这些结点被访问的顺序去依次访问它们能直接到达的所有结点,以此类推。广度优先搜索一般由 队列 实现,且总是按照 层次 的顺序进行遍历,基本写法如下:

void bfs(int s){
    queue<int> q ; // 定义队列
    q.push(s) ;
    while(!q.empty()){
        // 取出队首元素  top ;
        
        // 访问队首元素  top ;
        
        // 将队首元素出队 ;
        
        // 将 top 的下一层结点中未曾入队的结点全部入队,并设置为已入队 ;
	}
}

1 块的个数:

题目描述:

​ 给出一个 m x n 的矩阵,矩阵中的元素为 0 或 1。称位置 (x , y) 与其上下左右四个位置(x , y + 1) , (x , y - 1) , (x + 1 , y ) , (x - 1 , y) 是相邻的。如果矩阵中有若干个 1 是相邻的(不必两两相邻) , 那么称这些 1 构成了一个 “块” 。求给定的矩阵中 “块” 的个数。

0 1 1 1 0 0 1
0 0 1 0 0 0 0
0 0 0 0 1 0 0
0 0 0 1 1 1 0
1 1 1 0 1 0 0
1 1 1 1 0 0 0

例如上面的 6 x 7 的矩阵中,“快” 的个数为 4 。

输入描述:

​ 第一行输入矩阵的行数 n 和列数 m 。

​ 接下来 n 行,每行 m 个元素 ,表示矩阵。

输出描述:

​ 在一行中输出矩阵的中 “1” 块的数量。

样例输入:

6 7
0 1 1 1 0 0 1
0 0 1 0 0 0 0
0 0 0 0 1 0 0
0 0 0 1 1 1 0
1 1 1 0 1 0 0
1 1 1 1 0 0 0

样例输出:

4

思路:

​ 枚举每一个位置的元素,如果是 0 就跳过,如果为 1 ,则使用 BFS 查询与该位置相邻的 4 个位置(前提是不出界),判断他们是否为 1 (如果某个相邻的位置刚好是 1 , 则同样的去查询与该位置相邻的 4 个位置,直到整个 “1” 块访问完毕) 。而为了防止回头路,一般可以设置一个 bool 型数组 inq 来记录每个位置是否在 BFS 中已入过队。

#include<bits/stdc++.h>
using namespace std ;
#define MAX 22
int matrix[MAX][MAX] = { 0 } ; // 存储 01 矩阵 
bool inq[MAX][MAX] = { false } ; // 记录位置 x ,y 是否已经入过队 
int n , m , ans = 0 ; // 矩阵大小为 n 行 m 列 , ans 记录 块 的数量 
int dx[4] = {0 , -1 , 0 , 1} ; // 增量数组 
int dy[4] = {-1 , 0 , 1 , 0} ;
bool judge(int x , int y){
	// 如果越界 返回 false  
	if(x > n || x < 1 || y > m || y < 1) return false ;
	// 如果当前位置是 0 或 当前位置已经 入过队 返回 false 
	if(matrix[x][y] == 0 || inq[x][y] == true) return false ;
	// 上述均不满足,返回 true 
	return true ;
}
// bfs 参数 x , y 代表当前处理位置 
void bfs(int x , int y){
	queue<pair<int , int> > q ; // 定义队列 
	q.push(make_pair(x , y)) ; // 将当前坐标 入队
	inq[x][y] = true ; // 设置已入队 
	while(!q.empty()){
		pair<int , int> top = q.front() ; // 取出队首元素 
		q.pop() ; // 队首元素出队 
		for(int i = 0 ; i < 4 ; i ++){ // 循环四次,访问相邻位置 
			int newX = top.first + dx[i] ; 
			int newY = top.second + dy[i] ;
			if(judge(newX , newY)){ // 如果新位置需要访问 
				q.push(make_pair(newX , newY)) ; // 将新结点入队 
				inq[newX][newY] = true ; // 设置新结点已经入队 
			}
		} 
	} 
}
int main(){
	scanf("%d%d" , &n , &m) ; // 输入矩阵的大小 
	for(int i = 1 ; i <= n ; i ++){
		for(int j = 1 ; j <= m ; j ++){
			scanf("%d" , &matrix[i][j]) ;// 输入 01 矩阵 
		}
	} 
	for(int i = 1 ; i <= n ; i ++){
		for(int j = 1 ; j <= m ; j ++){
			if(matrix[i][j] == 1 && inq[i][j] == false){ // 当前位置为 1 且未被访问
				ans ++ ;  // 块的数目加 1
				bfs(i , j) ; // bfs遍历相邻元素
			}
		}
	} 
	printf("%d\n" , ans) ;
	return 0;
}

2 最小步数:

题目描述:

​ 给定一个 n * m 大小的迷宫,其中 * 代表不可通过的墙壁, 而 " . " 代表平地,S 代表起点,T 代表终点。移动过程中,如果当前位置是(x , y) (下标从 0 开始),且每次只能前往上、下、左、右(x , y + 1) ,(x , y - 1) ,(x - 1 , y ), (x + 1 , y) 四个位置的平地,求从起点 S 到终点 T 的最小步数。

.....
.*.*.
.*S*.
.***.
...T*

在上面的样例中,S 的坐标为 (2 , 2) , T 的坐标为 (4 , 3) 。起点 S 到终点 T 的最小步数为 11 。

输入描述:

​ 第一行输入 n 和 m ,代表迷宫的大小。

​ 接下来 n 行, 每行 m 个元素,表示迷宫 。其中 "."代表平地,"*" 代表墙壁,"S" 代表起点 ,"T" 代表终点。

输出描述:

​ 输出起点 S 到终点 T 的最小步数。如果不可达,输出 -1 。

样例输入:

5 5
.....
.*.*.
.*S*.
.***.
...T*

样例输出:

11
#include<bits/stdc++.h>
using namespace std ;
#define MAX 100
int n , m ; // 迷宫大小
char maze[MAX][MAX] ; // 迷宫信息 
bool inq[MAX][MAX] = { false } ; // 记录  位置 x , y 是否已经入过队 
int dx[4] = { 0 , 0 , -1 , 1 } ; // 增量 
int dy[4] = { 1 , -1 , 0 , 0 } ;
pair<int , pair<int , int> > s , t , top ; // 定义捆绑三个数的 pair  
bool judge(int x , int y){
	if(x >= n || x < 0 || y >= m || y < 0) return false ; // 越界 返回 false 
	if(maze[x][y] == '*') return false ; // 墙壁,不可通过 返回 false 
	if(inq[x][y] == true) return false ; // 已经入过队 返回 false 
	return true ; // 上述均不满足 返回 true 
}
int bfs(){
	queue<pair<int , pair<int , int> > > q ; // 定义队列 
	q.push(s) ; // 将起点入队 
	while(!q.empty()){
		top = q.front() ; // 获得队首元素 
		q.pop() ;		// 队首元素出队 
		if(top.second.first == t.second.first && top.second.second == t.second.second) 
			return top.first ; // 到达终点,返回最小步数
		for(int i = 0 ; i < 4 ; i ++){ // 遍历该点的上下左右位置 
			int newX = top.second.first + dx[i] ;
			int newY = top.second.second + dy[i] ;	
			if(judge(newX , newY)){ // 如果当前点还未遍历,且需要遍历
				q.push(make_pair(top.first + 1 , make_pair(newX , newY))) ; // 将该点入队
				inq[newX][newY] = true ; // 将该点设置为已入队
			}
		} 
	}
	return -1 ; // 不能到达 T 则返回 - 1 
} 
int main(){
	scanf("%d%d" , &n ,&m) ; // 输入迷宫的大小 
	for(int i = 0 ; i < n ; i ++){
		getchar() ; // 吸收行尾 换行符 
		for(int j = 0 ; j < m ; j ++){
			maze[i][j] = getchar() ; 
		}
		maze[i][m+1] = '\0' ;
	}
	scanf("%d%d%d%d" , &s.second.first , &s.second.second , &t.second.first , &t.second.second) ; // 输入起始点和终点坐标 
	s.first = 0 ;	// 起始点的层次为 0 ,即 S 到 S 的最少步数为 0 
	printf("%d\n" , bfs()) ;
	return 0;
}

3 魔板

题目描述:

​ 在成功地发明了魔方之后,鲁比克先生发明了它的二维版本,称作魔板。这是一张有8个大小相同的格子的魔板:

1 2 3 4
8 7 6 5
我们知道魔板的每一个方格都有一种颜色。这8种颜色用前8个正整数来表示。可以用颜色的序列来表示一种魔板状态,规定从魔板的左上角开始,沿顺时针方向依次取出整数,构成一个颜色序列。对于上图的魔板状态,我们用序列(1,2,3,4,5,6,7,8)来表示。这是基本状态。
这里提供三种基本操作,分别用大写字母“A”,“B”,“C”来表示(可以通过这些操作改变魔板的状态):
“A”:交换上下两行;
“B”:将最右边的一列插入最左边;
“C”:魔板中央四格作顺时针旋转。
下面是对基本状态进行操作的示范:

A: 
8 7 6 5 
1 2 3 4 
B: 
4 1 2 3 
5 8 7 6 
C: 
1 7 2 4 
8 6 3 5 

​ 对于每种可能的状态,这三种基本操作都可以使用。
​ 你要编程计算用最少的基本操作完成基本状态到目标状态的转换,输出基本操作序列。

输入描述:

​ 只有一行,包括8个整数,用空格分开(这些整数在范围 1——8 之间),表示目标状态。

输出描述:

​ Line 1: 包括一个整数,表示最短操作序列的长度。

​ Line 2: 在字典序中最早出现的操作序列,用字符串表示,除最后一行外,每行输出60个字符。

样例输入:

2 6 8 4 5 7 3 1

样例输出:

7
BCABCCB
#include<bits/stdc++.h>
using namespace std ;
queue<string> q ; // 定义队列
map<string , string> mp ; // 记录基本状态到达 key 状态时已经进行的操作 value 
void opA(string oldStr){
	string newStr = oldStr ;
	// 对旧的字符串进行 A 操作 
	swap(newStr[0] , newStr[7]) ; 
    swap(newStr[1] , newStr[6]) ;
    swap(newStr[2] , newStr[5]) ;
    swap(newStr[3] , newStr[4]) ;
    if(mp.count(newStr) == 0){ // 如果 map 中没有这个新的字符串 
    	q.push(newStr) ; // 将这个新的字符串入队 
    	mp[newStr] = mp[oldStr] + "A" ;  // 新的字符串的 value 值为旧的字符串加上一个 A 操作 
	}
}
void opB(string oldStr){
	string newStr = oldStr ;
	// 对旧的字符串进行 B 操作 
	swap(newStr[0] , newStr[3]) ; 
    swap(newStr[4] , newStr[7]) ;
    swap(newStr[2] , newStr[3]) ; 
    swap(newStr[4] , newStr[5]) ;
    swap(newStr[1] , newStr[2]) ;
    swap(newStr[6] , newStr[5]) ;
    if(mp.count(newStr) == 0){ // 如果 map 中没有这个新的字符串 
    	q.push(newStr) ; // 将这个新的字符串入队 
    	mp[newStr] = mp[oldStr] + "B" ;  // 新的字符串的 value 值为旧的字符串加上一个 B 操作 
	}
}
void opC(string oldStr){
	string newStr = oldStr ;
	// 对旧的字符串进行 C 操作 
	swap(newStr[1], newStr[2]) ;
    swap(newStr[5], newStr[6]) ;
    swap(newStr[1], newStr[5]) ;
    if(mp.count(newStr) == 0){ // 如果 map 中没有这个新的字符串 
    	q.push(newStr) ; // 将这个新的字符串入队 
    	mp[newStr] = mp[oldStr] + "C" ;  // 新的字符串的 value 值为旧的字符串加上一个 C 操作 
	}
}
// bfs 参数说明 , target 为目标状态 
void bfs(string target){
	q.push("12345678") ; // 基本状态入栈 
	mp["12345678"] = "" ; // 基本状态到基本状态经过了 "" 个操作 
	while(!q.empty()){ 
		string top = q.front() ; // 获取队首元素 
		q.pop() ; // 队首元素出队 
		opA(top) ; // 进行一个 A 操作 
		opB(top) ; // 进行一个 B 操作 
		opC(top) ; // 进行一个 C 操作 
		if (mp.count(target) == 1) { // 如果目标状态已经到达 
            cout << mp[target].length() << endl ; // 输出对应操作的长度 
            cout << mp[target] << endl ; // 输出所有的操作顺序 
            break ;
        }
	}
} 
int main(){
	string str("12345678") ;
//	cin >> str[0] >> str[1] >> str[2] >> str[3] ;
//    cin >> str[4] >> str[5] >> str[6] >> str[7] ;
    for(int i = 0 ; i < 8 ; i ++){
    	scanf("%c" , &str[i]) ;
    	getchar() ; // 吸收数字后面的 空格或回车
	}
	bfs(str) ;
	return 0 ;
}

posted @ 2021-04-06 09:14  凌宸1642  阅读(69)  评论(0编辑  收藏  举报