在有向无环图中找到最低共同祖先的算法?

时间:2013-02-13 23:14:25

标签: algorithm graph directed-acyclic-graphs lowest-common-ancestor

想象一下有向无环图如下,其中:

  • “A”是根(总有一个根)
  • 每个节点都知道其父节点
  • 节点名称是任意的 - 无法从中推断出任何内容
  • 我们从另一个来源得知节点是按照A到G的顺序添加到树中的(例如它们是版本控制系统中的提交)

Directed Acyclic Graph

我可以使用什么算法来确定两个任意节点的最低共同祖先(LCA),例如,共同的祖先:

  • B和E是B
  • D和F是B

注意:

11 个答案:

答案 0 :(得分:9)

Den Roman's link似乎很有希望,但对我来说似乎有点复杂,所以我尝试了另一种方法。这是我使用的一个简单算法:

假设您要使用 x y 两个节点计算LCA(x,y)。 每个节点的值必须为colorcount。初始化为白色 0

  1. x 的所有祖先标记为 blue (可以使用BFS完成)
  2. y 的所有蓝色祖先标记为红色(再次BFS)
  3. 对于图表中的每个 red 节点,递增其父项' count一个
  4. 每个 red 节点的count值设置为 0 是一个解决方案。

    根据您的图表,可以有多个解决方案。例如,请考虑以下图表:

    DAG example where LCA can have two values

    LCA(4,5)可能的解决方案是1和2.

    请注意,如果您想要找到3个或更多节点的LCA,它仍然有效,您只需要为每个节点添加不同的颜色。

答案 1 :(得分:5)

我正在寻找同一问题的解决方案,我在下面的论文中找到了解决方案:

http://dx.doi.org/10.1016/j.ipl.2010.02.014

简而言之,您不是在寻找最低的共同祖先,而是寻找他们在本文中定义的最低的共同祖先。

答案 2 :(得分:1)

只是一些疯狂的想法。如何将两个输入节点用作根,并逐步同时执行两个BFS。在某一步骤,当它们的BLACK集(记录访问节点)中存在重叠时,算法停止并且重叠的节点是它们的LCA(s)。通过这种方式,任何其他共同的祖先将拥有比我们发现的更长的距离。

答案 3 :(得分:1)

我知道这是一个老问题,而且讨论很好,但是由于我有一些类似的问题要解决,因此我遇到了JGraphTLowest Common Ancestor算法,因此认为这可能会有所帮助:

答案 4 :(得分:0)

如果图表有周期,那么'祖先'的定义是松散的。也许你的意思是DFS或BFS的树输出上的祖先?或者也许是'祖先',你的意思是有向图中的节点可以最小化来自EB的跳数?

如果您不担心复杂性,那么您可以计算从每个节点到EB的A *(或Dijkstra的最短路径)。对于可以同时访问EB的节点,您可以找到最小化PathLengthToE + PathLengthToB的节点。

编辑: 既然你已经澄清了一些事情,我想我明白你在寻找什么。

如果你只能“爬上”树,那么我建议你从E执行BFS,从B执行BFS。图表中的每个节点都有两个与之关联的变量:来自B的跃点和来自E的跃点。让BE都包含图节点列表的副本。 B的列表按B的跃点排序,而E的列表按E的跃点排序。

对于B列表中的每个元素,尝试在E列表中找到它。将匹配项放在第三个列表中,按B +来自E的跃点的跃点排序。在您用尽B列表后,您的第三个排序列表应该包含LCA。这允许一个解决方案,多个解决方案(在B的BFS排序中任意选择),或者没有解决方案。

答案 5 :(得分:0)

我还需要完全相同的东西,在DAG(有向无环图)中找到LCA。 LCA问题与RMQ(范围最小查询问题)有关。

可以将LCA降低到RMQ,并从有向无环图中找到两个任意节点的所需LCA。

我发现THIS TUTORIAL细节很好。我也计划实施这个。

答案 6 :(得分:0)

我提议O(| V | + | E |)时间复杂度解决方案,我认为这种方法是正确的,否则请纠正我。

给定有向无环图,我们需要找到两个顶点v和w的LCA。

步骤1:使用时间复杂度为O(| V | + | E |)的bfs http://en.wikipedia.org/wiki/Breadth-first_search查找根顶点的所有顶点的最短距离,并找到每个顶点的父节点。

步骤2:使用父级找到两个顶点的共同祖先,直到达到根顶点时间复杂度 - 2 | v |

步骤3:LCA将是具有最大最短距离的共同祖先。

所以,这是O(| V | + | E |)时间复杂度算法。

如果我错了或欢迎任何其他建议,请纠正我。

答案 7 :(得分:0)

http://www.gghh.name/dibtp/2014/02/25/how-does-mercurial-select-the-greatest-common-ancestor.html

此链接描述了如何在Mercurial中完成 - 基本思想是找到指定节点的所有父节点,按距离根节点对它们进行分组,然后搜索这些节点。

答案 8 :(得分:0)

假设您想在图表中找到x和y的祖先。

维护一组vector- (存储每个节点的父节点)。

  1. 首先做一个bfs(继续存储每个顶点的父节点)并找到x的所有祖先(找到x的父级并使用父级,找到x的所有祖先)并存储他们在向量中。另外,将每个父项的深度存储在向量中。

  2. 使用相同的方法查找y的祖先并将它们存储在另一个向量中。现在,你有两个向量分别存储x和y的祖先及其深度。

  3. LCA将是具有最大深度的共同祖先。深度定义为距离根的最长距离(顶点与in_degree = 0)。现在,我们可以按照深度的递减顺序对向量进行排序,并找出LCA。使用这种方法,我们甚至可以找到多个LCA(如果有的话)。

答案 9 :(得分:0)

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;
    }
}

答案 10 :(得分:-2)

的所有人。 请用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按降级顺序排序。 我们找到字符串的最低和最高索引(哈希 - 并不意味着)。 显然(考虑后代顺序)共同的祖先只能在上指数之后(哈希中的较低值)。 然后我们开始枚举提交的哈希和构建后代父链的链。为此,我们有两个hashset由commit的最低和最高哈希的父项初始化。 setOfAncestorsLower,setOfAncestorsUpper。如果下一个hash -commit属于任何链(hashsets), 那么如果当前索引高于最低散列索引,那么如果它包含在另一个集合(链)中,我们将返回当前散列作为结果。如果没有,我们将其父(祖先[i])添加到hashset,它跟踪set的祖先集合,其中包含当前元素。这就是全部,基本上是