文章目录
- 1.PAT-1091题目
- 2.思路
- 3.代码
- 4.BFS复习
- 4.二维BFS
- 5.STL的queue
1.PAT-1091题目
https://pintia.cn/problem-sets/994805342720868352/problems/994805375457411072 一个的三维图,找出三维图结点数量大于T的连通分量,将这样的全部连通块的1累加输出。
2.思路
用BFS遍历每个连通分量并统计结点数,将1的个数大于T的连通块的1的个数进行累加。
由于是三维数组,所以需要用一个增量数组表示上下左右前后,共6个方向。
int X[6] = {0, 0, 0, 0, 1, -1};
int Y[6] = {0, 0, 1, -1, 0, 0};
int Z[6] = {1, -1, 0, 0, 0, 0};
注意:
(1)若用DFS则在最后两组数组会出现段错误——因为当三维矩阵中所有元素均为1时,DFS的深度过深,会使系统栈达到上限(爆栈)。
(2)输入数据时是按多个二维矩阵的方式读入的,因此3层for循环中的第一层需要遍历矩阵编号,第二三层才是单个矩阵的数据读入。
3.代码
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<queue>
using namespace std;
//利用BFS遍历,找到卒中核心块
struct node{
int x,y,z; //位置(x,y,z)
}Node;
int n,m,slice,T; //矩阵为n*m,共有slice层,T为卒中核心区中1的个数的下限
int pixel[1290][130][61]; //三维01矩阵
bool inq[1290][130][61] = {false}; //记录位置(x,y,z)是否已入过队
int X[6]={0,0,0,0,1,-1}; //增量矩阵!!!一开始在1和-1之间漏了逗号。。。
int Y[6]={0,0,1,-1,0,0};
int Z[6]={1,-1,0,0,0,0};
bool judge(int x,int y,int z){ //判断坐标(x,y,z)是否需要访问
//越界返回false
if(x>=n || x<0 || y>=m || y<0 || z>=slice || z<0) return false;
//若当前位置为0或(x,y,z)已入队,则返回false
if(pixel[x][y][z] == 0 || inq[x][y][z] == true) return false;
//以上都不满足,返回true
return true;
}
//BFS函数访问位置(x,y,z)所在的块,将该块所有"1"的inq都设置为true
int BFS(int x,int y,int z){
int tot=0; //计数当前块中1的个数
queue<node> Q; //定义队列
Node.x=x,Node.y=y,Node.z=z; //结点Node的位置为(x,y,z)
Q.push(Node); //将结点Node入队
inq[x][y][z]=true; //设置位置(x,y,z)已入过队
while( !Q.empty() ){
node top=Q.front(); //取出队首元素
Q.pop(); //队首元素出队
tot++; //当前块中1的个数加1
for(int i=0;i<6;i++) { //循环6次,得到6个增量方向
int newX=top.x+X[i];
int newY=top.y+ Y[i];
int newZ=top.z+Z[i];
if( judge(newX,newY,newZ) ) { //新位置(newX,newY,newZ)需要访问
//设置Node的坐标
Node.x=newX, Node.y=newY, Node.z=newZ;
Q.push(Node); //将结点Node入队
inq[newX][newY][newZ]=true ; //设置(newX,newY,newZ)已入过队
}
}
}
if(tot >= T) return tot; //如果超过阈值,则返回
else return 0; //否则不记录该块1的个数
}
int main(){
scanf("%d%d%d%d",&n,&m,&slice,&T);
for(int z=0; z<slice; z++){ //注意先枚举切片层号!!!
for(int x=0; x<n; x++){
for(int y=0; y<m; y++){
scanf("%d",&pixel[x][y][z]);
}
}
}
int ans=0; //记录卒中核心区中1的个数总和
for(int z=0;z<slice; z++){
for(int x=0;x<n ; x++) {
for(int y=0;y<m; y++){
//如果当前位置为1,且未被访问,则BFS当前块
if(pixel[x][y][z]==1 && inq[x][y][z]==false){
ans += BFS(x,y,z);
}
}
}
}
printf("%d\n",ans);
system("pause");
return 0;
}
4.BFS复习
void BFS(int s){
queue<int>q;
q.push(s);
while(!q.empty()){
取出队首元素top;
访问队首元素top;
将队首元素出队pop;
将top的下一层结点中未曾入队的结点全部入队,并设置为已入队;
}
}
可以设置一个bool型数组inq(即in queue的简写)来记录每个位置是否在BFS中已入过队。
注意inq数组是判断结点是否已入过队,而不是结点是否被访问过。
区别:
如果设置成是否已被访问,有可能在某个结点正在队列中(但还未访问)时由于其他结点可以到达它而将这个结点再次入队,导致很多结点反复入队,计算量大大增加。
4.二维BFS
给定一个大小的迷宫,*是墙壁,“.”是平地,S表示起点,T为终点,如果当前位时
(下标从0开始),且每次只能前往当前位置的上下左右
四个位置的平地,求从起点S到终点T的最少步数。
栗子:
.....
.*.*.
.*S*.
.***.
...T*
求S到T的最少步数,而是BFS是通过层次的顺序来遍历,因此可以从起点S开始计数遍历的层数,在到达终点T时的层数即所求解的起点S到终点T的最少步数。
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int maxn=100;
struct node{
int x,y;//位置(x,y)
int step;//step为从起点S到达该位置的最少步数(即层数)
}S,T,Node;//S为起点,T为终点,Node为临时结点
int n,m;//n为行,m为列
char maze[maxn][maxn];//迷宫信息
bool inq[maxn][maxn]={false};//记录位置(x,y)是否已入过队
int X[4]={0,0,1,-1};//增量数组
int Y[4]={1,-1,0,0};
//检测位置(x,y)是否有效
bool test(int x,int y){
if(x>=n||x<0||y>=m||y<0)
return false;//超越边界
if(maze[x][y]=='*')
return false;//碰壁
if(inq[x][y]==true)
return false;//已入过队
return true;//有效位置
}
int BFS(){
queue<node>q;
q.push(S);//将起点S入队
while(!q.empty()){
node top=q.front();//取出队首元素
q.pop();//队首元素出队
if(top.x==T.x&&top.y==T.y){
return top.step;//终点,直接返回最少步数
}
for(int i=0;i<4;i++){//循环四次,得到4个相邻位置
int newX=top.x+X[i];
int newY=top.y+Y[i];
if(test(newX,newY)){//位置(newX,newY)有效
//设置Node的坐标为(newX,newY)
Node.x=newX,Node.y=newY;
Node.step=top.step+1;//Node层数为top的层数+1
q.push(Node);//将结点Node加入队列
inq[newX][newY]=true;//设置位置(newX,newY)已入过队
}
}
}
return -1;//无法到达终点T时返回-1
}
int main(){
scanf("%d%d",&n,&m);//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';//将外界边界点设0
}
scanf("%d%d%d",&S.x,&S.y,&T.x,&T.y);//起点和终点的坐标
S.step=0;//初始化起点的层数为0,即S到S的最少步数为0
printf("%d\n",BFS());
//return 0;
system("pause");
}
输入数据:
5 5 //5行5列
.....
.*.*.
.*S*.
.***.
...T*
2 2 4 3//起点S的坐标和终点T的坐标
正确的输出数据应该是11,而我在VS上输出竟然是8。。后面找下bug。。
5.STL的queue
当使用STL的queue时,元素入队的push只是制造了该元素的一个副本入队,即在入队后对元素进行修改不会影响队列中的副本,反过来说,对队列中副本的修改也不会改变原元素。
——解决方法:
需要对队列的元素修改,而非仅仅是访问时,队列中存放的元素最好不要是元素本身,而是队列的编号(如果是数组的话就是数组的下标), 如下栗子:
#include<cstdio>
#include<queue>
using namespace std;
struct node{
int data;
}a[10];
int main(){
queue<int> q;//q存放结构体数组中元素的下标
for(int i=1;i<=3;i++){
a[i].data=i;//a[1]=1,a[2]=2,a[3]=3
q.push(i);//将数组下标i入队,而不是结点a[i]本身入队
}
a[q.front()].data=100; //q.front()为下标,通过a[q.front()]即可修改原元素
printf("%d\n",a[1].data);
return 0;
}
输出结果为
100