【问题标题】:Seemingly correct BFS implementation finding paths, but not *shortest* paths (weightless edges)看似正确的 BFS 实现寻找路径,但不是*最短*路径(失重边缘)
【发布时间】:2014-04-30 20:31:08
【问题描述】:

下面是一种算法尝试,可以在具有无权边的图中找到最短路径,并添加一个约束:一组不能在路径中的节点。因此,它不是找到节点之间的绝对最短路径,而是找到不包括某些节点的最短路径。

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

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

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

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

那么,您能看出以下算法在 avoids 非空时比空时找到更短路径的任何原因吗?

注意:“this”是起点,目的地(“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;
}

【问题讨论】:

  • BFS 不正确:您将顶点标记在错误的位置。推送到队列时要标记,否则同一个顶点会被处理多次。

标签: java algorithm graph breadth-first-search


【解决方案1】:

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

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

当您提供avoids 节点时,您会跳过更新previous 中的边的过程,因此您可能最终会得到更短的路径 - 与是否指定 avoids 无关,而是avoids 是否包含可能“破坏”边缘列表的较长路径上的节点。

【讨论】:

  • 好收获。所以我有可能覆盖最初的“以前的”值。因此,如果先前的 n 中还没有条目,我只需要执行 previous.put(n, curr) 。我可以调用 containsKey,但这会损害运行时......也许我必须重写 HashSet 以仅在 k 当前没有值的情况下放置(k,v),否则不理会它。
  • 其实既然是HashMap, containsKey 应该是无害的。这解决了我的问题。仅当 !previous.containsKey(n) 时才执行 previous.put(n, curr)。
【解决方案2】:

您的 BFS 是正确的。问题是如何编写找到的路径。 BFS 中的最短路径是指“从源到目的地的层数”。但是您正在计算从源到目的地的途中被查看的唯一节点的数量。

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

    B
  /  
A   |
  \ 
    C

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

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

      猜你喜欢
      • 2018-09-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多