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

Java 看似正确的BFS实现查找路径,但不是“最短”路径(无重边),java,algorithm,graph,breadth-first-search,Java,Algorithm,Graph,Breadth First Search,下面是一个算法的尝试,在一个无重边图中寻找最短路径,并添加了一个约束:一组不能在路径中的节点。因此,它不是查找节点之间的绝对最短路径,而是查找不包含特定节点的最短路径 Wordnode是节点类,HashSet avoids是必须避免的节点集。算法中唯一起作用的地方是检查是否将节点添加到队列中。如果它在数据库中(或者已经被访问过),不要添加它。我相信这种检查的效果应该相当于临时删除节点中的任何边,尽管通过使用HashSet,我避免了实际改变数据结构 我以为这个算法是有效的,直到我设法通过添加单词来

下面是一个算法的尝试,在一个无重边图中寻找最短路径,并添加了一个约束:一组不能在路径中的节点。因此,它不是查找节点之间的绝对最短路径,而是查找不包含特定节点的最短路径

Wordnode是节点类,HashSet avoids是必须避免的节点集。算法中唯一起作用的地方是检查是否将节点添加到队列中。如果它在数据库中(或者已经被访问过),不要添加它。我相信这种检查的效果应该相当于临时删除节点中的任何边,尽管通过使用HashSet,我避免了实际改变数据结构

我以为这个算法是有效的,直到我设法通过添加单词来缩短路径。e、 例如,如果avoids是空的,那么对于最短路径(A,Z,{}),它可能返回(A,B,e,C,F,L,D,Z),但是在将e和C添加到avoids并调用最短路径(A,Z,{e,C})时,我得到(A,R,K,Z),它较短

我使用的图形有数千个节点,但我已经检查了(A、B、E、C、F、L、D、Z)和(A、R、K、Z)是否都是有效路径。问题是,当avoids为空时,算法返回长度为8的路径,而显然存在长度仅为4的路径

这向我表明,要么我的算法(如下)不正确,要么我的图形数据结构存在问题。检查后者会更困难,所以我想我会先看看是否有人发现下面的问题

那么,你能看到下面的算法在避免为非空时比为空时找到更短路径的任何原因吗

注意:“this”是原点,dest(“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;
}
public LinkedList最短路径(Wordnode dest、int limit、HashSet)
{
访问的HashSet=新HashSet();
HashMap previous=新的HashMap();
LinkedList q=新建LinkedList();
previous.put(本,空);
q、 加上(这个);
Wordnode curr=null;
布尔值=false;
而(!q.isEmpty()&&!found)
{
curr=q.removeLast();
已访问。添加(curr);
如果(当前==目的地)
发现=真;
其他的
{
for(Wordnode n:当前邻居)
{
如果(!visted.contains(n)&&!avoids.contains(n))
{
q、 addFirst(n);
上一次卖出(n,当前);
}
}
}
}
如果(!找到)
返回null;
LinkedList ret=新建LinkedList();
while(curr!=null)
{
ret.addFirst(当前单词);
curr=previous.get(curr);
}
返回ret;
}

您的BFS是正确的。问题在于如何编写找到的路径。BFS中的最短路径表示“从源到目标的级别数”。但是,您正在计算从源到目标途中查看的唯一节点的数量

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

    B
  /  
A   |
  \ 
    C
路径A-C的长度为1级。您的实现可以将路径长度设置为2,因为节点可以先访问A-B,然后访问C。顺序将取决于您的输入数据。
因此,您需要计算级别。

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

从队列中拉出时,您会检查
dest
,但是
dest
节点的
previous
中存储的边可能不再是添加到队列中时到达
dest
的边


当您提供
避免
节点时,您将跳过更新
上一个
中的边的过程,因此您可能会得到较短的路径-这与是否指定了
避免
无关,而是
是否避免
在较长路径上包含可能“损坏”边缘列表的节点。

我在这里添加一个答案,因为前面的答案都不表示正确的错误

问题在于节点被标记为已访问的位置:在最初的版本中,当节点从队列中弹出时,就会执行该操作,这意味着在给定节点到达队列顶部之前,可能会多次添加该节点,从而改变路径结构


您必须在排队时标记节点,因此,在排队后。addFirst(n)只需添加已访问的。add(n)。

很好。所以我有可能会覆盖初始的“先前”值。所以我只需要做previous.put(n,curr),如果previous中还没有n的条目。我可以打电话给containsKey,但那会影响运行时间。。。如果k当前没有值,可能我必须重写HashSet,使其仅为put(k,v),否则就不使用它。实际上,由于它是一个HashMap,containsKey应该是无害的。这就解决了我的问题。只做上一步。如果有,就放(n,curr)!BFS不正确:你在错误的地方标记了你的顶点。当您将它们推送到队列时,应该标记它们,否则同一顶点可以多次处理。