1

迷路を解くように設計されたアルゴリズムに問題があります。

ここからアルゴリズムを使用しました。http://www.cs.bu.edu/teaching/alg/maze/

FIND-PATH(x, y)

  1. if (x,y out of maze) return false
  2. (x,y が目標) の場合、true を返す
  3. (x,y が開いていない) の場合、false を返す
  4. x、y をソリューション パスの一部としてマークする
  5. if (FIND-PATH(x,y の北) == true) true を返す
  6. if (FIND-PATH(x,y の東) == true) true を返す
  7. if (FIND-PATH(x,y の南) == true) true を返す
  8. if (FIND-PATH(x,y の西) == true) true を返す
  9. ソリューション パスの一部として x,y のマークを外す
    1. false を返す

これは再帰的な解決策です。他の解決策も見つけられるように、出口を見つけた後も続行するように変更しました。私が知っている解決策の総数の半分が可能であるように見えるというだけで、うまくいくようです。

  1. if (x,y is goal) return true が false を返すように変更されました。

そのようなアルゴリズムの問​​題が何であるかを知っている人は、可能なソリューションの総数の半分になりますか? 行き止まりのパスの総数を見つけるのにも問題がありますが、それに関する提案はありますか?

4

5 に答える 5

2

欠けているように見えるのは、X&Y がソリューションの一部として既にマークされているかどうかのチェックです。そうであれば中止します。(これはポイント 3.5 のどこかにあるはずです)

どこかにループの可能性がある迷路が無期限に実行され、スタックが爆破されない場合

ところで、私が読んだアルゴリズムは、1つのソリューションしかない迷路に基づいています

R

于 2009-08-10T11:42:09.733 に答える
1

迷路を通る 1 つの方法を見つけようとするのではなく、迷路を通る複数の方法を見つける (したがってマッピングする) 必要があります。

これを行うには、(特定の経路で) 行った場所をマークする必要があります。すでに行ったことのあるポイントに到達した場合は、そのルートを行き止まりとしてフラグを立てる必要があります。

再帰関数は依然として有効ですが、(placesIhaveBeen) 構造体を再帰関数に渡すようにしてください。

N、S、E、W がすべてブロックされているポイントに到達したら、再帰ループを中断する必要があります。(前にそこに行ったことがあります。その方向に進むことはできません。迷路の外にあります) 再帰ループは、ターゲットに到達したときにも中断する必要があります。

目標を達成したら、グローバル変数を 1 増やします。行くところがない場合は、行き止まりを 1 つ増やします。

このための pcode を書くことはできません (時間がかかりすぎます) が、その秘密は、N、S、E、および W がすべてブロックされている場合に true を返す関数にあると思います。

私の最初の答えへの追加

行ったことのあるエリアをなぜ「通行止め」として扱うのか、なぜそれらを行き止まりとして扱うのかについて....

      ########
      #      #  
      # #### #
####### #  # #
        #  # #
####### #  # #
      # #### #
      #      #  
      ########

上記の迷路の部分は行き止まりに分類されますが、行ったことのある場所をブロックされたエリアとして扱わない限り、どのようにそれを特定できるかわかりません。

これにより、以下にも行き止まりが表示されることになると思いますが、それを回避する方法がわかりません。

      #######
      #     #  
      # ### #
####### #G# #
        # # #
####### #   #
      # ### #
      #     #  
      #######
于 2009-08-10T11:58:00.450 に答える
0

これはサンプルの迷路です

####################
#S #  #       #    #
#  # ##  ##  ### ###
#     #   #   #    #
## #  #   #     ## #
#     ### #####    #
# #   #   #  #   ###
# ### ### ## # #####
#  #      #       E#
####################
于 2009-08-10T18:39:14.187 に答える
0

Javaでアルゴリズムの単純な実装を作成してみました。私の結論は、あなたが説明したアルゴリズムは、複数のパスを見つける場合でも機能するということでした。あるいは、あなたは私よりも賢いテスト ケースを思いついたのかもしれません。(迷路を投稿して、アルゴリズムを試すことができます)

行き止まりカウンターの私の実装は、おそらく最も効率的なものではありませんが、仕事は完了します。アクセスされた現在の OPEN ノードごとに、周囲の 4 つのノードがチェックされます。

  • 少なくとも 1 つのネイバーが開いている場合、現在のノードは行き止まりではありません
  • 複数のネイバーが VISITED である場合、現在のノードは行き止まりではありません
  • 1 つのノードのみが VISITED (前のステップで取得したノード) であり、他のネイバーが開いていない場合、現在のノードは行き止まりです。

これは私が書いた Java コードです (かなり長いことに注意してください)。必要に応じて、パスをスタックに格納し、ノードが VISITED に設定されるたびにノードをプッシュし、OPEN に設定されるたびにノードをポップすることもできます。GOAL に到達するたびに、現在のパスを保持しているスタックをコピーして保存する必要があります。

「dead-end-investigation-step」というコメントでマークされた if ブロックを削除すると、この実装は質問で説明されている実装とほぼ同じになります。

package test;

import java.util.HashSet;
import java.util.Set;

