Проблема с алгоритмом лабиринта - PullRequest
1 голос
/ 10 августа 2009

У меня проблема с алгоритмом, предназначенным для решения лабиринтов.

Я использовал алгоритм отсюда. http://www.cs.bu.edu/teaching/alg/maze/

НАЙТИ-ПУТЬ (х, у)

  1. если (x, y вне лабиринта) вернуть false
  2. если (x, y является целью), верните true
  3. if (x, y not open) вернуть false
  4. пометить x, y как часть пути решения
  5. if (НАЙТИ-ПУТЬ (к северу от x, y) == true) вернуть true
  6. if (FIND-PATH (к востоку от x, y) == true) возвращает true
  7. if (НАЙТИ-ПУТЬ (юг от x, y) == true) вернуть true
  8. if (FIND-PATH (запад от x, y) == true) вернуть true
  9. снять отметку x, y как часть пути решения
    1. вернуть false

Это рекурсивное решение, я изменил его так, чтобы оно продолжалось даже после поиска выхода, чтобы можно было найти и другие решения. Кажется, это работает, просто кажется, что можно найти половину общего числа решений, которые, как я знаю, возможны.

  1. если (x, y является целью), возвращаемое значение true изменяется на возвращаемое значение false.

Кто-нибудь знает, в чем может быть проблема с таким алгоритмом, приводящим к половине общего числа возможных решений? У меня также есть проблема с поиском общего числа тупиковых путей, какие-либо предложения по этому поводу?

Ответы [ 5 ]

2 голосов
/ 10 августа 2009

то, что, по-видимому, отсутствует, это проверка того, были ли X & Y уже помечены как часть решения, и если это так, мы отменяем. (это должно быть где-то в пункте 3.5)

Если бы лабиринт с возможным циклом где-то не работал бы бесконечно и взорвал бы стек

кстати, из того, что я читал, алгоритм основан на лабиринте только с 1 решением

R

1 голос
/ 10 августа 2009

Вместо того, чтобы пытаться найти один путь через лабиринт, вам нужно найти (и, следовательно, отобразить) несколько путей через лабиринт.

Для этого вам нужно отметить, где вы были (на определенном пути). Если вы достигли точки, в которой вы уже были, вам нужно пометить этот маршрут как тупик.

Рекурсивная функция - все еще путь, но удостоверьтесь, что вы передаете структуру (placeIhaveBeen) через рекурсивную функцию.

Рекурсивный цикл должен прерваться, когда вы дойдете до точки, где все N, S, E, W заблокированы. (Вы были там раньше, вы не можете идти в этом направлении, это за пределами лабиринта) Рекурсивный цикл также должен прерваться, когда вы достигнете своей цели.

Если вы доберетесь до цели - увеличьте глобальную переменную на единицу. Если вам некуда идти - увеличьте свои тупики на один.

Я не могу написать pcode для этого (это займет слишком много времени), но я считаю, что секрет в функции, которая возвращает true, если N, S, E и W все заблокированы.

Дополнение к моему первоначальному ответу

Что касается того, почему я отношусь к областям, в которых я находился, как к "заблокированным", и почему я отношусь к ним как к тупикам ...

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

Я бы классифицировал вышеуказанную часть лабиринта как тупик, и я не могу понять, как я могу идентифицировать ее как таковую, не рассматривая места, в которых я был, как заблокированные области.

Я понимаю, что это могло бы привести к тому, что следующее также зашло бы в тупик, но я не вижу выхода из этого.

      #######
      #     #  
      # ### #
####### #G# #
        # # #
####### #   #
      # ### #
      #     #  
      #######
0 голосов
/ 10 августа 2009

Это образец лабиринта

####################
#S #  #       #    #
#  # ##  ##  ### ###
#     #   #   #    #
## #  #   #     ## #
#     ### #####    #
# #   #   #  #   ###
# ### ### ## # #####
#  #      #       E#
####################
0 голосов
/ 10 августа 2009

Я попытался сделать простую реализацию алгоритма в Java. Я пришел к выводу, что алгоритм, который вы описали, работает даже для поиска нескольких путей. Или, возможно, вам удалось придумать более умный тестовый пример, чем я. (Пожалуйста, опубликуйте ваш лабиринт, чтобы я мог попробовать мой алгоритм на нем)

Моя реализация счетчика тупиков, вероятно, не самая эффективная, но она выполняет свою работу. Для каждого текущего ОТКРЫТОГО узла, который посещается, он проверяет 4 окружающих узла:

  • Если хотя бы один сосед ОТКРЫТ, текущий узел не является тупиком
  • Если более одного соседа - VISITED, текущий узел не является тупиком
  • Если ВИДИТСЯ только один узел (тот, с которого мы пришли на предыдущем шаге), а другой сосед не ОТКРЫТ, текущий узел является тупиком

Это код Java, который я написал (будьте осторожны! Довольно долго). Альтернативой может быть, если вы хотите, сохранить путь в стеке, нажимая на узел каждый раз, когда он установлен на VISITED, и возвращая узел на место каждый раз, когда он снова устанавливается на OPEN. Каждый раз, когда достигается ЦЕЛЬ, стек, содержащий текущий путь, должен копироваться и сохраняться.

Если блок if, помеченный комментарием как "тупик-расследование-шаг", будет удален, эта реализация почти точно будет соответствовать описанной в вопросе.

</p> <pre><code>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(); } }

Приведенный выше алгоритм сообщает о четырех различных путях решения и двух тупиках в примерном лабиринте, который жестко закодирован в код.


Может быть, причина того, почему вы получаете слишком мало путей решения, может быть неверной интерпретацией того, что такое путь решения? Например, рассмотрим этот лабиринт:

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

Этот лабиринт имеет только один допустимый путь решения. Это связано с тем, что вам разрешено посещать каждый узел только один раз, поэтому обход по круговому пути не является допустимым путем решения, поскольку это приведет к посещению узла к востоку от S и к северу от E дважды. Это определение пути решения подразумевается алгоритмом, который вы используете.

Если бы разрешить посещать один и тот же узел несколько раз, было бы бесконечно много решений, так как вы могли бы обходить круг 1, 2, 3 ... бесконечное количество раз.

Точно так же, как вы говорите, я увеличиваю pathLength каждый раз, когда я устанавливаю узел на VISITED, и уменьшаю длину пути каждый раз, когда я устанавливаю узел VISITED обратно на OPEN.

Для записи кратчайшей длины пути у меня также есть int-значение shorttestPath, которое я инициирую в Integer.MAX_VALUE. Затем, каждый раз, когда я достиг цели, я делаю это:

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

Что касается тупиков ... Я попытался подсчитать их вручную и подумал, что 9 кажется правильным. Вот лабиринт, размещенный Сарин , но с тупиками, отмеченными (от руки) D:

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

Можете ли вы найти больше? Или я неправильно истолковал то, что вы имели в виду под тупиком? Я думал, тупик означает: узел, к которому вы можете прийти только с одного направления.

Пример 1:

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

У лабиринта выше есть один тупик.

Пример 2:

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

Указанный лабиринт не имеет тупиков. Даже если вы находитесь в одном из доступных к северу узлов, все равно есть два соседних квадрата, не относящихся к СТЕНЕ.

У вас есть другое определение тупика? </ EDIT2>

0 голосов
/ 10 августа 2009

Для количества тупиков нужно что-то вроде этого:

3.5 если (к северу от x, y не открыт) и (к югу от x, y не открыт) и (к западу от x, y не открыт) и (к востоку от x, y не открыт) deadends ++

...