web-dev-qa-db-ja.com

プログラミング理論:迷路を解く

迷路を解決する可能な方法は何ですか?
2つのアイデアがありますが、それらはあまりエレガントではないと思います。

基本状況:マトリックスがあり、このマトリックス内の要素は、迷路を表すように、1つの方法で1つの方法で、1つの方法で1つの順序で並べられています。

私の最初のアイデアは、迷路を出るまでロボットを迷路の中に送り、一方の側を追いかけることでした。これは非常に遅い解決策だと思います。

2番目のものは、1でマークされたすべての連続するアイテムを通過し、どこに進むことができるか(上、右、下、左)をチェックして、1つの方法を選択し、そこでパスを継続します。これは最初のものよりもさらに遅いです。

もちろん、すべてのジャンクションで2つのボットをマルチスレッド化すると少し高速になりますが、これも最善の方法ではありません。

迷路を介してボットを送信するには、より良いソリューションが必要です。

[〜#〜] edit [〜#〜]
最初:素敵な答えをありがとう!

私の質問の2番目の部分は:多次元グラフがある場合に何をすべきか?そのための特別な慣行はありますか、それともジャスティンL.の答えもそのために使用できますか?
この場合、これは最善の方法ではないと思います。

番目の質問:
これらの迷路ソルバーアルゴリズムのうち、どれが最速ですか? (純粋に仮説的に)

67
Max Ruf

あなたは迷路を木と考えることができます。

 A 
/\ 
/\ 
 BC 
/\/\ 
 DEFG 
/\\
 HIJ 
/\ 
 LM 
/\ 
 ** O 
 
(おそらく表現可能)
 
 START 
 + + --- + --- + 
 | A C G | 
 + --- + + + + 
 | D B | F | J | 
 + --- + --- + + --- + --- + 
 | L H E I | 
 + --- + + --- + --- + 
 | M O | 
 + + --- + 
 FINISH 
 
(ツリーの左右の順序付けを無視)

各ノードはパスのジャンクションです。 D、I、J、L、Oは行き止まりで、**が目標です。もちろん、実際のツリーでは、各ノードにthreeの子が存在する可能性があります。

ここでの目標は、単純にどのノードを経由してフィニッシュを見つけるかを見つけることです。すべてのol 'ツリー検索アルゴリズムで十分です。

ツリーを見ると、ツリーの最も深い部分の**から単純に「トレース」するだけで、正しい解決策を簡単に確認できます。

A B E H M **

このアプローチは、迷路内に「ループ」がある場合(つまり、バックトレースなしで可能な場合は、パッセージを再入力する場合)わずかにだけ複雑になることに注意してください。既に通過した)。 Niceソリューションのコメントを確認してください。

さて、このツリーに適用した最初のソリューションを見てみましょう。

最初の解決策は基本的に Depth-First Search で、これはそれほど悪くはありません。実際にはかなり良い再帰検索です。基本的に、「常に一番右のアプローチを最初に取ります。何もない場合は、まっすぐまたは左に行くことができる最初の場所までバックトラックし、それから繰り返します。

深さ優先検索は、上記のツリーを次の順序で検索します。

A B D (backtrack) E H L (backtrack) M ** (backtrack) O (backtrack thrice) I
(backtrack thrice) C F (backtrack) G J

**が見つかったらすぐに停止できることに注意してください。

ただし、実際に深さ優先検索をコーディングする場合、再帰プログラミングを使用すると、すべてがはるかに簡単になります。反復的な方法でも機能し、バックトラックする方法を明示的にプログラムする必要はありません。実装については、リンクされた記事をご覧ください。

ツリーを検索する別の方法は、 Breadth-First ソリューションです。これは、ツリーを深さで検索します。上記のツリーを次の順序で検索します。

A (next level) B C (next level) D E F G (next level)
H I J (next level) L M (next level) ** O

迷路の性質上、幅優先では、チェックするノードの平均量がはるかに多いことに注意してください。 Breadth-firstは、検索するパスのキューを作成し、各反復でキューからパスをポップし、1ステップ後に変換できるすべてのパスを取得して新しいパスを配置することで「展開」することにより、簡単に実装できます。キューの最後。コーディングするための明示的な「次のレベル」コマンドはなく、それらは理解を助けるためだけにありました。

実際、全体の ツリーを検索する方法の拡張リスト があります。 2つの最も単純で最も単純な方法について説明しました。

あなたの迷路が非常に長く、深く、ループと狂気があり、複雑な場合、 A * をお勧めします=アルゴリズム。これは、幅優先探索とヒューリスティックを組み合わせた業界標準の経路探索アルゴリズムです。「インテリジェントな幅優先探索」のようなものです。

