图的遍历
图的遍历是指从图中某一个顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次
图的遍历算法是求解图的连通性问题、拓扑排序和求关键路径等算法的基础
由于图结构本身的复杂性,所以其遍历也较复杂,主要体现在:
(1)图中任意一个顶点可作为第一个被访问的顶点
(2)如何选取不同连通分量上的访问出发点
(3)图中如果有回路,可能重复访问
(4)如何选取下一个要访问的邻接点
一个图有那么多个结点,
如何遍历这些结点,需要特定策略,一般有两种访问策略:深度优先遍历、广度优先遍历
图的深度遍历
深度优先遍历,从初始访问结点出发,我们知道初始访问结点可能有多个邻接结点,深度优先遍历的策略就是首先访问第一个邻接结点,然后再以这个被访问的邻接结点作为初始结点,访问它的第一个邻接结点。总结起来可以这样说:每次都在访问完当前结点后首先访问当前结点的第一个邻接结点。
第一步:假设初始状态是图中所有顶点均未被访问,则可以从图中某个顶点出发,访问此顶点。然后,依次从它的未被访问的邻接点出发深度优先遍历图,直至图中所有和它有路径相通的顶点均被访问到
第二步:若此时图中尚有未被访问的顶点,则另选图中一个未曾被访问的顶点作起始点。重复上述过程,直至图中所有顶点均被访问过为止
注:为了防止顶点重复访问,可为每个顶点设置一个访问标志,若等于true,则说明该顶点已被访问,顶点不得重复访问;等于false,则访问该顶点
如上图所示对其进行深度遍历为:0,1,3,4,5,2
代码实现
#include <stdio.h>
#include <iostream>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 100;
int a[maxn][maxn]={
0};//两点时间是否连接
bool ju[maxn]={
false};//判断是否遍历成功
int n,m;
void dfs(int x)
{
cout<<x<<' ';
ju[x]=true;
for(int i=0;i<n;i++)
{
if(ju[i]==false&&a[x][i])//找出其邻居点且未遍历,进行遍历
{
dfs(i);//遍历该点
}
}
}
int main()
{
fill(a[0],a[0]+maxn*maxn,0);//初始化所有边都没有连接
fill(ju,ju+maxn,false);//初始化所有边都未访问
scanf("%d %d",&n,&m);
for(int i=0;i<m;i++)
{
int x,y;//输入连接的两个点
scanf("%d %d",&x,&y);
a[x][y]=1;
}
for(int i=0;i<n;i++)
{
if(ju[i]==false)//该点是否遍历过
{
dfs(i);//遍历该点
}
}
return 0;
}
总结:深度搜索是一种枚举所有可能以遍历所有情况的搜索方法
练习题目:
习题一:http://codeup.cn/problem.php?cid=100000608&pid=0
习题二:http://codeup.cn/problem.php?cid=100000608&pid=1
图的广度搜索
广度遍历或广度优先搜索(BFS):类似树的层序遍历,也需要队列配合
第一步:假设初始状态是图中所有顶点均未被访问,则可以从图中某个顶点出发,访问此顶点。然后,依次遍历它的未曾遍历过的所有邻接点,并使“先被访问的顶点的邻接点” 先于 “后被访问的顶点的邻接点” 被访问,直至图中所有已被访问的顶点的邻接点均被访问到
第二步:若此时图中尚有未被访问的顶点,则另选图中一个未曾被访问的顶点作起始点。重复上述过程,直至图中所有顶点均被访问过为止
注:为了防止顶点重复访问,与深度遍历一样需要为每个顶点设置一个访问标志
广度搜索:0,1,2,3,4,5
代码实现
#include <stdio.h>
#include <iostream>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 100;
int a[maxn][maxn]={
0};
bool ju[maxn]={
false};
int n,m;
queue <int> q;
void bfs()
{
while(!q.empty())
{
int x=q.front();
q.pop();
cout<<x<<' ';
for(int i=0;i<n;i++)
{
if(a[x][i]==1&&ju[i]==false)
{
ju[i]=true;
q.push(i);
}
}
}
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=0;i<m;i++)
{
int x,y;
scanf("%d %d",&x,&y);
a[x][y]=1;
}
q.push(0);
ju[0]=true;
bfs();
return 0;
}
总结:广度搜索,搜索本节点相邻的所有未访问的节点,可以用队列来实现,一遍出现矩阵,搜索的题目用BFS
例题讲解:
给出一个n*m的矩阵,矩阵中的元素为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*7的矩阵中,"块"的个数为4
#include <stdio.h>
#include <iostream>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 100;
int n,m;
int a[maxn][maxn];//01矩阵
bool ju[maxn][maxn];//记录是否访问
struct node{
int x;
int y;
};
int dx[4]={
0,0,1,-1};
int dy[4]={
1,-1,0,0};
bool judge(int xx,int yy)//判断该点是否可以访问
{
if(xx<0||xx>=n||yy<0||yy>=m)
return false;
if(ju[xx][yy]==true||a[xx][yy]==0)
{
return false;
}
return true;
}
void bfs(int xx,int yy)
{
node no;
no.x=xx;
no.y=yy;
ju[xx][yy]=true;
queue<node> q;
q.push(no);
while(!q.empty())
{
no=q.front();
q.pop();
for(int i=0;i<4;i++)
{
node nn;
nn.x=no.x+dx[i];
nn.y=no.y+dy[i];
if(judge(nn.x,nn.y))
{
ju[nn.x][nn.y]=true;
q.push(nn);
}
}
}
}
int main()
{
scanf("%d %d",&n,&m);
int ans=0;
fill(ju[0],ju[0]+maxn*maxn,false);
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
scanf("%d",&a[i][j]);
}
}
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
if(a[i][j]==1&&ju[i][j]==false)
{
ans++;
bfs(i,j);
}
}
}
cout<<ans;
return 0;
}