如何找到在一个图中距离给定节点集合等距离的所有节点?

8
如果你有一个简单无向图G(V,E)和一个FV的子集。如何找到某个节点v,使得从F中的每个节点到v的距离相同且距离最小?如果没有v,则只需返回None。据说可以在O(|V|+|E|)的复杂度下完成。
假设所有边的距离都为1。
有人能解释一下这是如何完成的吗?提供伪代码也会有所帮助。

2
我认为重要的是要知道距离是非负的还是正的? - Alexander
@Alexander +1。我的先前解决方案是基于所有边缘距离为1的假设。 - ElKamina
所有边的距离都为1。 - omega
我不明白。能否请您举个例子? - Colonel Panic
你需要一个节点v,使得从F到v的最短距离相等,或者至少有一条路径从每个F到v都相等吗? - SidR
F是V节点的子集。我们需要一个节点v,使得从v到F中每个节点的最短距离相同。 - omega
2个回答

3
解决方案类似于BFS,但有些变化:
  1. 从标记了节点的F集合开始,S=F。

  2. 找到与S中每个元素距离为1的|S|个集合(所有这些集合都应包含未标记的节点)。如果这些集合的交集不为空,则找到解决方案。

  3. 将上述|S|个集合在S'中联合并标记这些节点。如果S'为空,则返回“无”。

  4. 返回步骤2。

假设所有集合操作都需要常数时间,则该算法的复杂度与BFS相同,即O(|V| + |E|)。
现在来推理集合操作的复杂度。我的推理是,集合操作不会增加复杂度,因为步骤2和3中的联合和交集操作可以组合起来花费O(|S|)时间,并且由于每步S都不同于前面迭代中的S,因此集合操作的总体复杂度将是O(|V|)。

我不太确定我理解你的算法,你能展示一下伪代码吗?那么重新说一下你说的:
  1. 设 S = F,然后标记 S 的每个节点
  2. 获取从每个标记节点到距离为 1 的 |S| 个节点集合。
  3. 如果所有 |S| 个集合有交集,则该交集为候选集。
  4. 否则,我们将 |S| 个集合并起来,并重复步骤 2。 这样对吗?
- omega
此外,我不理解您的推理,如果有|S|个操作,那么它不是BFS复杂度的|S|倍吗? - omega
是的,你的总结是正确的。至于|S|操作,可以在计算这些|S|集合时通过保持两个累加器来完成|S|集合的交集,一个表示并集,另一个表示交集。 - Tushar
你确定这个能行吗?如果我理解正确的话,只有当一个节点距离原始集合中的每个节点都是一跳之遥时,你才能找到一个距离每个节点两跳之遥的节点。如果一个节点距离原始集合中的每个节点都只有一个节点是一跳之遥的话,那么它就可以距离原始集合中的每个节点两跳之遥。 - templatetypedef
如果一个节点距离原始集合中的每个节点都是两个跳之遥,那么它只需要距离其中一个节点一跳,而这个节点又分别与原始节点相隔一跳。如果找到了这个一跳节点,则算法会在第二步将其作为候选节点返回,因此上述情况永远不会出现。我同意,如果要找到“所有”等距节点,则上述解决方案将无法奏效。 - Tushar

3

这里有一个伪代码算法,试图添加注释来解释它的工作原理。

declare explored // a table of all the vertices that can keep track of one number (distance, initialized to -1) and a list of vertex references (origins, initialized to null)
to_explore = S  // fifo queue of vertices to explore

while (to_explore not empty) {
    pop vertex v from to_explore
    current_distance = explored[v].distance
    current_origins = explored[v].origins
    for (vertex n, neighbor of v) {
        if (explored[n].origins contains v)
            continue // we just hit a loop and we're only interested in shortest distances
        if (explored[n].distance == -1) { // first time we come here
            explored[n].distance = current_distance+1
            explored[n].origins = current_origins
            push n to to_explore
            continue
        }
        if (explored[n].distance != current_distance+1) {
            continue // we are merging path from another node of S but different distance, cannot lead to any solution
        }
        // only case left is explored[n].distance == current_distance+1
        // so we've already come here from other place(s) in S with the same distance
        add / merge (without duplicates) origins to explored[n].origins
        if (explored[n].origins = S) // maybe compare the size is enough?
            return n // we found a solution
        // if not , we just continue our exploration, no need to add to the queue since we've already been through here before
    }
}

使用FIFO队列的想法是,我们将探索离集合S距离为1的所有内容,如果在那里找不到任何解决方案,则探索距离为2的所有内容...等等,因此我们将首先找到最短的距离。
我不完全确定复杂度,但我认为在最坏的情况下,我们只会探索每个顶点和每个边仅一次,因此复杂度为O(| E | + | V |)。 但在某些情况下,我们会多次访问相同的顶点。虽然这并不会增加要探索的路径太多,但我不确定是否应该在某个地方有一个因子| S |(但如果将其视为常数,则可以...)
希望我没有漏掉任何内容。显然,我没有测试过任何内容....:)
编辑(回复评论)
您的代码能否适用于此类图形?E =(a,b),(a,c),(a,d),(b,e),(c,e),(d,e),而我的F = {b,c,d}。假设您从a开始进行bfs。我怀疑,最终origins数组将只有{a},因此代码将返回None。- Guru Devanla
在这种情况下,以下是发生的情况:
to_explore is initialized to {b,c,d}
//while (to_explore not empty)
pop one from to_explore (b). to_explore becomes {c,d}
current_distance=0
current_origins={b}
//for (neighbors of b) {
handling 'a' as neighbor of b first
explored[a].distance=1
explored[a].origins={b}
to_explore becomes {c,d,a}
//for (neighbors of b)
handling 'e' as next neighbor of b
explored[e].distance=1
explored[e].origins={b}
to_explore becomes {c,d,a,e}
//while (to_explore not empty)
pop one from to_explore (c). to_explore becomes {d,a,e}
current_distance=0
current_origins={c}
//for (neighbors of c)
handling 'a' as neighbor of c first
explored[a].distance is already 1
explored[a].origins={b,c}
to_explore already contains a
//for (neighbors of c) {
handling 'e' as next neighbor of b
explored[e].distance is already 1
explored[e].origins={b,}
to_explore already contains e
//while (to_explore not empty)
pop one from to_explore (d)
current_distance=0
current_origins={d}
//for (neighbors of d)
handling 'a' as neighbor of d first
explored[a].distance is already 1
explored[a].origins={b,c,d}
that matches F, found a as a solution.

你的代码能处理这样的图吗?E = (a, b), (a, c), (a, d), (b, e), (c, e), (d, e),以及我的 F = {b, c, d}。假设你从 a 开始进行 bfs。我怀疑最终 origins 数组只会有 {a},因此代码将返回 None。 - grdvnl
@GuruDevanla,我编辑了答案以解释在这种情况下会发生什么。我相信在这种情况下那应该可以正常工作... - Matthieu
很高兴看到它对我尝试证明算法错误的所有情况都起作用了! - Aseem Goyal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接