看似正确的BFS实现查找路径,但不是*最短*路径(无重边)

时间:2014-04-30 20:31:09

标签: java algorithm graph breadth-first-search

下面是一种算法的尝试,用于在具有无重力边缘的图中找到最短路径,其中一个附加约束:一组不能在路径中的节点。因此,它不是找到节点之间的绝对最短路径,而是找到不包含某些节点的最短路径。

Wordnode 是节点类, HashSet避免是必须避免的节点集。算法中唯一发挥作用的地方是检查是否将节点添加到队列中。如果中的内容避免(或者已经访问过它),请不要添加它。我相信这个检查的效果应该等同于暂时删除 avoidids 中的任何边进出节点,尽管使用HashSet我避免实际改变数据结构。

我认为算法正在运行,直到我通过向避免添加单词来设法获得更短的路径。例如,如果避免为空,那么对于 shortestPath(A,Z,{}),它可能会返回(A,B,E,C,F,L,D, Z),但在向中添加E和C以避免并调用 shortestPath(A,Z,{E,C})时,我得到(A,R,K,Z) ),这更短......

我使用的图表有数千个节点,但我检查过(A,B,E,C,F,L,D,Z)(A, R,K,Z)是有效路径。问题是当避免为空时,当有明显存在的长度路径仅为4时,算法返回长度为8的路径。

这告诉我,我的算法(下面)不正确,或者我的图形数据结构存在问题。检查后者会更加困难,所以我想我会看到是否有人首先发现问题。

那么,当避免非空时比下面的算法为空时,你能否看到下面的算法找到更短路径的原因?

注意:"这"是原点,而目的地(" dest")是一个参数。

由于

public LinkedList<String> shortestPath(Wordnode dest, int limit, HashSet<Wordnode> avoids)
{
    HashSet<Wordnode> visited = new HashSet<>();
    HashMap<Wordnode, Wordnode> previous = new HashMap<>();
    LinkedList<Wordnode> q = new LinkedList<Wordnode>();
    previous.put(this, null);
    q.add(this);
    Wordnode curr = null;
    boolean found = false;
    while(!q.isEmpty() && !found)
    {
        curr = q.removeLast();
        visited.add(curr);
        if(curr == dest)
            found = true;
        else
        {
            for(Wordnode n: curr.neighbors)
            {
                if(!visited.contains(n) && !avoids.contains(n))
                {
                    q.addFirst(n);
                    previous.put(n, curr);
                }
            }
        }
    }
    if(!found)
        return null;
    LinkedList<String> ret = new LinkedList<>();
    while(curr != null)
    {
        ret.addFirst(curr.word);
        curr = previous.get(curr);
    }
    return ret;
}

3 个答案:

答案 0 :(得分:1)

您的BFS是正确的。问题是如何编写找到的路径。 BFS中的最短路径表示&#34;远离源到目的地的级别的数量&#34;。但是,您正在计算从源到目的地的路上查找的唯一节点数。

考虑每个相互连接的3个节点的图表:

    B
  /  
A   |
  \ 
    C

路径A-C长1级。您的实现可以给出路径长度为2,因为节点可以作为A-B然后C访问。顺序将取决于您的输入数据。 所以你需要计算水平。

答案 1 :(得分:1)

我认为您的问题是如何使用previous地图构建边缘列表。在排队节点时存储最后看到的边缘,但此边缘可能不在最短路径上。

从队列中提取dest时检查previous,但dest节点dest中存储的边缘可能不再是到达avoids的边缘{1}}将其添加到队列中。

当您提供previous个节点时,您会跳过更新avoids边缘的过程,这样可能会以更短的路径结束 - 不是{{1}是否已指定,而是avoids是否包含可能“损坏”边列表的较长路径上的节点。

答案 2 :(得分:0)

我在这里添加了一个答案,因为前面没有一个表明正确的错误。

问题在于节点被标记为已访问:在原始节点从队列中弹出节点时完成,这意味着在给定节点到达队列顶部之前,它可能会被添加几次,因此改变路径建设。

你必须在排队时标记你的节点,所以在行队列之后.addFirst(n)只需添加visited.add(n)。