用深度优先搜索(DFS)法遍历图
深度优先搜索:每次都是沿着路径到不能再前进时才退到最近的岔道口。
以一个有向图进行DFS遍历:
从V0 开始进行遍历,黑色表示结点未访问,白色表示结点已访问,虚线边表示当前遍历路径
- 访问V0 ,发现从V0 出发可以到达两个未访问顶点:V1 和V2 ,因此准备访问V1 和V2 这两个顶点。
- 从V0 出发访问V1 ,发现从V1 出发可以到达两个未访问顶点:V3 和V4 ,因此准备访问V3和V4这两个顶点。
- 从V1出发访问V3,但是从V3出发不能到达任何未访问顶点,因此退回到当前路径上距离V3最近的仍有未访问分支顶点的岔道口V1。
- 从V1出发访问V4,发现从V4出发可以到达一个未访问顶点:V5,因此准备前往访问V5
- 从V4出发访问V5,发现从V5出发不能到达任何未访问顶点,因此退回到当前路径上距离V5最近的仍有未访问分支顶点的岔道口V0。
- 从V0出发访问V2,发现从V2出发不能到达任何未访问顶点,因此退回到当前路径上距离V5最近的仍有未访问分支顶点的岔道口。但是此时路径上所有顶点的分支顶点都已被访问,因此DFS算法结束。
DFS的具体实现
连通分量:在无向图中,如果两个顶点之间可以相互到达(可以是通过一定路径间接到达),那么就称这两个顶点连通。如果图G(V, E)的任意两个顶点都连通,则称图G为连通图;否则,称图G为非连通图,且称其中的极大连通子图为连通分量。
强连通分量:在有向图中,如果两个顶点可以各自通过一条有向路径到达另一个顶点,就称这两个顶点强连通。如果图G(V, E)的任意两个顶点都强连通,则称图G为强连通图;否则,称图G为非强连通图,且称其中的极大强连通子图为强连通分量。
例如:无向图,V1V2V3、V4V5V6V7、V8V9形成了三个连通分量;有向图,V1V2V3、V4、V5V6V7V8形成了三个强连通分量。
遍历图的伪代码
DFS(u) //访问顶点u
{
vis[u] = true; //设置u已被访问
for(从u出发能到达的所有顶点v) //枚举从u出发可以到达的所有顶点v
{
if(vis[v] == false) //如果v未被访问
{
DFS(v); //递归访问v
}
}
}
DFSTrave(G) //遍历图G
{
for(G的所有顶点u) //对G的所有顶点u
{
if(vis[u] == false) //如果u未被访问
{
DFS(u); //访问u所在的连通块
}
}
}
定义MAXV为最大顶点数、INF为一个很大的数字
constint MAXV = 1000; //最大顶点数
constint INF = 100000000; //设INF为一个很大的数
- 邻接矩阵版
int n, G[MAXV][MAXV]; //n为顶点数,MAXV为最大顶点数
bool vis[MAXV] = {false}; //如果顶点i已被访问,则vis[i] == true。初值为false
void DFS(int u, int depth) //u为当前访问的顶点标号,depth为深度
{
vis[u] = true; //设置u已被访问
//如果需要对u进行一些操作,可以在这里进行
//下面对所有从u出发能到达的分支顶点进行枚举
for(int v = 0; v < n; v++) //对每个顶点v
{
if(vis[v] == false && G[u][v] != INF) //如果v未被访问,且u可到达v
{
DFS(v, depth + 1); //访问v,深度加1
}
}
}
void DFSTrave() //遍历图G
{
for(int u = 0; u < n; u++) //对每个顶点u
{
if(vis[u] == false) //如果u未被访问
{
DFS(u, 1); //访问u和u所在的连通块,1表示初始为第一层
}
}
}
- 邻接表版
vector<int> Adj[MAXV]; //图G的邻接表
int n; //n为顶点数,MAXV为最大顶点数
bool vis{MAXV] = {false}; //如果顶点i已被访问,则vis[i] == true。初值为false
void DFS(int u, int depth) //u为当前访问的顶点标号,depth为深度
{
vis[u] = true; //设置u已被访问
/*如果需要对u进行一些操作,可以在此处进行*/
for(int i = 0; i < Adj[u].size(); i++) //对从u出发可以到达的所有顶点v
{
int v = Adj[u][j];
if(vis[v] == false) //如果v未被访问
{
DFS(v, depth + 1); //访问v,深度加1
}
}
}
void DFSTrave() //遍历图G
{
for(int u = 0; u < n; u++) //对每个顶点u
{
if(vis[u] == false) //如果u未被访问
{
DFS(u, 1); //访问u和u所在的连通块,1表示初始为第一层
}
}
}
采用广度优先搜索法(BFS)遍历图
广度优先搜索:需要使用一个队列,通过反复出队首顶点,将该顶点可到达的未曾加入过队列的顶点全部入队,直到队列为空时遍历结束。
以一个有向图进行BFS遍历:
从V0 开始进行遍历,黑色表示结点未访问,白色表示结点已访问,虚线边表示当前遍历路径
- 当前队列内元素为{V0}进行访问。之后将从V0出发能够到达的两个未曾加入过队列的顶点V1、V2加入队列。
- 当前队列内元素为{V1, V2},取出队首元素V1进行访问。之后,将从V1出发能够到达的两个未曾加入过队列的顶点V3、V4加入队列。
- 当前队列内元素为{V2, V3, V4},取出队首元素V2进行访问。由于从V2出发无法找到未曾加入过队列的顶点(V1、V4均已加入过队列),因此不予处理。
- 当前队列内元素为{V3, V4},取出队首元素V3进行访问。由于V3出发无法找到未曾加入过入过队列的顶点,因此不予处理。
- 当前队列内元素为{V4},取出队首元素V4进行访问。之后,将从V4出发能够到达的一个未曾加入过队列的顶点V5加入队列。
- 当前队列内元素为{V5},取出队首元素V5进行访问。由于从V5出发无法找到未曾加入过队列的顶点,因此不予处理。
- 当前队列为空,BFS遍历结束。
BFS的具体实现
BFS遍历伪代码
BFS(u) //遍历u所在的连通块
{
queue q; //定义队列q
将u入队;
inq[u] = true; //设置u已被加入过队列
while(q非空) //只要队列非空
{
取出q的队首元素u进行访问;
for(从u出发可达的所有顶点v) //枚举从u能直接到达的顶点v
{
if(inq[v] == false) //如果v未曾加入过队列
{
将v入队;
inq[v] = true; //设置v已被加入过队列
}
}
}
}
BFSTrave(G) //遍历图G
{
for(G的所有顶点u) //枚举G的所有顶点u
{
if(inq[u] == false) //如果u未曾加入过队列
{
BFS(u); //遍历u所在的连通块
}
}
}
- 邻接矩阵版
int n, G[MAXV][MAXV]; //n为顶点数,MAXV为最大顶点数
bool inq[MAXV] = {false}; //若顶点i曾入过队列,则inq[i]==true。初值为false
void BFS(int u) //遍历u所在的连通块
{
queue<int> q; //定义队列q
q.push(u); //将初始点u入队
inq[u] = true; //设置u已被加入过队列
while(!q.empty()) //只要队列非空
{
int u = q.front(); //取出队首元素
q.pop(); //将队首元素出队
for(int v = 0; v < n; v++)
{
//如果u的邻接点v未曾加入过队列
if(inq[v] == false && G[u][v] != INF)
{
q.push(v); //将v入队
inq[v] = true; //标记v为已被加入过队列
}
}
}
}
void BFSTrave() //遍历图G
{
for(int u = 0; u < n; u++) //枚举所有顶点
{
if(inq[u] == false) //如果u未曾加入过队列
{
BFS(q); //遍历u所在的连通块
}
}
}
- 邻接表版
vector<int> Adj[MAXV]; //图G,Adj[u]存放从顶点u出发可以到达的所有顶点
int n; //n为顶点数,MAXV为最大顶点数
bool inq[MAXV] = {false}; //若顶点i曾入过队列,则inq[i] == true.初值为false
void BFS(int u) //遍历单个连通块
{
queue<int> q; //定义队列q
q.push(u); //将初始点u入队
inq[u] = true; //设置u已被加入过队列
while(!q.empty()) //只要队列非空
{
int u = q.front(); //取出队首元素
q.pop(); //将队首元素出队
for(int i = 0; i < Adj[u].size(); i++) //枚举从u出发能到达的所有顶点
{
int v = Adj[u][i];
if(inq[v] == false) //如果v未曾加入过队列
{
q.push(v); //将v入队
inq[v] = true; //标记v为已被加入过队列
}
}
}
}
void BFSTrave() //遍历图G
{
for(int u = 0; u < n; u++) //枚举所有顶点
{
if(inq[u] == false) //如果u未曾加入过队列
{
BFS(q); //遍历u所在的连通块
}
}
}
与树的BFS遍历一样,在给定BFS初始点的情况下,可能需要输出该连通块内所有其他顶点的层号。
首先,由于需要存放顶点层号。需要定义结构体Node,并在其中存放顶点编号和层号
struct Node
{
int v; //顶点编号
int layer; //顶点层号
}
vector邻接表中的元素就不再是int,而变为Node
vector<Node> Adj[N];
考虑层号的传递关系,当前顶点的层号为L,那么它所有出边的终点的层号都是L+1。
void BFS(int s) //s为起始顶点编号
{
queue<Node> q; //BFS队列
Node start; //起始顶点
start.v = s; //起始顶点编号
start.layer = 0; //起始顶点层号0
q.push(start); //将起始顶点压入队列
inq[start.v] = true; //起始顶点的编号设为已被加入过队列
while(!q.empty())
{
Node topNode = q.front(); //取出队首顶点
q.pop(); //队首顶点出队
int u = topNode.v; //队首顶点的编号
for(int i = 0; i < Adj[u].size(); i++)
{
Node next = Adj[u]pj]; //从u出发能到达的顶点next
next.layer = topNode.layer + 1; //next层号等于当前顶点层号加1
//如果next的编号未被加入过队列
if(inq[next.v] == false)
{
q.push(next); //将next入队
inq[next.v] = true; //next的编号设为已被加入过队列
}
}
}
}