グラフ検索(DFS、BFS)

グラフ検索 (グラフ トラバーサル) は、グラフの任意の頂点から開始し、グラフのすべての頂点を訪問し、各頂点を 1 回だけ訪問することを指します。

深さ優先検索

DFSのコンセプト:

深さ優先探索 (DFS) は、ある頂点 v1 から開始してグラフを検索し、各ステップはある分岐パスに沿って下方向に検索し、下方向の検索を続行できなくなった場合は、パスの前の頂点を通過した後にすべてに戻ります。 、バックトラッキング プロセス中に渡された各頂点は、渡されていない分岐を見つけようとし、アクセス可能なすべての頂点が訪問されるまで検索を続けます。深さ優先検索は、ツリーのルート優先探索に似ています。
頂点が繰り返し訪問されるのを防ぐために、訪問した頂点をマークする必要があります。説明の便宜上、未訪問の頂点は白で色付けされ、訪問済みの頂点は黒で色付けされます。

 検索プロセスでは、DFS シーケンスと呼ばれるアクセス順序に従って頂点に番号が付けられます。
上記の例の DFS シーケンスは、v1、v2、v4、v3、v5、v6、v7 です。頂点には複数の選択肢がある可能性があるため、DFS シーケンスは一意ではありません。

DFS アルゴリズムのロールバック プロセスは関数の再帰またはスタックの特性に似ているため、再帰またはスタックを使用して DFS を実装できます。

 ベクトル配列表現に基づく 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 の親ノードをグラフ G のツリー エッジとしてエッジ (u, v) と呼びます。頂点 v が黒の場合、この時点では v は u の祖先ノードでなければならず、エッジ (u, v) ) は DFS ツリー ) に含まれていないため、エッジ (u, バックエッジv グラフ G にバック エッジがある場合、無向グラフにはサイクルがあります。さらに、ノードのサブツリー間にバック エッジはありません (バック エッジがある場合は、2 つのサブツリーをマージできます)。

        有向グラフの深さ優先探索によって生成された DFS ツリーでは、ツリーのエッジは無向グラフと同じように定義されます。頂点uから頂点vまで探索する場合、vが黒の場合、vがuの祖先ノードの場合(u,v)を逆エッジ、vがuの子孫ノードの場合の3種類に分けられます。の場合、(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) は、特定の頂点から開始され、レイヤーごとに探索され、1 つのレイヤーのすべての頂点が探索された場合にのみ、次のレイヤーの探索に入ることができます。幅優先検索は、ツリーの階層の走査に似ています。

BFS の実装はキューに依存します。つまり、頂点の訪問が完了すると、まだ訪問されておらず、キュー内にないすべての隣接ポイントがキューに追加され、処理のためにキューに入れられます。DFS と同様に、頂点への繰り返しの訪問を避けるために、キューに入っていない頂点は白に設定され、キューに入った頂点は黒に設定されます。レイヤーごとの操作を実現するには、頂点を訪問した後、その頂点に隣接するすべての白い点がキューに追加され、各操作がキューの先頭から頂点を取得します。

幅優先探索プロセスでは、BFS シーケンスと呼ばれるアクセス順序に従って頂点に番号が付けられます。DFS シーケンスと同様、BFS シーケンスは一意ではありません。また、DFS と同様に、頂点の色をマークするために配列 vis も使用されます。次の実装では、STL のキューを使用します。

 

#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