这是我从网站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
的代码答案 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的答案中。