迷路問題って何ですか?
迷路問題は古典的なアルゴリズム問題であり、始点から終点までの最短経路を見つけることを目的としています。通常、迷路は2 次元の行列で表されます。0 は通行可能なスペースを表し、1 は通行できない壁を表します。
この状況では、幅優先探索 (BFS) や深さ優先探索 (DFS) などのデータ構造でグラフ アルゴリズムを使用して、始点から終点までの最短パスを見つける必要があります。
- たとえば、次のような迷路があります。
111111111111111
100010001000001
101110101011001
100000101000101
111110111010101
100000001010101
101111111010101
101000001010001
101111101110101
100010000000101
111110111110101
100000100000101
111110101011101
100000001000001
111111111111111
(1 は壁が通過できないことを意味し、0 はパスが通過できることを意味します。始点は (1,1)、終点は (15,15) です。)
迷路の問題をどうやって解くのか?
DFS (深さ優先検索)
ディープサーチの基礎知識については、私の以前のブログ: [アルゴリズムの基礎] ディープサーチを参照してください。
- 深さ優先探索 (DFS) アルゴリズムを使用して、迷路の問題を解決します。具体的な考え方は次のとおりです。
- 開始点を選択し、訪問済み配列を作成して、各点が通過したかどうかを記録します。
- 開始地点として、周囲の探索を開始します。未踏破の地点に到達できたら、先に進めなくなるか出口に到達できなくなるまで、その地点まで進み続けます。
- 探索プロセス中に、各ポイントのステータスを訪問済み配列に更新し、訪問済みとしてマークします。
- 出口に行けば、実行可能なパスが見つかったことになります。そうでない場合は、前のノードに戻り、すべての状態を訪問するまで他の方向から探索を続けます。
ただし、深い探索は迷路の問題を解くのには適しておらず、得られる解が間違っている場合もあります。DFS アルゴリズムは理解と実装が簡単ですが、バックトラック時間が多く、時間の複雑さが高いなどの欠点があることに
実装中に注意する必要があります。したがって、実際のアプリケーションでは、アルゴリズムの時間計算量を慎重に考慮し、適切なデータ構造と最適化手法を選択してプログラムの効率を向上させる必要があります。
-
間違ったケースを見てみましょう。
迷路を表すために 2 次元配列が使用され、各要素はその位置の状態 (壁、通路など) を表します。このうち、「O」は到達可能点、「X」は到達不能点、「S」は開始点、「E」は終了点を表します。 -
まず、初期配列のファイルは次のようになります。
-
最短経路は明らかに S から E に直接到達するものであり、問題は 1 ステップで解決できますが、実際には dfs は 27 ステップを実行しています。。。
dfs のコードは次のようなものであるため、
//ei, ej表示终点坐标,si, sj表示起点坐标,i, j表示现在的坐标
int dfs(int ei, int ej, int si, int sj, int i, int j){
int flag = 0; //标记是否到达终点
a[i][j] = '.'; //标记此位置,表示已经访问过
if(i == ei && j == ej){
flag = 1;
if(cnt <= min_cnt) min_cnt = cnt;
}
//往右
if(flag != 1 && j+1 <= n && (a[i][j+1] == 'O' || a[i][j+1] == 'E')){
push(s, i, j);
if(dfs(ei ,ej, si, sj, i, j+1)==1){
cnt++;
flag = 1;
}
}
//往下
if(flag != 1 && i+1 <= m && (a[i+1][j] == 'O' || a[i+1][j] == 'E')){
push(s, i, j);
if(dfs(ei, ej, si, sj, i+1, j) == 1){
cnt++;
flag = 1;
}
}
//往左
if(flag != 1 && j-1 > 0 && (a[i][j-1] == 'O' || a[i][j-1] == 'E')){
push(s, i, j);
if(dfs(ei, ej, si, sj, i, j-1) == 1){
cnt++;
flag = 1;
}
}
//往上
if(flag != 1 && i-1 > 0 && (a[i-1][j] == 'O' || a[i-1][j] == 'E')){
push(s, i, j);
if(dfs(ei, ej, si, sj, i-1, j) == 1){
cnt++;
flag = 1;
}
}
if(flag != 1){
a[i][j] == 'O';
pop(s);
cnt--;
}
return flag;
}
最短経路は見つかりませんが、プログラムによって設定された順序で終点が見つかります。もちろん、次のプログラムを改良して経路探索をよりインテリジェントにすることもできますが、この方法で記述されたコードは複雑すぎます。
BFS (幅優先検索)
BFS とは異なり、BFS アルゴリズムの基本的な考え方は、開始点からマルチレベル検索を開始し、終了点が見つかるかすべての状態を訪問するまで徐々に外側に拡張することです。
- 具体的な手順は次のとおりです。
- 開始点を選択し、それをルート ノードとして BFS ツリーを構築し、キューにプッシュします。
- ノードごとに、考えられるすべての方向を列挙し、ノードの子ノードを生成して、それらをキューの末尾に追加します。
- 子ノード生成時に正当かどうかを判断し、正当でない場合は無視する必要がある。ここでは、無限ループを避けるために、訪問済み配列を使用して各ポイントが通過したかどうかを記録することをお勧めします。
- 毎回ノードがキューの先頭から取得され、キューが空になるか終わりが見つかるまでアクセスされます。
- 最後に、始点から終点までのパスを出力したい場合は、スタックと BFS を連携して使用する必要があります。
- チェーンスタックの基本操作を実現する:スタックはこの実験で迷路を解くための経路を記録するために使用され、初期化、積み重ね、積み上げ、空判定などの基本操作を実現する必要があります。
- 連鎖キューの基本操作を実現する: bfs アルゴリズムはキューを使用して迷路経路の検索を実現するため、初期化、エンキュー、デキューなどの基本操作を実現する必要があります。
BFS アルゴリズムは再帰関数を使用する必要がないため、DFS よりも実装とデバッグが容易で、最短ルートを見つけることができます。ただし、空間複雑度が高いため、実際のアプリケーションではアルゴリズムの効率やメモリ使用量に注意する必要があります。
次に、BFS アルゴリズムは複数のパスを同時に外側に拡張します。各拡張中の各パスのステップ数は同じであるため、最初に終点に到達するパスのステップ数が最小、つまり最短でなければなりません。道。
上記と同じ迷路表現方法を使用して、BFS のコードとパフォーマンスを見てみましょう。
// bfs算法,求出从起点到终点的最短路径,并输出路径中每一个点的坐标
int bfs(Point start, Point end) {
Queue queue;
initQueue(&queue);
enQueue(&queue, start);
vis[start.x][start.y] = 1;
//bfs
while (queue.front != NULL) {
Point current = deQueue(&queue);
//已经到达终点
if (isEndPoint(maze, current)) {
showPath(current, start, end);
return 1;
}
// 上下左右四个方向搜索可达点
Point up = {
current.x - 1, current.y};
Point down = {
current.x + 1, current.y};
Point left = {
current.x, current.y - 1};
Point right = {
current.x, current.y + 1};
//向上
if (up.x > 0 && isAccessible(maze, up) && !vis[up.x][up.y]) {
enQueue(&queue, up);
vis[up.x][up.y] = current.x * MAX_LEN + current.y;
}
//向下
if (down.x <= m && isAccessible(maze, down) && !vis[down.x][down.y]) {
enQueue(&queue, down);
vis[down.x][down.y] = current.x * MAX_LEN + current.y;
}
//向左
if (left.y > 0 && isAccessible(maze, left) && !vis[left.x][left.y]) {
enQueue(&queue, left);
vis[left.x][left.y] = current.x * MAX_LEN + current.y;
}
//向右
if (right.y <= n && isAccessible(maze, right) && !vis[right.x][right.y]) {
enQueue(&queue, right);
vis[right.x][right.y] = current.x * MAX_LEN + current.y;
}
}
printf("Error: no path found!\n");
return 0;
}
特定のコードはここからダウンロードできます。
最初の迷路は次のようになります。
最短パスと出力を見つけることができます。
上記のコードがすべて必要な場合は、このブログによってアップロードされたコード パッケージを参照してください。
要約する
迷路の問題は、パスが迷路内のすべての有効なグリッドを通過できるように、始点から終点までのパスを解くことです。この問題はコンピューターサイエンスで広く研究されており、それを解決するためのアルゴリズムが多数あります。
-
深さ優先検索 (DFS) アルゴリズムでは、終わりが見つかるまで再帰的に前方に探索する必要があります。そうしないと、それ以上先に進めなくなります。DFS アルゴリズムは実装が比較的簡単ですが、無限ループや非最適なソリューションが発生する可能性があります。
-
幅優先検索 (BFS) アルゴリズムは階層拡張方法を採用しており、開始点から複数のレベルで検索を開始し、終点が見つかるかすべての状態を訪問するまで徐々に外側に拡張します。BFS アルゴリズムは最短パスを見つけることができ、無限ループは発生しませんが、空間の複雑さは DFS よりも高くなります。
さらに、A* アルゴリズムはヒューリスティック検索アルゴリズムであり、迷路問題も非常にうまく解決できます。評価関数に基づいて各ノードのコストを評価し、コストに応じて次の拡張ノードを選択します。A* アルゴリズムを使用すると、最短経路をより速く見つけることができますが、適切な評価関数を設計する必要があります。
さらに、ダイクストラ アルゴリズム、IDA* アルゴリズムなど、独自の特性と適用シナリオを備えた他のアルゴリズムもあります。