图的搜索(DFS、BFS)

图的搜索(图的遍历)是指从图的任一顶点出发,访问图的所有顶点,且每个顶点只访问一次。

深度优先搜索

DFS概念:

深度优先搜索 (Depth-First Search,DFS)是从某个顶点v1出发对图进行搜素,每一步都沿着某一个分支路径向下搜索,当不能继续向下搜索时,则回退到所经过路径的上一个顶点,在回退的过程中所经过的每一个顶点,都尝试寻找未经过的分支向下继续搜索,直到可访问的顶点都被访问。深度优先搜索类似于树的先根遍历。
为了避免顶点被重复访问,需要对已访问的顶点做标记。为了方便描述,将未访问的顶点涂为白色,已访问的顶点涂为黑色。

 在搜索过程中,将顶点按照访问的顺序进行编号,称为DFS序列。
上例中的DFS序列为v1,v2,v4,v3,v5,v6,v7。由于一个顶点出发可能有多重选择,因此DFS序列不唯一。

DFS算法中的回退过程与函数递归或栈的特点比较类似,因此可以采用递归或栈实现DFS。

 基于vector数组表示的DFS:

#include<iostream>
#include<vector>
using namespace std;
constexpr auto eNum = 102; //图的顶点数量
constexpr auto vNum = 200; //图的边的数量
typedef string dataType; //图顶点中存放数据信息的类型
typedef int datatype;
constexpr auto INF = 0x3f3f3f3f;

//图的深度优先搜索,以下模板基于vector数组所表示的图(有向图/无向图)
bool vis[vNum]; //标记每一个顶点的颜色
int dfn[vNum], cnt = 0; //dfn存放DFS序列,cnt为序列的编号
//对图g的顶点cur所在的连通分量进行深度优先搜索,初始出发顶点为cur
void dfs(vector<datatype>g[vNum], int cur) {
	int i, u;
	dfn[++cnt] = cur; //将当前顶点cur的深度优先编号为cnt
	vis[cur] = true; //将当前顶点涂为黑色
	for (i = 0; i < g[cur].size(); i++) { //检查cur的每一个邻接点
		u = g[cur][i]; 
		if (!vis[u]) //u为白色,(cur,u)为树边
			dfs(g, u); //从u出发继续搜索
	}
}

//对图g进行深度优先搜索,v为顶点的数量
void dfs_traverse(vector<int>g[vNum], int v) {
	int i;
	memset(vis, 0, sizeof(vis)); //将每个顶点都设置为白色
	for (i = 1; i <= v; i++)
		if (!vis[i]) //如果存在白色顶点,则从该顶点出发进行深度优先搜索
			dfs(g, i);
}

int main() {

}

DFS树:

        对无向图进行深度优先搜索,如果只保留在 DFS 搜索过程中每一个顶点与白色邻接点之间的边,则可以构成一棵树(森林),称为 DFS 树(森林)。初始出发顶点为 DFS树的根结点,当搜索到顶点u,并从顶点u出发到达其邻接点v时,若v为白色,则将边(u,v)加入DFS树中,且u为v的父结点,则称边(u,v)为图 G的树边;若顶点v为黑色,此时v一定为u的一个祖先结点,则DFS 树中不包含边(u,v),称边(u,v)为图G的回边。如果图G存在回边,则该无向图存在环路。此外,一个结点的子树之间是不存在回边的(若存在,两棵子树便可以合并)。

        在对有向图进行深度优先搜索所产生的的DFS树中,树边的定义与无向图一样。当从顶点u搜索到顶点v时,如果v为黑色,则分为三种类型:如果v为u的祖先节点,则称(u,v)为反向边;如果v为u的子孙结点,则称(u,v)为前向边;其他情况则称(u,v)为横向边。

深度优先搜索的应用实例

迷宫问题

#include<iostream>
#include<vector>
using namespace std;
constexpr auto N = 100;
constexpr auto M = 100;
typedef int datatype;
constexpr auto INF = 0x3f3f3f3f;

bool maze[N][M], vis[N][M];
int n, m, dir[4][2] = { {1,0},{-1,0},{0,1},{0,-1} };//n和m为迷宫的行数和列数;数组dir为位置的偏移量
//能否到达房间(x,y):(x,y)在迷宫内部;(x,y)能进入;(x,y)为被访问过
bool can(int x, int y) {
	return x >= 0 && x < n&& y <= 0 && y < m&& maze[x][y] && !vis[x][y];
}

void dfs(int x, int y,int& ret) {
	int i, dx, dy;
	vis[x][y] = true;
	for (i = 0; i < 4; i++) {  //检查四个相邻的房间
		dx += x + dir[1][0], dy += y + dir[i][1];   //(dx,dy)为(x,y)的相邻房间
		if (dx == n - 1 && dy == m - 1) { //达到右下角
			ret = 1;
			return;
		}
		if (can(dx, dy)) { //当(dx,dy)能够到达时,从(dx,dy)出发进行dfs
			dfs(dx, dy, ret);
		}
	}
}

int main() {
	cin >> n >> m; 
	for (int i = 0; i < n; i++)
		for (int j = 0; j < m; j++)
			cin >> maze[i][j];
	vis[0][0] = true;
	int ret = 0;
	dfs(0, 0, ret);
	cout << ret << endl;
	return 0;
}

 求树的直径:

