Реализация A* в проверке PHP

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

<?php

class AStarSolver
{
    function solve(&$s)
    {
        include_once('PQueue.class.php');
        $o = new PQueue();
        $l = array();
        $c = array();
        $p = array();
        $a = $s->getStartIndex();
        $z = $s->getGoalIndex();
        $d = $s->goalDistance($a);

        $n0 = array('g'=>0, 'h'=>$d, 'i'=>$a, 'p'=>NULL, 'f'=>$d);
        $o->push($n0, -$d);
        $l[$a] = TRUE;

        while (! $o->isEmpty())
        {
            $n = $o->pop();

            if ($n['i'] == $z)
            {
                while ($n)
                {
                    $p[] = $n['i'];
                    $n = $n['p'];
                }
                break;
            }

            foreach ($s->getNeighbors($n['i']) as $j => $w)
            {
                if ((isset($l[$j]) || isset($c[$j])) && isset($m) && $m['g'] <= $n['g']+$w)
                    continue;

                $d = $s->goalDistance($j);
                $m = array('g'=>$n['g']+$w, 'h'=>$d, 'i'=>$j, 'p'=>$n, 'f'=>$n['g']+$w+$d);

                if (isset($c[$j]))
                    unset($c[$j]);

                if (! isset($l[$j]))
                {
                    $o->push($m, -$m['f']);
                    $l[$j] = TRUE;
                }
            }
            $c[$n['i']] = $n;
        }
        return $p;
    }

}

?>

Код Pqueue можно найти здесь


person aherlambang    schedule 26.02.2011    source источник
comment
Понятия не имею, почему за вас проголосовали; это не плохая тема сама по себе. Однако проверка кода и алгоритма не совсем соответствует теме Stackoverflow. Таким образом, вы можете в конечном итоге удалить и переместить этот вопрос на codereview.stackexchange.com.   -  person mario    schedule 26.02.2011
comment
@mario Не совсем, Code Review требует, чтобы у вас был рабочий код, поэтому обнаружение ошибок не полностью входит в его компетенцию.   -  person Yi Jiang    schedule 26.02.2011
comment
Вы пытались связаться с codezilla, чтобы спросить его о его реализации? За то, что вы предложили это вам, когда вы впервые спросили об A * в PHP, проголосовали против: я все еще думаю, что это выглядит как правильная реализация. Но я подожду, чтобы увидеть, что другие люди говорят об этом.   -  person Mark Baker    schedule 26.02.2011
comment
Да ... я не понимаю, почему за него проголосовали ... Я думаю, что это правильный вопрос ... если его нужно перенести на проверку кода, пусть будет так. Код работает... просто не работает в некоторых случаях   -  person aherlambang    schedule 26.02.2011
comment
@Марк Бейкер да, и пока нет ответа   -  person aherlambang    schedule 26.02.2011


Ответы (1)


Сайт предполагает, что ошибка может быть в классе PQueue.

В PQueue::pop этом

$j+1 < $m

является тестом, имеет ли узел кучи в $i одного дочернего элемента (в $j) или двух (в $j и $j+1).

Но $m здесь равно count($h) только на первой итерации цикла, так как --$m в условии цикла оценивается каждый раз.

Переместите этот --$m рядом с array_pop, где он принадлежит, и это будет одной ошибкой меньше.


Теперь AStarSolver.

Переменные (относительно псевдокода Википедии):

  • $o – открытый набор в качестве приоритетной очереди;
  • $l – открытый набор как карта с ключом по индексу;
  • $c – закрытый набор как карта с ключом по индексу;
  • $n – текущий узел (x);
  • $m – соседний узел (y) ?;
  • $j – индекс соседнего узла.

Проблемы, которые я вижу:

  • За $n = $o->pop() не следует unset($l[$n['i']]). Поскольку и $o, и $l представляют один и тот же набор, их следует синхронизировать.

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

    if (isset($c[$j]) || isset($l[$j]) && isset($m) && $m['g'] <= $n['g']+$w)

    Затем мы можем удалить файл unset($c[$j]).

  • $m['g'] в этом условии должен быть g-показателем текущего соседа, проиндексированным $j. Но $m имеет любое значение, оставшееся от предыдущего цикла: узел, соответствующий $j на предыдущей итерации.

    Нам нужен способ найти узел и его g-оценку по индексу узла. Мы можем сохранить узел в массиве $l: вместо $l[$j] = TRUE мы делаем $l[$j] = $m и приведенное выше условие становится

    if (isset($c[$j]) || isset($l[$j]) && $l[$j]['g'] <= $n['g']+$w)

  • Теперь самое сложное. Если только что найденного узла нет в открытом наборе, мы добавляем его туда (это $o->push и $l[$j] =).

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

    if (! isset($l[$j])) {
       $o->push($m, -$m['f']);
       $l[$j] = $m; // add a new element
    } else {
       $l[$j] = $m; // replace existing element
       $o = new PQueue();
       foreach ($l as $m)
          $o->push($m, -$m['f']);
    }

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


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

  • В лабиринте crazy в третьем внутреннем контуре: выбранный верхний путь вокруг немного длиннее, чем нижний путь, из-за препятствий слева.

  • В лабиринте big в правой верхней части пути есть ненужная петля вверх.


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

person aaz    schedule 01.03.2011
comment
есть ли еще одна ошибка, которую вы можете увидеть? - person aherlambang; 02.03.2011
comment
@EquinoX — остальная часть PQueue выглядит нормально; А* еще не смотрел. - person aaz; 02.03.2011
comment
если вы можете пересмотреть код и вставить его выше, я вознагражу вас наградой.. это также может быть установлено как вики сообщества - person aherlambang; 02.03.2011