图的深度优先遍历
基本思想
图的深度优先搜索, 也叫图的深度优先遍历, 它的思想类似于树的先序遍历, 即尽可能“深” 地遍历图的顶点, 伴随回溯操作, 直至所有顶点都被访问过
![![[Pasted image 20251001070328.png]]](https://i-blog.csdnimg.cn/direct/00303da037dc43a49459ba086408873f.png)
算法所需数据结构
深度优先遍历算法既可以借助邻接矩阵实现, 也可以借助邻接表实现。还需要一个栈来支持回溯操作, 并且需要一个数组来标记每个顶点是否被访问过。
- 算法伪代码
DFS(u){ //访问顶点u
标记顶点u的状态为已访问
for(从u出发可以到达的所有顶点v){
if(visited[v] == false){ //如果v未被访问
DFS(V); //递归访问顶点v
}
}
}
DFSTrave(G){ //遍历图G
for{G的所有顶点u){
if{visited[u]==false){//如果顶点u未被访问
DFS(u);//访问遍历顶点u
}
}
}
对于无向图,调用DFS的次数等于该无向图连通分量的个数
![![[Pasted image 20251001083350.png]]](https://i-blog.csdnimg.cn/direct/caad10eb4e7e46dfa454d3768786fef9.png)
- 使用邻接矩阵的 DFS
int G[numOfV][numOfV];
//用二维数组表示该邻接矩阵
//numOfV为顶点数
//需要初始化,若顶点u到顶点v有边,则G[u][v]=1,否则G[u][v]=0
bool visited[numOfV];
//标记数组,标记顶点是否被访问过,如果访问过则为true,否则为false
//numOfV指图的顶点数
//初始化为全false
void DFS(int u,int depth){ //u为当前访问的顶点标号,depth为当前深度
visited[u]=true; //设置顶点u的状态为已被访问
for(int v = 0; v < numOfV; v++){ //查找每个顶点v
if(visited[v] == false && G[u][v] !=0){
//如果v未被访问,且存在从u到v的边
DFS(v, depth+1);//访问v,深度加1
}
}
}
void DFSTrave() { //深度优先遍历图G
for(int u = 0; u < numOfV; u++){ //对每个顶点u
if(visited[u] == false){ //如果u未被访问
DFS(u, 1);//访问顶点u(以及由它可以访问到的其他顶点),初始深度为1
}
}
}
空间复杂度:
深度优先遍历算法是一个递归算法, 需要一个工作栈来支持, 故其空间复杂度为 O(|V|)(其中|V|指的是图的顶点个数) 。
时间复杂度:
以邻接矩阵存储图时, 查找每个顶点的邻接点需要的时间为 O(|V|), 共|V|个顶点, 故总的时间复杂度为 O(|V|2)。
唯一性:
对于同一个图, 其邻接矩阵写法唯一, 在指定一个开始节点的情况下, 其对应的深度优先遍历序列也是唯一的。
- 使用邻接表的 DFS
vector<int> Adj[numOfV];
//图G的邻接表,vector是可变长数组(了解即可)
//numOfV为顶点数
//需要初始化,若顶点u到顶点v有边,则将顶点v加入Adj[u]
bool visited[numOfV];
//标记数组,标记顶点是否被访问过,如果访问过则为true,否则为false
//numOfV指图的顶点数
//初始化为全false
void DFS(int u,int depth){ //u为当前访问的顶点标号,depth为当前深度
visited[u]=true; //设置顶点u的状态为已被访问
for(int i = 0; i < Adj[u].size(); i++){//查找从u出发可以到达的每个顶点v
int v = Adj[u][i];
if(visited[v] == false){//如果v未被访问
DFS(v, depth+1);//访问v,深度加1
}
}
}
void DFSTrave(){ //深度优先遍历图G
for(int u = 0; u < numOfV; u++){//对每个顶点u
if(visited[u] == false){//如果u未被访问
DFS(u, 1);//访问顶点u(以及由它可以访问到的其他顶点),初始深度为1
}
}
}
空间复杂度:
深度优先遍历算法是一个递归算法, 需要一个工作栈来支持, 故其空间复杂度为 O(|V|)(其中|V|指的是图的顶点个数) 。
时间复杂度:
以邻接表存储图时, 查找所有顶点的邻接点需要的时间为 O(|E|)(其中|E|指的是图的边数) , 访问所有顶点的时间复杂度为 O(|V|), 故总的时间复杂度为 O(|E|+|V|)。
不唯一性:
对于同一个图, 其邻接表可以有多种写法, 在指定一个开始节点的情况下,其对应的深度优先遍历序列也可能有多种。
深度优先生成树和深度优先生成森林
在深度优先遍历的过程中, 我们可以得到一棵遍历树(只调用一次 DFS 就遍历到 所有顶点) 或得到遍历森林( 调用多次 DFS 才能遍历到所有顶点) , 它们分别被叫做深度优先生成树和深度优先生成森林。
由邻接表生成的深度优先生成树不唯一, 由邻接矩阵生成的深度优先生成树唯一。
如下图, 共调用 2 次 DFS 才遍历到所有顶点, 所以得到了深度优先生成森林。
![![[Pasted image 20251001073756.png]]](https://i-blog.csdnimg.cn/direct/418cfd99264446048881bbe54d693fa6.png)
图的广度优先遍历
基本思想
图的广度优先搜索, 也叫图的广度优先遍历, 它的思想类似于树的层次遍历, 以一种“逐层外扩” 的方式遍历顶点, 直至所有顶点都被访问过。 (实际算法是看所有顶点都加入过队列) 。
![![[Pasted image 20251001074440.png]]](https://i-blog.csdnimg.cn/direct/e02c9b5a82c54efeb58923700163c6db.png)
算法所需数据结构
广度优先遍历算法既可以借助邻接矩阵实现, 也可以借助邻接表实现。 还需要一个队列来支持层次遍历, 并且需要一个数组来标记每个顶点是否被访问过(在实际算法中用是否入过队来表示) 。
- 算法伪代码
queue q;//定义队列
BFS(U){ //访问顶点u
inQ[u] = true; //标记顶点u的状态为已入过队列
while(q 非空){//只要队列非空
for(从u出发可以到达的所有顶点v){
if(inQ[v] = false){ //如果顶点v未入过队
将顶点v加入队列q;
inQ[v] == true;//标记顶点u的状态为已入过队列
}
}
}
BFSTrave(G){ //遍历图G
for(G的所有顶点u){
if(inQ[u] == false){ //如果顶点u未加入过队列,它当然也没有被访问过
BFS(u); //访问遍历顶点u
}
}
}
对于无向图,调用BFS的次数等于该无向图连通分量的个数。
![![[Pasted image 20251001083526.png]]](https://i-blog.csdnimg.cn/direct/1972468806884f2c892cb4f72cd753e7.png)
- 使用邻接矩阵的 BFS
int G[numOfV][numOfV]; //用二维数组表示该邻接矩阵
//numOfV为顶点数
//需要初始化,若顶点u到顶点v有边,则G[u][V]=1,否则G[u][v]=0
queue<int> q;
//定义一个队列,初始为空 bool inQ[numOfV];
//标记数组,标记顶点是否入过队列,如果入过则为true,否则为false
//numOfV指图的顶点数
//初始化为全false
void BFS(int u){ //u为当前访问的顶点标号
q.push(u);//将顶点u加入队列q
inQ[u] = true;//设置顶点u的状态为已入过队
while(!q.empty()){//只要队列非空
int u = q.front();//取出队首元素
q.pop();//将队首元素出队
for(int v = 0; v < numOfV; v++){//查找每个顶点v
if(inQ[M] == false && G[u][V] != 0){
//如果顶点v未如果队,且存在从u到v的边
q.push(v);//将v入队
inQ[v] = true;//标记v为已入过队
}
}
}
}
void BFSTrave(){//广度优先遍历图G
for(int u = 0; u < numOfV; u++){ //对每个顶点u
if(inQ[u] == false){ //如果u未入过队
BFS(U); //访问顶点u(以及由它可以访问到的其他顶点)
}
}
}
空间复杂度:
广度优先遍历算法需要维护一个队列, 其空间复杂度为 O(|V|)(其中|V|指的是图的 顶点个数)
时间复杂度:
以邻接矩阵存储图时, 查找每个顶点的邻接点需要的时间为O(|V|), 故总的时间复 杂度为 O(|V|2)。
唯一性:
对于同一个图, 其邻接矩阵写法唯一, 在指定一个开始节点的情况下, 其对应的广 度优先遍历序列也是唯一的。
- 使用邻接表的 BFS
vector<int>Adj[numOfV];
//图G的邻接表,vector是可变长数组(了解即可)
//numOfV为顶点数
//需要初始化,若顶点u到顶点v有边,则将顶点v加入Adj[u]
queue<int> q;
//定义一个队列,初始为空
bool inQ[numOfV];
//标记数组,标记顶点是否入过队,如果访问过则为true,否则为false
//numOfV指图的顶点数
//初始化为全false
void BFS(int u){//u为当前访问的顶点标号,depth为当前深度
q.push(u);//将顶点u加入队列q
inQ[u] = true;//设置顶点u的状态为已被访问
while(!q.empty()){//只要队列非空
int u = q.front()://取出队首元素
q.pop();//将队首元素出队
for(int i = 0; i < Adj[u].size(); i++){
//查找从u出发可以到达的每个顶点v
int v = Adj[u][i];
if(inQ[v] == false){//如果v未入过队
q.push(v)://将v入队
inQ[M] = true;//标记v为已入过队 1
}
}
}
}
void BFSTrave(){ //广度优先遍历图G
for(int u = 0; u < numOfV; u++){//对每个顶点u
if(inQ[u] == false){//如果u未被访问
BFS(u);//访问顶点u(以及由它可以访问到的其他顶点)
}
}
}
空间复杂度:
广度优先遍历算法需要维护一个队列, 其空间复杂度为 O(|V|)(其中|V|指的是图的 顶点个数)
时间复杂度:
以邻接表存储图时, 查找所有顶点的邻接点需要的时间为 O(|E|)(其中|V|指的是图 的顶点个数) , 访问所有顶点的时间复杂度为 O(|V|), 故总的时间复杂度为 O(|E|+|V|)。
不唯一性:
对于同一个图, 其邻接表可以有多种写法, 在指定一个开始节点的情况下, 其对应 的广度优先遍历序列也可能有多种
广度优先生成树和广度优先生成森林
在广度优先遍历的过程中, 我们可以得到一棵遍历树(只调用一次 BFS 就遍历到所有顶点) 或得到遍历森林(调用多次 BFS 才能遍历到所有顶点),它们分别被叫做广度优先生成树和广度优先生成森林。
由邻接表生成的广度优先生成树不唯一, 由邻接矩阵生成的广度优先生成树唯一。
如下图, 只调用 1 次 BFS 才遍历到所有顶点, 所以得到了广度优先生成树。
![![[Pasted image 20251001081732.png]]](https://i-blog.csdnimg.cn/direct/1ec7688a875748a2ac6ebbe6de8fc071.png)
广度优先生成树的重要性质:
起点到其他顶点的路径是原图中对应的最短路径
图的遍历与图的连通性
- 对于无向图
对于无向图, 如果只需调用一次 DFS 或者 BFS 就可以遍历所有的点,
说明该图是连通的。 反之, 如果一个无向图是连通的, 那么只需调用一次 DFS 或者 BFS 就可以遍历所有的点。
调用 DFS(或 BFS) 的次数 = 连通分量的个数 - 对于有向图
对于有向图, 如果只需调用一次 DFS 或者 BFS 就可以遍历所有的点,
说明从起始点出发到每个顶点都有路径。 反之, 如果从起始点出发到每个顶点都有路径, 则只需调用一次 DFS 或者 BFS 就可以遍历所有的点。
1059

被折叠的 条评论
为什么被折叠?



