Есть ли способ сохранить приоритеты направления в *? (т.е. создание того же самого пути, что и ширина)

У меня есть приложение, которое выиграет от использования A *; однако по причинам, связанным с наследством, мне нужно, чтобы он продолжал генерировать точно те же пути, что и раньше, когда есть несколько наилучших путей для выбора.

Например, рассмотрите этот лабиринт

...X
FX.S
....

S = start
F = finish
X = wall
. = empty space

с приоритетами направления Вверх; Правильно; Вниз; Left. Используя ширину, мы найдем путь DLLLU; однако, используя A *, мы немедленно уходим влево и заканчиваем поиск пути LULLD.

Я пробовал следить за тем, чтобы всегда расширяться в правильном направлении при разрыве связей; и переписывая указатели PreviousNode при переходе с более важного направления, но не работает в этом примере. Есть ли способ сделать это?

Ответ 1

Я придумал два способа сделать это. Оба требуют продолжения алгоритма, в то время как верхняя часть очереди имеет расстояние до начала g-value <= g (end- node). Поскольку эвристика, используемая в *, допустима, это гарантирует, что каждый node, принадлежащий к некоторому лучшему пути, в конечном итоге будет расширен.


Первый метод заключается в том, что когда мы приходим к конфликту (т.е. находим два узла с тем же f-значением, которые потенциально могут быть родителями некоторого node вдоль наилучшего пути), мы разрешаем его возвращаясь к первой точке вдоль пути, с которым они встречаются (мы можем сделать это легко в O(path-length)). Затем мы просто проверяем приоритеты направления обоих путей и переходим к тому, какой путь имеет более высокий приоритет в BFS-поиске.


Второй метод работает только для сеток, где каждый node касается горизонтально и вертикально (и, возможно, диагонально) смежных узлов (т.е. 4-связанных сетчатых графа). Мы делаем то же самое, что и раньше, но вместо того, чтобы отступать, чтобы разрешить конфликт, мы сравниваем узлы по путям с самого начала и находим первое место, которое они отличаются. Первое место, которое они отличаются, будет таким же критическим node, как и раньше, из которого мы можем проверить приоритеты направления.

Мы делаем это, сохраняя наилучший путь до сих пор для каждого node. Обычно это было бы громоздким, но поскольку у нас есть 4-связный граф, мы можем сделать это довольно эффективно, сохраняя каждое направление по пути. Это займет всего 2 бита за node. Таким образом, мы можем по существу кодировать путь с помощью целых чисел - с 32-битными регистрами мы можем сравнивать по 16 узлов за раз; 32 узла с 64-битными регистрами; и 64 (!) узлов с 128-разрядными регистрами (например, регистры SSE в процессорах x86 и x64), что делает этот поиск очень недорогим даже для путей со 100 узлами.


Я реализовал оба эти метода вместе с алгоритмом @generic для проверки скорости. На сетке 50x50 с 400 башнями,

  • @generic человеческий алгоритм работал примерно на 120% медленнее, чем обычно A *
  • мой алгоритм обратного отслеживания работал примерно на 55% медленнее, чем обычно A *
  • Алгоритм целочисленного кодирования работает только на 10% медленнее, чем A *

Таким образом, поскольку мое приложение использует 4-связанные графики, кажется, что алгоритм целочисленного кодирования лучше для меня.


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

Ответ 2

Если исходный алгоритм был BFS, вы ищете наименьший из кратчайших путей, где "наименьший" соответствует лексикографическому порядку, вызванному некоторым общим порядком Ord по краям (и, конечно, "кратчайший" соответствует к длине пути).

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

К счастью, это все еще можно сделать с помощью двух простых и недорогих модификаций для A *:

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

Схематически классический A *:

path_length = infinity for every node
path_length[start] = 0

while score(goal) > minimal score of unvisited nodes:
    x := any unvisited node with minimal score
    mark x as visited
    for y in unvisited neighbors of x:
        path_length_through_x = path_length[x] + d(x,y)
        if path_length[y] > path_length_through_x:
            path_length[y] = path_length_through_x
            ancestor[y] = x