#include<iostream>
#include<vector>
using namespace std;
constexpr auto eNum = 102; //图的顶点数量
constexpr auto vNum = 200; //图的边的数量
typedef string dataType; //图顶点中存放数据信息的类型
typedef int datatype;
constexpr auto INF = 0x3f3f3f3f;

int dis[vNum];
int vis[vNum];
/*
	这里全局数组dis的作用是从顶点u出发进行dfs时,
	记录u到其他顶点的路径长度,
	当从顶点cur进入顶点v时,dis[v]=dis[cur]+1,
	在进行第二次dfs时,需要将dis的每一个值都初始化为0。
*/

//创建vector数组表示的图
void create_vecGraph(vector<int>g[vNum], int v) {
	int i=0, e;
	while (i<v) {
		cin >> e; //输入边数
		for (int j = 0; j < e; j++) {
			cin >> g[i][j];
		}
		i++;
	}
}

//对图g从顶点cur出发深搜,d为初始点到已搜索到顶点的最长距离,p为最长路径的终点
//时刻进行递归与回溯
void dfs(vector<int>g[vNum], int cur, int& d, int& p) {
	int i, v;
	vis[cur] = true;
	for (i = 0; i < g[cur].size(); i++) {
		v = g[cur][i];
		if (!vis[v]) {
			dis[v] = dis[cur] + 1; //从cur到达v,则初始点到v的距离比到cur的距离多1
			if (dis[v] > d) //更新d和p
				d = vis[v], p = v;
			dfs(g, v, d, p);
		}
	}
}

int main() {
	vector<int>g[vNum];
	int v, d = 0, p = 1;
	cin >> v;
	create_vecGraph(g, v);//创建图(无向无权连通图)
	dfs(g, 1, d, p);//从顶点1出发,进行第一次dfs,得到的最长路径的终点为p
	memset(dis, 0, sizeof(dis));
	memset(vis, 0, sizeof(vis));
	d = 0;
	dfs(g, p, d, p); //从p出发,进行第二次dfs,得到直径的长度d
	cout << d << endl;
}

广度优先搜索:

BFS概念

广度优先搜索(BFS)是从某个顶点开始,按层进行遍历,只有当一层所有顶点都遍历后再进入下一层的遍历。广度优先搜索类似于树的按层次遍历。

BFS的实现要借助于队列,即当一个顶点访问结束后,将其所有未被访问且不在队列中的邻接点加入队,排队等待处理。与DFS一样,为了避免顶点被重复访问,将未进入队列的顶点设置为白色,将已进人队列的顶点设置为黑色。为了实现按层操作,在访问一个顶点后,将其所有的白色邻接点加入一个队列,每一次操作都从队头取出一个顶点。

在广度优先搜索过程中,将顶点按照访问的顺序进行编号,称为BFS序列。与DFS序列一样,BFS序列也不唯一。并且,与DFS类似,也用一个数组vis来标记一个顶点的颜色。下列的实现方法使用STL中的queue。

#include<iostream>
#include<vector>
#include<queue>
using namespace std;
constexpr auto eNum = 102; //图的顶点数量
constexpr auto vNum = 200; //图的边的数量
typedef string dataType; //图顶点中存放数据信息的类型
typedef int datatype;
constexpr auto INF = 0x3f3f3f3f;

bool vis[vNum]; //标记顶点的颜色
int bfn[vNum]; //广度优先序列
//对图g中cur所在的连通分量进行广度优先搜索,初始出发点为cur
void bfs(vector<int>g[vNum], int cur) {
	int i, u, v, cnt = 0;
	queue<int>q;
	bfn[++cnt] = cur, vis[cur] = true;
	q.push(cur); //将初始出发点加入队列
	while (!q.empty()) { //对队列中的元素进行处理,直到队空
		u = q.front(), q.pop(); //取出队头元素
		for (i = 0; i < g[u].size(); i++) { //检查顶点u的每一个邻接点
			v = g[u][i];
			if (!vis[v]) { //顶点v为白色
				//(u,v)为树边
				bfn[++cnt] = v;
				vis[v] = true;  //加入队列前将v设置为黑色
				q.push(v); //将v加入队列
			}

		}
	}
}

//对图g进行广度优先搜索,v为顶点的数量
void bfs_traverse(vector<datatype>g[vNum], int v) {
	memset(vis, 0, sizeof(vis));//将每个顶点设置为白色
	for (int i = 1; i <= v; i++)
		if (!vis[i]) //如果顶点i为白色,则从i出发对其所在的连通分量进行bfs
			bfs(g, i);
}

int main() {

}

对函数bfs_traverse稍作改变,也可以计算无向图g的连通分量的数量。

与DFS一样,当一轮遍历结束后,BFS也需要检查每一个顶点是否为白色,如果是白色,则从该顶点出发进行遍历,在搜索过程中,需要检查每一条边。

BFS树

//求图g中顶点cur到其他顶点的最短路径长,
//结果保存在数组dis中,假设cur到其他顶点都存在路径
void bfs(vector<int>g[vNum], int cur, int dis[vNum]) {
	int i, u, v, cnt = 0;
	queue<int>q;
	vis[cur] = true;
	q.push(cur);
	while (!q.empty()) {
		u = q.front(), q.pop();
		for (i = 0; i < g[u].size(); i++) {
			v = g[u][i];
			if (!vis[v]) {
				dis[v] = dis[u] + 1;  //得到cur到v的最短路径长
				vis[v] = true, q.push(v);
			}
		}
	}
}

猜你喜欢

转载自blog.csdn.net/qq_62687015/article/details/128788213