数据结构之——图的遍历

1.定义

图由顶点(Vertex)和边(Edge)组成,每条边的两端都必须是图的两个顶点,记号G(V,E)表示图G的顶点集为V,边集为E。

图可以分为有向图和无向图,有向图的所有边都有方向,即确定了顶点到顶点的一个指向;无向图的所有边都是双向的,即无向边所连接的两个顶点可以互相到达。

顶点的度是指和该顶点相连的边的条数,对有向图来说,顶点的出边条数称为该顶点的出度,顶点的入边条数称为该顶点的入度。

顶点和边都可以有一定属性,而量化的属性称为权值,顶点的权值和边的权值分别称为点权和边权。

2.存储

图的存储方式常用的有两种:邻接矩阵和邻接表。

1.邻接矩阵

设图G(V,E)的顶点标号为0,1,…N-1,那么可以令二维数组G[N]
[N]的两维分别表示图的顶点标号,即如果G[i][i]为1,则说明顶点i和顶点j之间有边;如果G[i][j]为0,则说明顶点i和顶点j之间不存在边,而这个二维数组G[][]则被称为邻接矩阵。如果存在边权,则可以令G[i][j]存放边权,对不存在的边可以设边权为0、-1或者一个很大的数。无向图与对应的邻接矩阵如下图所示。

在这里插入图片描述邻接矩阵

2.邻接表

设图G(V,E)的顶点编号为0,1,…N-1,每个顶点都可能有若干条出边,如果把同一个顶点的所有出边放在一个列表中,那么N个顶点就会有N个列表(没有出边,则对应空表)。这N个列表被称为图G的邻接表,记为Adj[N],其中Adj[N]存放顶点i的所有出边组成的列表,这样Adj[0],Adj[1],…,Adj[N-1]就分别都是一个列表。由于列表可以用链表实现。可以使用vector来实现链表,vector有变长数组之称,因此可以开一个vector数组Adj[N],其中N为顶点个数,这样每个Adj[i]就都是一个变长数组vector,使得存储空间只与图的边数有关。

在这里插入图片描述

如果邻接表只存放每条边的终点编号,而不存放边权,则vector中的元素可以直接定义为int型,如下所示:

vector<int>   Adj[N];

如果需要同时存放边的终点编号后和边权,那么可以建立结构体Node,用来存放每条边的终点编号和边权,代码如下:

struct Node{
	int v;   //边的终点编号
	int w;    //边权
}

//这样vector邻接表中的元素类型就是Node型的
vector<Node>  Adj[N];

图的遍历是指对图的所有顶点按一定顺序进行访问,遍历方法一般有两种:深度优先搜索(DFS)和广度优先搜索(BFS)。

3.图的深度优先搜索

深度优先搜索以“深度”作为第一个关键词,每次都是沿着路径到不能再前进时才退回到最近的岔道口,以一个有向图为例,深度优先遍历搜索路径为:V0、V1、V3、V4、V5、V2。

在这里插入图片描述
DFS具体实现需要理解的概念:
(1)连通分量:在无向图中,如果两个顶点之间可以互相到达(可以是通过一定路径间接到达),那么称为这两个顶点连通。如果图G(V,E)的任意两个顶点都连通,则称图G为连通图;否则称图G为非连通图,且称其中的极大连通子图为连通分量。
(2)强连通分量:在有向图中,如果两个顶点可以各自通过一条有向路径到达另一个顶点,就称这两个顶点强连通。如果图G(V,E)的任意两个顶点都强连通,则称图G为强联通图;否则称图G为非强连通图,且称其中的极大强连通子图为强连通分量。
连通分量和强连通分量均称为连通块。

DFS遍历图的基本思路就是将经过的顶点设置为已访问,在下次递归碰到这个顶点就不再去处理,直到整个图的顶点都标记为已访问。如果已知给定的图是一个连通图,则只需要一次DFS就能完成遍历。
伪代码如下:

DFS(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所在的连通块 
		} 
	}
	 
} 

邻接矩阵版DFS

