dfsはずっと前に記事を書きたいと思っていましたが、ついに私の兄の深さ優先探索を書くための言い訳を見つけました!
基本概念
ディープファーストサーチはdfsとも呼ばれます。基本的な考え方は、特定のポイントから開始し、パスを選択して、南の壁にぶつかったり、振り返ったりせずに続行することです。あなたがそれを好きかどうかにかかわらず、それはとても献身的です。
あなたは現在のパスが不可能な場合がわかりに来るまで、ヘッドが通過背中合わせに、これだけの時間をレンガの壁にヒットスタック+バックトラックを行う方法を特定、その後、達成するために↓
例
dfsには多くのアプリケーションがありますが、最も基本的なのは、迷路内のパスの数を見つける問題です。たとえば、次の単純な迷路↓を選択してaから歩き始めます。毎回、bの終わりまで、上下左右の4方向に1マスしか歩けません。行くためにすべての方法を探します。
アルゴリズム的思考
まず、現在の位置に問題がないことを確認してください。次に、現在の位置から開始し、テストを開始する方向を選択します。この方向に進むことができる場合は、この新しい位置から方向を選択してテストを続けます...南の壁にぶつかって戻るまで、または迷路の出口に到達する
おなじみですか?正しい!すべての位置で実行する必要のある手順は1つです。種類!の!したがって、再帰を介してそれを行うことができます。
再帰は、再帰的出口(ベースライン条件、境界条件とも呼ばれます)を考慮する必要があります。再帰的出口とは何ですか?この機能はどこで終了できますか?(実際には、上記のように)出口に行きます!
3つの質問
コードを見る前に、立ち止まって3つの質問について考えてください。
- 行くとはどういう意味ですか?
- 誘惑の方向を選ぶ方法は?
- 振り向く方法は?
応答
- 歩けるポイントは満足です:(1)このポイントはこの写真にあります(2)このポイントは経路です(単純な迷路では壁ではありません)。1つを使用して
if
判断してください。 - 選択して裁判の方向あなたは次の行くべきところである。いくつかの可能な選択肢があります。これらの選択肢を横断する必要がある(単一選択は、直接、後に再帰に加え、複数の選択肢を使用している
for
すべてを横断するループ) - 戻ることは戻ることです。なぜなら、戻って自分のポイントがもはや実現不可能であることを示したい場合は、この位置に対して行ったすべてのことを元に戻すからです。この位置に移動するためにこの位置をxとしてマークする必要がある場合は、この位置の元の状態を復元します。
たとえば、迷路の問題では、あるポイントから到達可能なポイントは、上、下、左、右の4つの位置であるため、これらの4つの位置をトラバースします。2次元配列で表されるグラフに実装するにはどうすればよいですか?上の点a(x、y)は(x-1、y)、下の点は(x + 1、y)、左:(x、y-1)右:(x、y + 1)
もちろんできます4つの再帰呼び出しステートメントを使用して↓を達成します。ここでgはグラフを表します
g[x][y] = ' '; //标记通路
dfs(x + 1, y); //往下试探
dfs(x - 1, y); //往上试探
dfs(x, y + 1); //往右试探
dfs(x, y - 1); //往左试探
g[x][y] = 'o'; //回溯,撤销操作
少しトリッキーな方法(見たくない場合は、上記の方法を使用してテストしてください。コードの説明にスキップできます):
仮定4x2
2次元アレイを(あなたは2次元アレイを理解していない場合は、一次元の両者を使用して同じであり、具体的な方法については後述する)、最初の次元は、最大4つの方向を示す、4つの量を有しています、下、左、右で、2番目の次元には2つあります。各量は表現x
を表しますy
。つまり、この方向に試したx
場合、またはy
それを変更する方法↓
int go[4][2] = {
{
1, 0},{
-1, 0},{
0, 1},{
0, -1}};//上下左右试探四个方向
2次元配列のヒューリスティックな方法は上記のとおりであり、それぞれの内側の中括弧は方向を表します。たとえば、(x, y)
この位置に上がりたい場合は、(x + go[1][0], y + go[1][1])
スムーズに取得できます(x - 1, y)
。
そのような配列を使用するのは面倒ですか?いいえ、このことを上下に誘惑する配列を使用する場合は、j = 0~3
forループを使用してトラバーサルを実行できます。ループの内側(x + go[j][0], y + go[j][1])
g[x][y] = ' '; //标记通路
for(int j = 0; j < 4; j++) //试探四个方向
dfs(x + go[j][0], y + go[j][1]);
g[x][y] = 'o'; //回溯,,撤销操作
2つの1ビット配列を使用してテストすることは同じですが、xとyを表す位置の変更が、2次元に配置されるのではなく、2つの配列に分割される点が異なります。記事の最後に記載されています。
コードの説明
コードを書く前に、アイデアをもう一度見てみましょう。
- 現在の位置に移動できるかどうかを判断します
- 歩くことができるかどうかをマークし、この位置から開始して、上下左右の4つの方向を探索します
- 再帰的な出口は最後までです(現在の位置==終了位置)
ps:ここで、迷路のパスはでo
表され、壁はでx
表され、グラフの2次元行列gといくつかの変数はすべてグローバルです。このように書きたくないので、パラメータに入れることができます
#include<bits/stdc++.h>
using namespace std;
const int N = 505; //假设地图大小不超过这个范围
char g[N][N];
int n, tx, ty; //图的实际大小,终点坐标
int go[4][2] = {
{
1, 0},{
-1, 0},{
0, 1},{
0, -1}};//上下左右试探四个方向
void printGraph(){
//输出图
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
cout<<g[i][j];
}
cout<<endl;
}
}//printGraph()
void dfs(int x, int y){
//从图g的当前位置(x,y)走到终点(tx,ty)的深搜
if(x == tx && y == ty){
//走到终点
g[x][y] = ' '; //终点也标记一次通路,或者你也可以用其它特殊字符标记终点
printGraph();
return;
}
if(g[x][y] == 'o' && x >= 0 && y >= 0 && x < n && y < n){
//这个点可以走吗?
g[x][y] = ' '; //标记通路
for(int j = 0; j < 4; j++){
//试探四个方向
dfs(x + go[j][0], y + go[j][1]);
}
g[x][y] = 'o'; //回溯,能执行到这里说明这个点已经不可行或者可行解已经输出。还原这个位置的状态
}
}//dfs()
int main(){
int x, y;
cin>>n; //图的边长
cin>>x>>y; //起点
cin>>tx>>ty; //终点
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
cin>>g[i][j];
cout<<endl;
dfs(x, y);
return 0;
}
テストケース
8
0 0
7 7
oxxxxxxx
oooooxxx
xoxxooox
xoxxoxxo
xoxxxxxx
xoxxooox
xooooxoo
xxxxxxxo
それだけです、それはかなり簡単ではありませんか?実際、アイデアは3つの文です。
- 現在のポイントが実行可能かどうかを判断する
- 変更する場合は、この時点の状態を実現可能で、その後再帰全てのテストこの時点によって到達可能な点を
- トライアルが完了したら戻って、この時点で元の状態に戻します
パスの数を要求する必要がある場合は、再帰的な終了操作を変更するだけです。ディープ検索はパスの数(実行可能なソリューションの数)を見つけるのに適しており、ワイド検索は最短パスの数(実行可能な最良のソリューション)を見つけるのに適しています。次回はワイド検索を記述します。。
テスト用の2つの1ビットアレイ
ああ、2つの1桁の配列を試すのをほとんど忘れていました。xとyの試行方向をそれぞれ表すために、2つの1ビット配列を使用します。
int dy[4] = {
0,1,0,-1}; //控制左右移动
int dx[4] = {
-1,0,1,0}; //控制上下移动
2つの1桁の配列を使用する場合、対応する添え字が一致する必要があることに注意してください。つまり、上に行くとき(x + dx[j], y + dy[j])
、j
対応する位置は2つの配列で一致する必要があります。