图的遍历

【概述】

根据搜索方法的不同,分为深度优先遍历(DFS)、广度优先遍历(BFS),两者时间复杂度都是 O ( N ∗ N ) O(N*N) O(NN),通常采用深度优先遍历。

【深度优先遍历】

图的深度优先遍历的基本过程为:

  1. 从图中某个顶点 v 0 v_0 v0 出发,首先访问 v 0 v_0 v0
  2. 访问结点 v 0 v_0 v0 的第一个邻接点,以这个邻接点 v t v_t vt 作为一个新节点,访问 v t v_t vt 所有邻接点,直到以 v t v_t vt 出发的所有节点都被访问
  3. 回溯到 v 0 v_0 v0 的下一个未被访问过的邻接点,以这个邻结点为新节点,重复步骤 2 2 2,直到图中所有与 v 0 v0 v0 相通的所有节点都被访问
  4. 若此时图中仍有未被访问的结点,则另选图中的一个未被访问的顶点作为起始点,重复步骤 1 1 1,直到图中的所有节点均被访问
图的遍历_第1张图片

(1)数组模拟邻接表

int dfs(int u)
{
    st[u] = true;			// 表示点u已经被遍历过

    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j]) dfs(j);
    }
}

如果要遍历整个图,就需要对所有连通块分别进行遍历。

当然,如果已知给定的图是一个连通图,则只需要一次DFS就能完成遍历。

(2)邻接矩阵版

const int MAXV = 1000;  			// 最大顶点数
const int 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和所在的连通块,1表示初始为第一层
		}
	}
}

(3)邻接表版

vector<int> Adj[MAXV];  				// 图G的邻接表

int n;  								// n为顶点数,MAXV为最大顶点数

bool vis[MAXV] = {false};  				// 如果顶点已被访问,则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][i];
		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表示初始为第一层 
		}
	}
}

【广度优先遍历】

图的广度优先遍历的基本过程为:

  1. 从图中某个顶点 v 0 v_0 v0 出发,首先访问 v 0 v_0 v0,将 v 0 v_0 v0 加入队列
  2. 将队首元素的未被访问过的邻接点加入队列,访问队首元素并将队首元素出队,直到队列为空
  3. 若此时图中仍有未被访问的结点,则另选图中的一个未被访问的顶点作为起始点,重复步骤 1 1 1,直到图中的所有节点均被访问过。
图的遍历_第2张图片

(1)数组模拟邻接表

queue<int> q;
st[1] = true; 					// 表示1号点已经被遍历过
q.push(1);

while (q.size())
{
    int t = q.front();
    q.pop();

    for (int i = h[t]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!s[j])
        {
            st[j] = true; 		// 表示点j已经被遍历过
            q.push(j);
        }
    }
}

同样地,如果要遍历整个图,则需要对所有连通块分别进行遍历。

当然,如果已知是连通图,那么只需要一次BFS就能完成遍历。

(2)邻接矩阵版

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()
{
	for(int u = 0; u < n; u++)			// 枚举所有顶点
	{
		if(inq[u] == false)  			// 如果u未曾加入过队列
		{
			BFS(u);  					// 遍历u所在的连通块
		}
	}
}

(3)邻接表版

vector<int> Adj[MAXV];   				// 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)
			{ 
				q.push(v) ;  			// 将v入队
				inq[v] = true;  		// 标记v为已被加入过队列
			}
		}
	}
}

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

如果需要输出该连通块内所有其他顶点的层号,这时只需要修改少量内容即可达到要求,以邻接表为例:

struct Node
{
	int v;  										// 顶点编号
	int layer;  									// 顶点层号
};

vector<Node> Adj[N];

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][i];  				// 从u出发能到达的顶点next
			next.layer = topNode.layer + 1; 		// next层号等于当前顶点层号加1
			if(inq[next.v] == false)				// 如果next的编号未被加入过队列
			{
				q.push(next); 						// 将next入队
				inq[next.v] = true;  				// next的编号设为已被加入过队列
			}
		}
	}
}

【例题】

  • PTA 1034 Head of a Gang (30 分) DFS:点击这里
  • PTA 1076 Forwards on Weibo (30 分) BFS:点击这里
  • AcWing 846. 树的重心 DFS:点击这里
  • AcWing 847. 图中点的层次:点击这里
  • 2017年第八届蓝桥杯决赛 基环树找环:点击这里

你可能感兴趣的:(图的遍历)