public class MazeSolver {

final static int OPEN = 0;
final static int WALL = 1;
final static int GOAL = 2;
final static int VISITED = 3;

static int[][] field = { { 0, 0, 0, 0, 0, 1 }, { 1, 0, 1, 1, 0, 1 },
        { 1, 0, 1, 0, 0, 0 }, { 0, 0, 0, 0, 1, 2 }, { 1, 0, 1, 0, 0, 0 } };

// This is what the field looks like:
//  
// 0 1 1 0 1
// 0 0 0 0 0
// 0 1 1 0 1
// 0 1 0 0 0
// 0 0 0 1 0
// 1 1 0 2 0

static int width = field.length;
static int height = field[0].length;
static int xStart = 0;
static int yStart = 0; // Initiated to start position: (x = 0, y = 0)
static int nrSolutions = 0; // Records number of solutions

// Used for storing id:s of dead end nodes.
// The integer id is (x + y * width)
static Set<Integer> deadEnds = new HashSet<Integer>();

public static void main(String[] arg) {
    System.out.println("Initial maze:");
    printField();

    findPath(xStart, yStart);

    System.out.println("Number of solutions: " + nrSolutions);
    System.out.println("Number of dead ends: " + deadEnds.size());
}

private static void findPath(int x, int y) {

    if (x < 0 || y < 0 || x >= width || y >= height) { // Step 1
        return;
    } else if (field[x][y] == GOAL) { // Step 2
        nrSolutions++;
        System.out.println("Solution nr " + nrSolutions + ":");
        printField();
        return;
    } else if (field[x][y] != OPEN) { // Step 3
        return;
    } else if (isDeadEnd(x, y)) { // Extra dead-end-investigation-step
        int uniqueNodeId = x + y * width;
        deadEnds.add(uniqueNodeId); // Report as dead end
        return;
    }

    field[x][y] = VISITED; // Step 4

    findPath(x, y - 1); // Step 5, go north
    findPath(x + 1, y); // Step 6, go east
    findPath(x, y + 1); // Step 7, go south
    findPath(x - 1, y); // Step 8, go west

    field[x][y] = OPEN; // Step 9

    // Step 10 is not really needed, since the boolean is intended to
    // display only whether or not a solution was found. This implementation
    // uses an int to record the number of solutions instead.
    // The boolean return value would be (nrSolutions != 0)
}

private static boolean isDeadEnd(int x, int y) {
    int nrVisitedNeighbours = 0;

    if (y > 0) { // If northern neighbour exists
        if (field[x][y - 1] == VISITED) {
            nrVisitedNeighbours++;
        } else if (field[x][y - 1] != WALL) {
            return false;
        }
    }
    if (x < width - 1) { // If eastern neighbour exists
        if (field[x + 1][y] == VISITED) {
            nrVisitedNeighbours++;
        } else if (field[x + 1][y] != WALL) {
            return false;
        }
    }
    if (y < height - 1) { // If southern neighbour exists
        if (field[x][y + 1] == VISITED) {
            nrVisitedNeighbours++;
        } else if (field[x][y + 1] != WALL) {
            return false;
        }
    }
    if (x > 0) { // If western neighbour exists
        if (field[x - 1][y] == VISITED) {
            nrVisitedNeighbours++;
        } else if (field[x - 1][y] != WALL) {
            return false;
        }
    }

    if (nrVisitedNeighbours > 1) { // Circular path scenario
        return false;
    }

    return true; // The exhaustive result of this check: this is a dead
    // end
}

private static void printField() {
    for (int yy = 0; yy < field[0].length; yy++) {
        for (int xx = 0; xx < field.length; xx++) {

            System.out.print(field[xx][yy] + " ");
        }
        System.out.println();
    }
    System.out.println();
}
}

上記のアルゴリズムは、コードにハードコードされている迷路の例に 4 つの異なるソリューション パスと 2 つの行き止まりを報告します。


<EDIT> ソリューション パスが少なすぎる理由は、ソリューション パスとは何かを誤解しているからでしょうか? たとえば、次の迷路を考えてみましょう。

######
##   #
## # #
#S   #
##E###
######

この迷路には、有効なソリューション パスが 1 つしかありません。これは、各ノードに 1 回しかアクセスできないためです。円形パスを周回すると、S の東と E の北のノードに 2 回アクセスすることになるため、有効なソリューション パスではありません。ソリューション パスのこの定義は、使用するアルゴリズムによって暗示されます。

同じノードに複数回アクセスできるようにすると、円 1、2、3 ... を無限に何度も回ることができるため、無限に多くのソリューションが存在することになります。 </編集>

<EDIT2>

おっしゃる通り、ノードを VISITED に設定するたびに pathLength を増やし、VISITED ノードを OPEN に戻すたびにパスの長さを減らします。

最短パスの長さを記録するために、Integer.MAX_VALUE に対して開始する shortestPath int 値もあります。次に、目標に到達するたびに、次のことを行います。

if(pathLength < shortestPath){
    shortestPath = pathLength;
}

行き止まりについては… 手で数えてみたら、9が正しいと思いました。これはSareenによって投稿された迷路ですが、行き止まりには (手で) D のマークが付けられています。

####################
#S # D#      D#D  D#
#  # ##  ##  ### ###
#     #   #   #    #
## #  #   #     ## #
#     ### #####    #
# #   #D  #D #   ###
# ### ### ## # #####
# D#D     #D      E#
####################

もっと見つけられますか?それとも行き止まりの意味を誤解したのですか?行き止まりとは、一方向からしかたどり着けない結節のことだと思っていました。

例 1:

######
## ###
## ### 
## ### 
#S  E#
######

上の迷路には行き止まりが 1 つあります。

例 2:

######
##  ##
##  ## 
##  ## 
#S  E#
######

上記の迷路には行き止まりがありません。最も北にあるアクセス可能なノードの 1 つにいる場合でも、隣接する非 WALL マスが 2 つあります。

デッドエンドの別の定義はありますか? </EDIT2>

于 2009-08-10T16:02:16.720 に答える
0

行き止まりの数については、次のようなものが必要です。

3.5 もし (x,y の北が開いていない) と (x,y の南が開いていない) と (x,y の西が開いていない) と (x,y の東が開いていない) 行き止まり++

于 2009-08-10T11:55:28.397 に答える