基本的には次のように機能します。

  1. 1つのパスをキューに入れます(迷路に1ステップだけまっすぐ進むパス)。パスには、現在の長さ+端からの直線距離(数学的に計算可能)によって与えられる「重み」があります。
  2. ウェイトが最小のパスをキューからポップします。
  3. 1つのステップの後にある可能性のあるすべてのパスにパスを「分解」します。 (つまり、パスが右、左、右の場合、展開されたパスはR L L R RおよびR L L R Lであり、壁を通過する違法なパスは含まれません)
  4. これらのパスの1つに目標がある場合、勝利!さもないと:
  5. 展開されたパスの重みを計算し、それらすべてをキューに戻します(元のパスは含まれません)
  6. キューを重みで並べ替え、最も低いものから順番に並べます。その後、手順2から繰り返します

そして、それはA *であり、allオフロードパスや山などを避けながら、マップの1つのエッジから別のエッジに移動するなど、パスファインディングのアプリケーション。最短距離のヒューリスティックを使用するため、非常に効果的です、それはそれに「知性」を与えます。 A *は非常に用途が広いため、問題が発生した場合、可能な限り最短の距離ヒューリスティックが利用可能であれば(簡単です-直線)、適用できます。

[〜#〜] but [〜#〜]A *はnot唯一のオプション。

実際、 ツリートラバーサルアルゴリズムのウィキペディアカテゴリ は97のみをリストしています! (ベストはまだ このページ 以前にリンクされています)

長さ= Pで申し訳ありません(私はとりとめがちです)

158
Justin L.

多くの迷路解決アルゴリズムが存在します:

http://en.wikipedia.org/wiki/Maze_solving_algorithm

http://www.astrolog.org/labyrnth/algrithm.htm#solve

ロボットの場合、 Tremauxのアルゴリズム は有望に見えます。

13
willoller

興味深いアプローチは、少なくとも私が面白いと思ったのは、セルオートマトンを使用することです。つまり、3つの「壁」セルに囲まれた「空間」セルは「壁」セルに変わります。最後に残っている唯一のスペースセルは、出口へのルート上のものです。

ジャスティンが答えを入れたツリーを見ると、リーフノードに3つの壁があることがわかります。パスができるまでツリーを剪定します。

11
Andre Artus

これは私のお気に入りのアルゴリズムの1つです。

1) Move forward
2) Are you at a wall?
2a) If yes, turn left
3) Are you at the finish?
3a) If no, go to 1
3b) If yes, solved
4
Nate Noonen

マトリックスからグラフを作成し、幅優先検索、深さ優先検索、またはダイクストラアルゴリズムを使用してはどうですか。

4
Kungi

これは、C++で迷路をシミュレートするための非常に単純な表現です:)

#ifndef vAlgorithms_Interview_graph_maze_better_h
#define vAlgorithms_Interview_graph_maze_better_h

static const int kMaxRows = 100;
static const int kMaxColumns = 100;

