有这样一个题目
给出一个mxn的矩阵,矩阵中的元素为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
例如上面的6x7矩阵中,“块”的个数是4。
对于这个问题,基本思想是枚举每一个位置的元素,如果为1且未访问过(用一个二维bool数组标记是否访问过),说明这是个新“块”,块数增1,访问该位(置对应bool二维数组元素为true),并对其进行深度搜索或者广度搜索,即同样地查询与之相邻的四个位置,枚举和遍历都结束后输出块数即可。
为方便查询相邻的四个位置,可以建立两个增量数组,表示四个方向。
int X[4]={0,0,1,-1};
int Y[4]={1,-1,0,0};
竖着来看即(0,1)、(0,-1)、(1,0)、(-1,0)
这样就可以用for循环来枚举四个方向,以确定与当前坐标(nowX,nowY)相邻的4个位置,如下所示:
for(int i=0;i<4;i++){
nowX=nowx+X[i];
nowY=nowy+Y[i];
}
广度遍历算法如下:
#include<cstdio>
const int maxn=100;
using namespace std;
int n,m;
int matrix[maxn][maxn];//0,1矩阵
int X[4]={0,0,1,-1};
int Y[4]={1,-1,0,0};//增量数组
bool inq[maxn][maxn]={false};//记录是否访问
bool judge(int x,int y){//判断坐标(x,y)是否需要访问
if(x<0||x>n||y<0||y>m)
//越界,返回false
return false;
//已经访问过或当前位为0,返回false
if(inq[x][y]==true||matrix[x][y]==0)
return false;
//除去以上情况,返回true
return true;
}
void dfs(int x,int y){//深度遍历算法
if(!judge(x,y)) return ;//递归出口,当前位不需要访问
inq[x][y]=true;//置访问位为true
for(int i=0;i<4;i++){//查询相邻的位置
dfs(x+X[i],y+Y[i]);
}
}
int main(){
scanf("%d%d",&m,&n);//输入行列
for(int i=0;i<m;i++){//输入矩阵
for(int j=0;j<n;j++){
scanf("%d",&matrix[i][j]);
}
}
int ans=0;//块数
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){//枚举每一位置
if(matrix[i][j]==1&&inq[i][j]==false){//当前位为1且未访问
//说明找到新"块"
ans++;
dfs(i,j);
}
}
}
printf("%d",ans);
return 0;
}
这道题也可以用广度遍历算法来做,广度遍历算法需用到队列,枚举思想同上,如遇到为1且未访问的位,说明是新块,块数要加1,还要将这块中的所有元素都访问。广度遍历的做法是将当前位入队,访问队首元素并出队,然后将与队首相邻的需要访问的位入队,此后不断地进行出队入队操作,直到队空为止,说明本块中全部访问完了。最后输出块数。
算法只有BFS部分不同,代码如下:
#include<cstdio>
#include<queue>
const int maxn=100;
using namespace std;
int n,m;
int matrix[maxn][maxn];
int X[4]={0,0,1,-1};
int Y[4]={1,-1,0,0};
bool inq[maxn][maxn]={false};
struct node{
int x,y;
}Node;
queue<node> q;
bool judge(int x,int y){
if(x<0||x>n||y<0||y>m)
return false;
if(inq[x][y]==true||matrix[x][y]==0)
return false;
return true;
}
void bfs(int x,int y){
queue<node> q;//定义队列
Node.x=x;Node.y=y;//为结点赋值横纵坐标
q.push(Node);//将该结点入队列
inq[x][y]=true;//设置该结点已经入过队
while(!q.empty()){//队空即结束
node top=q.front();//取队首结点
q.pop();//将队首结点出队
for(int i=0;i<4;i++){//访问相邻位置
int nowX=top.x+X[i];
int nowY=top.y+Y[i];
if(judge(nowX,nowY)){//如果该位置需要访问
Node.x=nowX;//设置Node坐标
Node.y=nowY;
q.push(Node);//将结点入队
inq[Node.x][Node.y]=true;//设置该结点已入过队
}
}
}
}
int main(){
scanf("%d%d",&m,&n);
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
scanf("%d",&matrix[i][j]);
}
}
int ans=0;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(matrix[i][j]==1&&inq[i][j]==false){
ans++;
bfs(i,j);
}
}
}
printf("%d",ans);
return 0;
}
最后再提醒一下,BFS中设置的inq数组的含义是判断结点是否入队,而不是是否访问过,如果含义为是否访问,考虑这种情形:当前结点已在队中,但还未访问,故inq的相应位不会置为true,此时被其他与其相邻的结点查询到,从而导致其重复入队,计算量大增。
还有一点就是STL的 queue的push操作只是制造了一个该元素的一个副本入队,因此在入队后对原元素的访问不会影响队中的副本,对队中副本的修改也不会改变原元素,这点和传递指针是相反的。