PHP验证中的*实现

时间:2011-02-26 05:56:56

标签: php algorithm shortest-path a-star

这是我从网站here获得的代码,我想知道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;
    }

}

?>

可以找到P here

的代码

1 个答案:

答案 0 :(得分:9)

网站建议该错误可能在PQueue类中。

PQueue::pop这个

$j+1 < $m

是测试$i处的堆节点是否有一个孩子($j)或两个($j$j+1)。

$m仅在循环的第一次迭代中为count($h),因为每次都会评估循环条件中的--$m

--$m移到它所属的array_pop旁边,这将减少一个错误。


现在是AStarSolver

变量是(相对于Wikipedia pseudocode):

  • $o - 打开设置为优先级队列;
  • $l - 打开设置为按索引键入的地图;
  • $c - 关闭设置为按索引键入的地图;
  • $n - 当前节点( x );
  • $m - 邻居节点( y )?;
  • $j - 邻居节点索引。

我看到的问题:

  • $n = $o->pop()后面没有unset($l[$n['i']])。由于$o$l代表相同的集合,因此应保持同步。

  • 根据维基百科,闭合集仅在启发式是单调时使用(并且我认为距离启发式是单调的),并且在这种情况下,一旦将节点添加到关闭集,它永远不会再次访问。这段代码似乎实现了一些other pseudocode,它确实从封闭集中删除了节点。我认为这会破坏闭集的目的,内循环中的第一个条件应该是

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

    然后我们可以移除unset($c[$j])

  • 在这种情况下,
  • $m['g']应该是由$j索引的当前邻居的 g - 分数。但$m具有上一循环遗留的任何值:前一次迭代中与$j对应的节点。

    我们需要的是一种通过节点索引查找节点及其 g -score的方法。我们可以将节点存储在$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迷宫中,有一个不必要的循环。


由于这是我的想法,我实施了自己的算法版本,并将其发布在previous question的答案中。