class MazeSolver
    {
private:
    char m_matrix[kMaxRows][kMaxColumns]; //matrix representation of graph
    int rows, cols; //actual rows and columns

    bool m_exit_found;
    int m_exit_row, m_exit_col;
    int m_entrance_row, m_entrance_col;

    struct square //abstraction for data stored in every verex
        {
        pair<int, int> m_coord; //x and y co-ordinates of the matrix
        square* m_parent; //to trace the path backwards

        square() : m_parent(0) {}
        };

    queue<square*> Q;

public:
    MazeSolver(const char* filename)
        : m_exit_found(false)
        , m_exit_row(0)
        , m_exit_col(0)
        , m_entrance_row(0)
        , m_entrance_col(0)
        {
        ifstream file;
        file.open(filename);

        if(!file)
            {
            cout << "could not open the file" << endl << flush;
            // in real world, put this in second phase constructor
            }
        init_matrix(file);
        }
    ~MazeSolver()
        {
        }
    void solve_maze()
        {
        //we will basically use BFS: keep pushing squares on q, visit all 4 neighbors and see
        //which way can we proceed depending on obstacle(wall)

        square* s = new square();
        s->m_coord = make_pair(m_entrance_row, m_entrance_col);

        Q.Push(s);

        while(!m_exit_found && !Q.empty())
            {
            s = Q.front();
            Q.pop();

            int x = s->m_coord.first;
            int y = s->m_coord.second;
            //check if this square is an exit cell
            if(x == m_exit_row && y == m_exit_col)
                {
                m_matrix[x][y] = '>'; // end of the path
                m_exit_found = true;
                //todo: try breaking? no= queue wont empty
                }
            else
                {
                //try walking all 4 neighbors and select best path
                //NOTE: Since we check all 4 neighbors simultaneously,
                //      the path will be the shortest path
                walk_path(x-1, y, s);
                walk_path(x+1, y, s);
                walk_path(x, y-1, s);
                walk_path(x, y+1, s);
                }
            } /* end while */

        clear_maze(); //unset all previously marked visited shit

        //put the traversed path in maze for printing
        while(s->m_parent)
            {
            m_matrix[s->m_coord.first][s->m_coord.second] = '-';
            s = s->m_parent;
            } /* end while */
        }

    void print()
        {
        for(int i=0; i<rows; i++)
            {
            for(int j=0; j<cols; j++)
                cout << m_matrix[i][j];
            cout << endl << flush;
            }
        }

private:
    void init_matrix(ifstream& file)
        {
        //read the contents line-wise
        string line;
        int row=0;
        while(!file.eof())
            {
            std::getline(file, line);
            for(int i=0; i<line.size(); i++)
                {
                m_matrix[row][i] = line[i];
                }
            row++;
            if(line.size() > 0)
                {
                cols = line.size();
                }
            } /* end while */
        rows = row - 1;

        find_exit_and_entry();
        m_exit_found = false;
        }

    //find and mark ramp and exit points
    void find_exit_and_entry()
        {
        for(int i=0; i<rows; i++)
            {
            if(m_matrix[i][cols-1] == ' ')
                {
                m_exit_row = i;
                m_exit_col = cols - 1;
                }
            if(m_matrix[i][0] == ' ')
                {
                m_entrance_row = i;
                m_entrance_col = 0;
                }
            } /* end for */
        //mark entry and exit for testing
        m_matrix[m_entrance_row][m_entrance_col] = 's';
        m_matrix[m_exit_row][m_exit_col] = 'e';
        }

    void clear_maze()
        {
        for(int x=0; x<rows; x++)
            for(int y=0; y<cols; y++)
                if(m_matrix[x][y] == '-')
                    m_matrix[x][y] = ' ';
        }
        // Take a square, see if it's the exit. If not, 
        // Push it onto the queue so its (possible) pathways
        // are checked.
    void walk_path(int x, int y, square* parent)
        {
        if(m_exit_found) return;
        if(x==m_exit_row && y==m_exit_col)
            {
            m_matrix[x][y] = '>';
            m_exit_found = true;
            }
        else
            {
            if(can_walk_at(x, y))
                {
                //tag this cell as visited
                m_matrix[x][y] = '-';

                cout << "can walk = " << x << ", " << y << endl << flush;

                //add to queue
                square* s = new square();
                s->m_parent = parent;
                s->m_coord = make_pair(x, y);
                Q.Push(s);
                }
            }
        }

    bool can_walk_at(int x, int y)
        {
        bool oob = is_out_of_bounds(x, y);
        bool visited = m_matrix[x][y] == '-';
        bool walled = m_matrix[x][y] == '#';

        return ( !oob && !visited && !walled);
        }
    bool is_out_of_bounds(int x, int y)
        {
        if(x<0 || x > rows || y<0 || y>cols)
            return true;
        return false;
        }
    };


void run_test_graph_maze_better()
        {
        MazeSolver m("/Users/vshakya/Dropbox/private/graph/maze.txt");
        m.print();
        m.solve_maze();
        m.print();
        }


#endif
3
Viren

ただのアイデア。モンテカルロ風にボットを投げてみませんか。ボットの第1世代をgen0と呼びましょう。このようにして、いくつかの連続した道路があるボットのみをgen0から保持します。
-開始からある時点まで
または-ある時点から最後まで

ボットの新しいgen1を新しいランダムドットで実行し、次にgen1のボットの道路をgen0の道路と接続して、最初から最後まで連続した道路が得られるかどうかを確認します。

そのため、gennについては、gen0、gen1、...、genn-1のボット形式に接続しようとします。

もちろん、世代は実現可能な時間だけ持続します。

アルゴリズムの顔色が小さなデータセットに対して実用的であることが証明されるかどうかはわかりません。
また、アルゴリズムは、開始点と終了点を知っていると仮定します。


アイデアに適したサイト:
http://citeseerx.ist.psu.edu/
http://arxiv.org/

2
19021programmer

ロボットがその場所を追跡できるため、以前にその場所に行ったことがあるかどうかがわかる場合、深さ優先の検索が明白なアルゴリズムです。敵対的な議論によって、深さ優先検索よりも最悪の場合のパフォーマンスを向上させることはできないことを示すことができます。