constint MAXV = 1000;    //最大顶点数
constint INF = 1000000000;   // 设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++){
	 	if(vis[v] == false && G[u][v] != INF){ //如果v未被访问,且u可到达v 
	 		DFS(v, depth+1);   //访问,深度加1 
		 }
	 } 
	
} 

void DFSTrave() {   //遍历图G 
	for(int u=0; u<n; u++) {  //对每个顶点v 
		if(vis[u] == false){//如果u未被访问
		DFS(u, 1);  //访问u和u所在的连通块,1表示初始为第一层 
		}
	}
}


邻接表版DFS

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已被访问
	  
	  for(int i = 0; i<Adj[u].size(); i++){  //对从u出发可以到达的所有顶点v 
	  		  int v = Adj[u][i];
	  		  if(vis[v]  == false){   // 如果v未被访问 
	  		  	DFS(v, depth+1);    //访问v,depth+1 
				}
	  } 
	
} 


void DFSTrave() {   //遍历图G 
	for(int u=0; u<n; u++) {  //对每个顶点v 
		if(vis[u] == false){//如果u未被访问
		DFS(u, 1);  //访问u和u所在的连通块,1表示初始为第一层 
		}
	}
}

4.图的广度优先搜索

广度优先搜索以“广度”作为关键词,每次以扩散的方式向外访问顶点,和树的遍历一样,使用BFS遍历图需要使用一个队列,通过反复取出队首顶点,将该顶点可到达的未曾加入过队列的盯电脑全部入队,直到队列为空时遍历结束。下图的遍历顺序为;V0、V1、V2、V3、V4、V5。
在这里插入图片描述BFS的具体实现:

和DFS一样,上面的例子是对单个连通块进行的遍历操作。如果要遍历整个图,则需要对所有连通块进行遍历 。使用BFS遍历图的基本思想是建立一个队列,并把初始顶点加入队列,此后每次都取出队首顶点进行访问,并把从该顶点出发可以到达的未曾加入过队列的顶点。伪代码如下:

BFS(u){   //遍历u所在的连通块
		   
	queue q;   //定义队列q
	将u入队;
	inq[u] = true;    //设置u已被加入过队列
	while(q非空){     //只要队列非空 
	 
	 取出q的队首元素u进行访问;
	 for(从u出发可达到的所有顶点){   //枚举从u能直接到达的顶点v 
	  if(inq[v] == false){     // 如果v未曾加入过队列 
	  	将v入队;
		inq[v] = true;    //设置v已被加入过队列 
	  } 
  } 
}


BFSTrave(G){    //遍历图G 
	for(G的所有顶点u)  {       //枚举G的所有顶点u 
	if( inq[u] == false){
		BFS(u);
	}
	}
}

邻接矩阵BFS:

int n, G[MAXV][MAXV];
bool inq[MAXV] = {false};

void BFS(int u){
	queue<int> 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++){  //如果v的邻接点v未曾加入过队列 
			if(inq[v] == false && G[u][v] != INF){
				q.push(v);  //将v入队 
				inq[v] = true;   //标记v为已被加入过队列 
			}    
		}
	}
} 

void BSFTrave(){//遍历图
	 for(int u=0; u<n; u++){   //枚举所有顶点 
	 	if(inq[u] == false ){    //如果u未曾加入过队列 
	 		BFS(q);   //遍历u所在的连通块 
		 }
	 } 
}

邻接表BFS:

vector<int>  Adj[MAXV];
int n;
bool inq[MAXV] = {false}; 

void inq[MAXV] = {false};

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++){  //枚举从u出发能到达的所有顶点 
			int v = Adj[u][v];
			if(inq[v] == false){    //如果v未曾加入过队列 
				q.push(v);  //将v入队 
				inq[v] = true;  //标记v为已被加入过队列 
			}
		}
	}
} 


void BSFTrave(){//遍历图
	 for(int u=0; u<n; u++){   //枚举所有顶点 
	 	if(inq[u] == false ){    //如果u未曾加入过队列 
	 		BFS(q);   //遍历u所在的连通块 
		 }
	 } 
}

  • 12
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值