NO.9数据结构图|深度优先遍历|广度优先遍历|伪代码|邻接表|邻接矩阵

图的深度优先遍历

基本思想

图的深度优先搜索, 也叫图的深度优先遍历, 它的思想类似于树的先序遍历, 即尽可能“深” 地遍历图的顶点, 伴随回溯操作, 直至所有顶点都被访问过
![[Pasted image 20251001070328.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]]

  • 使用邻接矩阵的 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]]

图的广度优先遍历

基本思想

图的广度优先搜索, 也叫图的广度优先遍历, 它的思想类似于树的层次遍历, 以一种“逐层外扩” 的方式遍历顶点, 直至所有顶点都被访问过。 (实际算法是看所有顶点都加入过队列) 。
![[Pasted image 20251001074440.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]]

  • 使用邻接矩阵的 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]]

广度优先生成树的重要性质:
起点到其他顶点的路径是原图中对应的最短路径

图的遍历与图的连通性

  • 对于无向图
    对于无向图, 如果只需调用一次 DFS 或者 BFS 就可以遍历所有的点,
    说明该图是连通的。 反之, 如果一个无向图是连通的, 那么只需调用一次 DFS 或者 BFS 就可以遍历所有的点。
    调用 DFS(或 BFS) 的次数 = 连通分量的个数
  • 对于有向图
    对于有向图, 如果只需调用一次 DFS 或者 BFS 就可以遍历所有的点,
    说明从起始点出发到每个顶点都有路径。 反之, 如果从起始点出发到每个顶点都有路径, 则只需调用一次 DFS 或者 BFS 就可以遍历所有的点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值