ロボットで実装できない手法を使用できる場合、幅優先探索は、グラフ内の最短経路を見つけるためのダイクストラのアルゴリズムと同様に、多くの迷路でより優れたパフォーマンスを発揮します。

1
Norman Ramsey

University Compの1つでも同様の問題がありました。科学コース。私たちが思いついた解決策は、左手の壁に従うことでした(右の壁も同様に機能します)。ここにいくつかの擬似コードがあります

While Not At End
    If Square To Left is open,
        Rotate Left
        Go Forward
    Else
        Rotate Right
    End If
Wend

それは基本的にそれです。複雑な部分は、あなたが向いている方向を追跡し、この方向に基づいてどのグリッド位置が左側にあるかを把握することです。それは私がそれに対して我慢したどんなテストケースでも機能した。興味深いことに、教授のソリューションは次のようなものでした。

While Not At End
    If Can Go North
        Go North
    ElseIf Can Go East
        Go East
    ElseIf Can Go South
        Go South
    ElseIf Can Go West 
        Go West
    EndIf
Wend

これは、ほとんどの単純な迷路ではうまく機能しますが、次のような迷路では失敗します。

SXXXXXXXXXXXXX
   X         X
   X         X
   X         X
 XXX         X
 X X         X
 X XXXXXXXXXXX     XXXE
 X                 X
 XXXXXXXXXXXXXXXXXXX

SとEが開始および終了です。

壁に沿っていないものでは、必要に応じて行き止まりになり、行き止まりになったとき、巻き込まれないように、過去の場所のリストを保持する必要がありますループで。壁をたどれば、どこに行ったのかを追跡する必要はありません。迷路を通る最適なパスは見つかりませんが、常に通過します。

1
Kibbee

多くのアルゴリズムがあり、最適なアルゴリズムを指定する多くのさまざまな設定があります。これは興味深い設定についての1つのアイデアです。

次のプロパティがあるとしましょう...

  • ロボットを動かして、CPU使用率ではなく動きを最小化するにしたい場合。
  • そのロボットは、隣接するセルのみを検査するか、廊下交差点を見るかどうかを確認できます。
  • [〜#〜] gps [〜#〜]があります。
  • 目的地の座標を知っています。

その後、A.I。を設計できますこれは...

  • 迷路に関する新しい情報を受信するたびにマップを描画します。
  • 既知の最小値パス長さすべて観測されていない位置(およびそれ自体と宛先)を計算します。
  • 周囲の構造に基づいて、検査のために観察されていない位置に優先順位を付けることができます。 (とにかくそこから目的地に到達できない場合...)
  • 目的地までの方向と距離に基づいて、検査のために観察されていない位置に優先順位を付けることができます。
  • 情報収集に関する経験に基づいて、検査のために観察されていない位置に優先順位を付けることができます。 (平均してどれくらい遠くまで見ることができ、どこまで歩く必要がありますか?)
  • 観察されていない位置を可能なショートカットを見つけるに優先させることができます。 (経験:多くのloops?)
1
comonad

このアズカバンアルゴリズムも役立つ場合があります http://journals.analysisofalgorithms.com/2011/08/efficient-maze-solving-approach-with.html

0
raghavankl

スタックオーバーフローに関するすべての質問と同じ答え;)

Viを使用してください!

http://www.texteditors.org/cgi-bin/wiki.pl?Vi-Maze

テキストエディタがASCII迷路を解決するのを見るのは本当に魅力的です。

0
schemathings

特にあなたの場合ではありませんが、私は Leeのアルゴリズム をすぐにコード化するのに非常に便利だとわかったいくつかのプログラミングコンテストの質問に出くわしました。すべての場合に最も効率的ではありませんが、簡単に解決できます。 one 私はコンテストのためにハックしました。

0
Jubin Chheda

迷路を解決する最良の方法は、ユニオン検出などの接続アルゴリズムを使用することです。これは、パス圧縮が行われたと仮定した準線形時間アルゴリズムです。

Union-Findは、セット内の2つの要素が推移的に接続されているかどうかを示すデータ構造です。

ユニオン検索データ構造を使用して迷路を解決するには、まず、隣接接続データを使用してユニオン検索データ構造を構築します。次に、共用体検索が圧縮されます。迷路が解けるかどうかを判断するために、入り口と出口の値が比較されます。それらが同じ値を持っている場合、それらは接続されており、迷路は解ける。最後に、解決策を見つけるために、入り口から始めて、各近隣に関連付けられているルートを調べます。現在のセルと同じルートを持つ以前に訪問していない隣人を見つけるとすぐに、そのセルを訪問し、プロセスを繰り返します。

このアプローチの主な欠点は、複数のパスがある場合、迷路を通る最短ルートを教えないことです。

0
Tyler Durden