我正在为A *编写一个Graph版本来解决8个拼图问题,我实现了一个测试它的树版本,并且工作正常。我只是通过跟踪我访问过的节点来扩展树版本,从而完成了图形版本。
以下是原始树版本:
int AStarTreeVersion (Node initialState){
priority_queue<Node, vector<Node>, greater<Node> > fringe;
fringe.push(initialState);
while (true){
if (fringe.empty()) // no solution
return -1;
Node current = fringe.top();
fringe.pop();
if (current.isGoal())
return current.getDistance();
auto successors = current.getSuccessors();
for (auto s : successors)
if (s != current)
fringe.push(s);
}
}
和图表版本:
int AStarGraphVersion(Node initialState){
priority_queue<Node, vector<Node>, greater<Node> > fringe;
fringe.push(initialState);
unordered_set<Node> visited; // <---
visited.insert(initialState);// <---
while (true){
if (fringe.empty()) // no solution
return -1;
Node current = fringe.top();
fringe.pop();
if (current.isGoal())
return current.getDistance();
auto successors = current.getSuccessors();
for (auto s : successors){
auto pair = visited.insert(s); //<--
if (pair.second) //<--
fringe.push(s); //<--
}
}
}
我添加了一个箭头来指示两个版本之间的差异。任何人都可以看到出了什么问题吗?
以下是测试用例,它用于解决8-puzzle:
array<int, 9> a= {1, 6, 4, 8, 7, 0, 3, 2, 5};
Node ini(a);
cout<<"Tree solution "<<AStarTreeVersion(ini)<<endl;
cout<<"Graph solution "<<AStarGraphVersion(ini)<<endl;
输出:
Tree solution 21
Graph solution 23
修改
以下是Node
类的相关详细信息:
class Node {
public:
bool operator>(const Node& that) const
{return this->getHeuristicValue() > that.getHeuristicValue() ;}
friend inline bool operator==(const Node & lhs, const Node & rhs)
{ return lhs.board == rhs.board;}
friend inline bool operator!=(const Node & lhs, const Node & rhs)
{ return ! operator==(lhs,rhs) ;}
size_t getHashValue ()const{
size_t seed = 0;
for (auto v : board)
seed ^= v + 0x9e3779b9 + (seed << 6) + (seed >> 2);
return seed;
}
private:
array<int, 9> board;
};
以下是我重载hash
模板的方法:
namespace std {
template <> struct hash<Node>
{
size_t operator()(const Node & t) const
{
return t.getHashValue();
}
};
}
答案 0 :(得分:2)
我认为你的问题在这里:
for (auto s : successors){
auto pair = visited.insert(s); //<--
if (pair.second) //<--
fringe.push(s); //<--
}
}
A *搜索通过维护这些节点的节点边缘和候选距离来工作。当首次插入节点时,不保证这些节点的候选距离是准确的。例如,考虑一个图,其中从起始节点到目标节点的直接连接成本为∞,另一个路径具有距离4但通过其他一些中间节点。当初始节点被扩展时,它将目标节点放入候选距离为∞的优先级队列中,因为从该节点到目标节点存在直接边缘。这是错误的距离,但通常没关系 - 稍后,A *将发现另一条路线,并将目标的候选距离从∞降低到4加上启发式。
但是,上面的代码没有正确实现。具体来说,它确保节点仅插入优先级队列一次。这意味着如果您的初始候选距离不正确,则不会更新队列中的优先级,以反映找到更好路径时更新的候选距离。在基于树的版本中,这不是问题,因为您稍后将使用正确的优先级将目标节点的第二个副本插入优先级队列。
有几种方法可以解决这个问题。最简单的方法如下 - 在实际将节点从优先级队列中出列并进行处理之前,不要将节点标记为已访问。这意味着只要您看到某个节点的路径,就应该将该节点添加到具有估计距离的队列中。这可能会导致您将同一节点的多个副本放入优先级队列,这很好 - 当您第一次出列时,您将获得正确的距离。更新后的代码如下所示:
priority_queue<Node, vector<Node>, greater<Node> > fringe;
fringe.push(initialState);
unordered_set<Node> visited;
while (true){
if (fringe.empty()) // no solution
return -1;
Node current = fringe.top();
fringe.pop();
/* Mark the node as visited and don't visit it again if you already saw it. */
if (visited.insert(current).second == false) continue;
if (current.isGoal())
return current.getDistance();
auto successors = current.getSuccessors();
/* Add successors to the priority queue. This might introduce duplicate nodes,
* but that's fine. All but the lowest-priority will be ignored.
*/
for (auto s : successors){
fringe.push(s);
}
}
上面的代码可能有错误,具体取决于后续实现的方式,但从概念上讲它是正确的。
希望这有帮助!