10.3 图的遍历
图的遍历是指对图的所有顶点按一定顺序进行访问,遍历方法一般有两种:深度优先搜索和广度优先搜索。
10.3.1 DFS遍历图
两个概念:
- 连通分量:在无向图中,如果两个顶点之间可以相互到达(可以是通过一定路径间接到达),那么就称这两个顶点连通。如果图G(V,E)的任意两个顶点都连通,则称图G为连通图;否则,称图G为非连通图,且称其中的极大连通子图为连通分量。
v1v2v3 , v4v5v6v7 , v8v9 形成三个连通分量
- 强连通分量:在有向图中,如果两个顶点可以各自通过一条有向路径到达另一个顶点,就称这两个顶点强连通。如果图G(V,E)的任意两个顶点都强连通,则称图G为强连通图;否则,称图G为非强连通图,且称其中的极大强连通子图为强连通分量。
v1v2v3 , v4 , v5v6v7v8 形成三个强连通分量。
可以想象,如果要遍历整个图,就需要对所有连通块分别进行遍历。所以DFS遍历图的基本思路就是将经过的顶点设置为已访问,在下次递归碰到这个顶点时就不再去处理,直到整个图的顶点都被标记为已访问。
下面是一份DFS的伪代码,不管是使用邻接矩阵还是邻接表,都是使用这种思想。注意:如果已知给定的图是一个连通图,则只需要一次DFS就能完成遍历。
DFS(u){ //访问顶点u
vis[u] = false ; // 设置u已被访问
for( 从u出发能到达的所有顶点v ){ //枚举从u出发可以到达的所有顶点v
if( vis[v] == false ) //如果v未被访问
DFS(v) ; //递归访问v
}
}
DFSTrave(G){ //遍历图G
for( G的所有顶点u ){
if( vis[u] == false ) //如果u未被访问
DFS[U] ; //访问u所在的连通块
}
}
将邻接矩阵和邻接表的实现方法带入上面伪代码中,可以得到如下模板
邻接矩阵模板
const int maxn = 1000 ; // 顶点最大数
const int INF = 1000000000 ; //一个很大的数
int n ; //n为顶点数
int G[maxn][maxn] ; //图
bool vis[maxn] = {false} ; //如果顶点i已被访问,则vis[i]=true ,否则vis[i]=false
void DFS( int u , int depth ){ //u为当前访问的顶点标号,depth为深度
vis[u] = true ; //设置u已被访问
//如果需要对u进行操作,在这里执行
for( int v = 0 ; v < n ; v++ ){ //所有从u出发能到达的顶点
//顶点未被访问且顶点u与顶点v之间存在边
if( vis[v] == false && G[u][v] != INF ){
DFS(v ,depth+1 ) ; //访问v,深度+1
}
}
}
void DFSTrave(){ //遍历图G
for( int u = 0 ; u < n ; u++ ){ //对每个顶点
if( vis[u] == false ){
DFS(u , 1) ; //访问u和u所在的连通块,初始为第1层
}
}
}
邻接表模板
const int maxn = 1000 ;
vector<int> Adj[maxn] ;
int n ; //顶点个数
bool vis[maxn] = {false} ;
void DFS( int u , int depth ){
vis[u] = true ;
//如果需要对u进行操作
for( int i = 0 ; i < Adj[u].size() ; i++ ){
int v = Adj[u][i] ;
if( vis[v] == false ){
DFS(v , depth+1 ) ;
}
}
}
void DFSTrace(){
for( int u = 0 ; u < n ; u++ ){
if( vis[u] == false ){
DFS(u , 1) ;
}
}
}
BFS遍历图
广度优先搜索以“广度”作为关键词,每次以扩散的方式向外访问顶点。和树的遍历一样,使用BFS遍历图需要使用一个队列,通过反复取出队首顶点,将该顶点可到达的未曾加入过队列的顶点全部入队,直到队列为空时遍历结束。
BFS遍历图的具体实现
如果要遍历整个图,需要对所有连通块分别进行遍历。使用BFS遍历图的基本思想是建立一个队列,并把初始顶点加入队列,此后每次都取出队首顶点进行访问,并把从该顶点出发可以到达的未曾加入过队列
(而不是未访问)的顶点全部加入队列,直到队列为空。
邻接矩阵模板
const int maxn = 1000 ;
const int INF = 10000000 ;
int n ;
int G[maxn][maxn] ;
bool inq[maxn] = {false} ; //若顶点i曾如果队,则inq[i] = true
void BFS( int u ){
queue<int> q ;
q.push(u) ;
inq[u] = true ;
while( !q.empty()){
int u = q.front() ;
q.pop();
for( int v = 0 ; v < n ; v++ ){
if( inq[v] == false && G[u][v] != INF ){
q.push(v) ;
inq[v] = true ;
}
}
}
}
void BFSTrave(){
for( int u = 0 ; u < n ; u++ ){
if( inq[u] == false ){
BFS(u) ;
}
}
}
邻接表模板
const int maxn = 1000 ;
int n ;
vector<int> Adj[maxn] ;
bool inq[maxn] = {false} ; //若顶点i曾如果队,则inq[i] = true
void BFS( int u ){
queue<int> q ;
q.push(u) ;
inq[u] = true ;
while( !q.empty()){
int u = q.front() ;
q.pop();
for( int i = 0 ; i < Adj[u].size() ; i++ ){
int v = Adj[u][i] ;
if( inq[v] == false ){
q.push(v) ;
inq[v] = true ;
}
}
}
}
void BFSTrave(){
for( int u = 0 ; u < n ; u++ ){
if( inq[u] == false ){
BFS(u) ;
}
}
}
如果需要记录层号
struct Node{
int v ; //顶点编号
int layer ; //顶点层号
};
const int maxn = 1000 ;
int n ;
vector<int> Adj[maxn] ;
bool inq[maxn] = {false} ; //若顶点i曾如果队,则inq[i] = true
void BFS( int s ){
queue<Node> q ;
Node start ;
start.v = s ;
start.layer = 0 ;
q.push(start) ;
inq[start.v] = true ;
while( !q.empty()){
Node top = q.front() ;
q.pop();
int u = top.v ; //队首顶点编号
for( int i = 0 ; i < Adj[u].size() ; i++ ){
Node next = Adj[u][i] ;
next.layer = top.layer + 1 ;
if( inq[next.v] == false ){
q.push(next) ;
inq[next.v] = true ;
}
}
}
}