在有向无环图中找到最低共同祖先的算法?
Posted
技术标签:
【中文标题】在有向无环图中找到最低共同祖先的算法?【英文标题】:Algorithm to find lowest common ancestor in directed acyclic graph? 【发布时间】:2013-01-29 16:18:41 【问题描述】:想象一个如下的有向无环图,其中:
“A”是根(总有一个根) 每个节点都知道它的父节点 节点名称是任意的 - 无法从中推断出任何内容 我们从另一个来源得知,节点按 A 到 G 的顺序添加到树中(例如,它们是版本控制系统中的提交)我可以使用什么算法来确定两个任意节点的最低共同祖先 (LCA),例如,以下的共同祖先:
B 和 E 是 B D 和 F 是 B注意:
从根到给定节点不一定只有一条路径(例如“G”有两条路径),所以你不能简单地traverse paths from root to the two nodes and look for the last equal element 我发现了树的 LCA 算法,尤其是二叉树,但它们不适用于这里,因为一个节点可以有多个父节点(即这不是一棵树)【问题讨论】:
你的意思是'无环'。 “父母”是指所有具有指向相关节点的有向边的节点吗? 所有节点都有指向其父节点的边,如果有的话(例如 A 没有父节点)。 AFAIK 由于循环 G-F-E-B-C-D-G,该图是循环的。 如果你把这个问题发在这里:cs.stackexchange.com,你肯定会得到更多更好的答案。 问题就变成了理解答案... ;-) @AndrewSwan:如果该图是无向的,它将是循环的。在当前状态下,它是非循环的。 【参考方案1】:package FB;
import java.util.*;
public class commomAnsectorForGraph
public static void main(String[] args)
commomAnsectorForGraph com = new commomAnsectorForGraph();
graphNode g = new graphNode('g');
graphNode d = new graphNode('d');
graphNode f = new graphNode('f');
graphNode c = new graphNode('c');
graphNode e = new graphNode('e');
graphNode a = new graphNode('a');
graphNode b = new graphNode('b');
List<graphNode> gc = new ArrayList<>();
gc.add(d);
gc.add(f);
g.children = gc;
List<graphNode> dc = new ArrayList<>();
dc.add(c);
d.children = dc;
List<graphNode> cc = new ArrayList<>();
cc.add(b);
c.children = cc;
List<graphNode> bc = new ArrayList<>();
bc.add(a);
b.children = bc;
List<graphNode> fc = new ArrayList<>();
fc.add(e);
f.children = fc;
List<graphNode> ec = new ArrayList<>();
ec.add(b);
e.children = ec;
List<graphNode> ac = new ArrayList<>();
a.children = ac;
graphNode gn = com.findAncestor(g, c, d);
System.out.println(gn.value);
public graphNode findAncestor(graphNode root, graphNode a, graphNode b)
if(root == null) return null;
if(root.value == a.value || root.value == b.value) return root;
List<graphNode> list = root.children;
int count = 0;
List<graphNode> temp = new ArrayList<>();
for(graphNode node : list)
graphNode res = findAncestor(node, a, b);
temp.add(res);
if(res != null)
count++;
if(count == 2) return root;
for(graphNode t : temp)
if(t != null) return t;
return null;
class graphNode
char value;
graphNode parent;
List<graphNode> children;
public graphNode(char value)
this.value = value;
【讨论】:
【参考方案2】:This link (Archived version) 描述了在 Mercurial 中是如何完成的 - 基本思想是找到指定节点的所有父节点,按照距根的距离对它们进行分组,然后对这些组进行搜索。
【讨论】:
【参考方案3】:Den Roman's link (Archived version) 看起来很有希望,但对我来说似乎有点复杂,所以我尝试了另一种方法。这是我使用的一个简单算法:
假设你想用 x 和 y 两个节点计算 LCA(x,y)。
每个节点必须有一个值color
和count
,分别。初始化为 white 和 0。
-
将x的所有祖先都涂成蓝色(可以使用BFS完成)
将 y 的所有 blue 祖先着色为 red(再次 BFS)
对于图中的每个 red 节点,将其父节点的
count
加一
每个将count
值设置为0 的red 节点都是一个解决方案。
可能有不止一种解决方案,具体取决于您的图表。例如,考虑这个图表:
LCA(4,5) 可能的解是 1 和 2。
请注意,如果您想找到 3 个或更多节点的 LCA,它仍然有效,您只需为每个节点添加不同的颜色。
【讨论】:
你描述的算法似乎有一些无端的复杂性,掩盖了真正发生的事情。当您仅将计数用作标志时,为什么要计数?当您似乎只需要“先前考虑的所有节点的祖先”的一种颜色和“当前正在考虑的节点的祖先”的第二种颜色时,为什么要使用 N 种颜色?【参考方案4】:我知道这是个老问题,而且讨论得很好,但是由于我有一些类似的问题要解决,所以我遇到了 JGraphT 的 Lowest Common Ancestor 算法,认为这可能会有所帮助:
NativeLcaFinder TarjanLowestCommonAncestor【讨论】:
JGraphT NaivaLcaFinder 是要走的路。 Tarjan 仅适用于树木。【参考方案5】:假设您想在图中找到 x 和 y 的祖先。
维护一个向量数组 - parents(存储每个节点的父节点)。
首先做一个bfs(继续存储每个顶点的父母)并找到x的所有祖先(找到x的父母并使用parents,找到x的所有祖先)并存储他们在一个向量中。此外,将每个父节点的深度存储在向量中。
使用相同的方法找到 y 的祖先并将它们存储在另一个向量中。现在,您有两个向量分别存储 x 和 y 的祖先及其深度。
LCA 将是具有最大深度的共同祖先。深度定义为距根的最长距离(in_degree=0 的顶点)。现在,我们可以按照向量深度的降序对向量进行排序并找出 LCA。使用这种方法,我们甚至可以找到多个 LCA(如果有的话)。
【讨论】:
【参考方案6】:每个人。 请在 Java 中尝试。
static String recentCommonAncestor(String[] commitHashes, String[][] ancestors, String strID, String strID1)
HashSet<String> setOfAncestorsLower = new HashSet<String>();
HashSet<String> setOfAncestorsUpper = new HashSet<String>();
String[] arrPair= strID, strID1;
Arrays.sort(arrPair);
Comparator<String> comp = new Comparator<String>()
@Override
public int compare(String s1, String s2)
return s2.compareTo(s1);
;
int indexUpper = Arrays.binarySearch(commitHashes, arrPair[0], comp);
int indexLower = Arrays.binarySearch(commitHashes, arrPair[1], comp);
setOfAncestorsLower.addAll(Arrays.asList(ancestors[indexLower]));
setOfAncestorsUpper.addAll(Arrays.asList(ancestors[indexUpper]));
HashSet<String>[] sets = new HashSet[] setOfAncestorsLower, setOfAncestorsUpper;
for (int i = indexLower + 1; i < commitHashes.length; i++)
for (int j = 0; j < 2; j++)
if (sets[j].contains(commitHashes[i]))
if (i > indexUpper)
if(sets[1 - j].contains(commitHashes[i]))
return commitHashes[i];
sets[j].addAll(Arrays.asList(ancestors[i]));
return null;
这个想法很简单。我们假设 commitHashes 按降级顺序排序。 我们找到字符串的最低和最高索引(哈希 - 不意味着)。 很明显(考虑后代顺序)共同祖先只能在上索引之后(哈希中的较低值)。 然后我们开始枚举提交的哈希并构建后代父链的链。为此,我们有两个哈希集由提交的最低和最高哈希的父母初始化。 setOfAncestorsLower,setOfAncestorsUpper。如果下一个 hash -commit 属于任何链(hashsets), 然后如果当前索引高于最低哈希的索引,那么如果它包含在另一个集合(链)中,我们返回当前哈希作为结果。如果不是,我们将其父级 (ancestors[i]) 添加到 hashset,它跟踪 set 的祖先集,其中包含当前元素。基本上就是这样
【讨论】:
【参考方案7】:我一直在寻找相同问题的解决方案,并在以下论文中找到了解决方案:
http://dx.doi.org/10.1016/j.ipl.2010.02.014
简而言之,您不是在寻找最低的共同祖先,而是寻找他们在本文中定义的最低的 SINGLE 共同祖先。
【讨论】:
【参考方案8】:我提出 O(|V| + |E|) 时间复杂度解决方案,我认为这种方法是正确的,否则请纠正我。
给定有向无环图,我们需要找到两个顶点 v 和 w 的 LCA。
Step1:使用 bfs http://en.wikipedia.org/wiki/Breadth-first_search 找到所有顶点到根顶点的最短距离,时间复杂度为 O(|V| + |E|),并找到每个顶点的父节点。
Step2:通过使用 parent 找到两个顶点的共同祖先,直到我们到达根顶点时间复杂度 - 2|v|
Step3: LCA 将是具有最大最短距离的共同祖先。
所以,这是 O(|V| + |E|) 时间复杂度算法。
如果我错了,请纠正我或欢迎任何其他建议。
【讨论】:
如何使用 parent 找到两个顶点的共同祖先?你能详细说明一下吗?【参考方案9】:只是一些疯狂的想法。将两个输入节点都用作根,并逐步同时执行两个 BFS 怎么样。在某个步骤,当它们的 BLACK 集合(记录访问过的节点)中有重叠时,算法停止并且重叠的节点是它们的 LCA(s)。这样一来,任何其他共同祖先的距离都会比我们发现的要长。
【讨论】:
【参考方案10】:我也需要完全相同的东西,在 DAG(有向无环图)中找到 LCA。 LCA 问题与 RMQ(Range Minimum Query Problem)有关。
可以将 LCA 简化为 RMQ,并从有向无环图中找到两个任意节点的所需 LCA。
我发现THIS TUTORIAL 很详细,很好。我也打算实现这个。
【讨论】:
【参考方案11】:如果图有循环,那么“祖先”的定义是松散的。也许您的意思是 DFS 或 BFS 的树输出上的祖先?或者,“祖先”是指有向图中最小化来自E
和B
的跳数的节点?
如果您不担心复杂性,那么您可以计算从每个节点到 E
和 B
的 A*(或 Dijkstra 的最短路径)。对于E
和B
都可以到达的节点,可以找到最小化PathLengthToE + PathLengthToB
的节点。
编辑: 现在你已经澄清了一些事情,我想我明白你在寻找什么。
如果你只能“向上”树,那么我建议你从 E
执行 BFS 并从 B
执行 BFS。图中的每个节点都有两个与之关联的变量:来自B
的跃点和来自E
的跃点。让B
和E
都拥有图节点列表的副本。 B
的列表按来自B
的跃点排序,而E
的列表按来自E
的跃点排序。
对于B
列表中的每个元素,尝试在E
列表中找到它。将匹配项放在第三个列表中,按来自B
的跳数+来自E
的跳数排序。在您用尽B
的列表后,您的第三个排序列表应该在其头部包含 LCA。这允许一个解决方案、多个解决方案(通过它们的 BFS 排序为B
任意选择)或没有解决方案。
【讨论】:
节点的祖先必须可以通过“向上”绘制图形来到达,即沿箭头方向遍历边。 @AndrewSwan:是的,但答案仍然不是唯一的。考虑A>C
、B>D
、C>E
、C>F
、D>E
、D>F
。如果我要LCA(A,B)
,你要E
还是F
?
该图在这种情况下无效,因为它有两个根,E 和 F。应该只有一个根,这意味着任何两个节点总是只有一个 LCA。我已经编辑了问题以澄清这一点。
将E>G
、F>G
添加到@tmyklebu 的示例中,您就得到了一个根和两个LCA,E
和F
。这是允许一个节点有多个父节点的直接结果。
@AndrewSwan:我对我的帖子进行了编辑。我是否正确理解了您的问题?以上是关于在有向无环图中找到最低共同祖先的算法?的主要内容,如果未能解决你的问题,请参考以下文章