return [..., ancestor[ancestor[goal]], ancestor[goal], goal]

где score(x) означает path_length[x] + heuristic(x, goal).

Мы просто превратим строгое неравенство цикла while в нестрогий и добавим фазу восстановления пути:

path_length = infinity for every node
path_length[start] = 0

while score(goal) >= minimal score of unvisited nodes:
    x := any unvisited node with minimal score
    mark x as visited
    for y in unvisited neighbors of x:
        path_length_through_x = path_length[x] + d(x,y)
        if path_length[y] > path_length_through_x:
            path_length[y] = path_length_through_x

optimal_nodes = [goal]
for every x in optimal_nodes:  // note: we dynamically add nodes in the loop
    for y in neighbors of x not in optimal_nodes:
        if path_length[x] == path_length[y] + d(x,y):
            add y to optimal_nodes

path = [start]
x = start
while x != goal:
    z = undefined
    for y in neighbors of x that are in optimal_nodes:
        if path_length[y] == path_length[x] + d(x,y):
            z = y if (x,y) is smaller than (x,z) according to Ord
    x = z
    append x to path

return path

Предупреждение: процитировать Кнута, я только доказал это правильно, не пробовал.

Что касается влияния производительности, оно должно быть минимальным: цикл поиска посещает узлы со счетом, который на 1 единицу выше классического A *, а фаза восстановления является квазилинейной по числу узлов, принадлежащих кратчайший путь. Влияние меньше, если, как вы подразумеваете, в большинстве случаев есть только один самый короткий путь. Вы можете даже оптимизировать этот специальный случай, например. вспомнив ancestor node, как в классическом случае, который вы установили для специального значения ошибки, когда имеется более одного предка (то есть, когда path_length[y] == path_length_through_x). Когда цикл поиска закончен, вы пытаетесь получить путь через ancestor, как в классическом A *; вам нужно всего лишь выполнить полную реконструкцию пути, если при построении пути возникло значение ошибки.

Ответ 3

i построил бы предпочтение по порядку пути непосредственно в эвристической функции

Сначала я бы посмотрел первый алгоритм хлеба

определить функцию для каждого пути, который выбирает алгоритм хлеба:

рассмотрим, что мы запускаем алгоритм глубины и на n-й глубине ранее сделанные решения по алгоритму: x_i\in {U, R, D, L} обозначим U = 0, R = 1, D = 2, L = 3

затем определите:

g(x_1,..,x_n) = sum_{i=1}^n x_i * (1/4)^i

исправьте этот шаг g как g ' на каждом шаге, когда алгоритм видит более глубокий node, чем этот, функция g() будет больше.

на каждом следующем шаге, когда изменяется значение {1..n} x_i, будет больше, поэтому верно, что g-функция всегда поднимается во время выполнения первой-первой.

обратите внимание: если алгоритм глубины-первого успешный, он выбирает путь с минимальным значением g()

Примечание: g() < 1 beacuse max (L, R, U, D) = 3

добавление g к эвристической функции A * не будет мешать кратчайшей длине пути, так как длина минимального края >= 1

первое решение, модифицированное таким образом, как это было найдено, было бы тем, что первая глубина найдет

для примера:

h_bread=g(DLLLU) = (23330)_4 * c
h_astar=g(LULLD) = (30332)_4 * c

() _ 4 - base4 с - постоянная (4 ^ {- 5})

для примера: h_bread < h_astar

Ответ 4

В общем, нет нетривиального способа сделать это:

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

Пример: если узлы рассматриваются в порядке A, B, C, то Node A < Node C. Таким образом, если существует связь между кратчайшим путем, начинающимся с A и одним, начинающимся с C, то найдена с A.

С другой стороны, поиск A * найдет кратчайший путь младшего порядка, определяемый эвристическим значением node. Таким образом, эвристика должна учитывать самый низкий лексикографический путь к каждому node. И единственный способ найти это BFS.