树中所有边缘不相交路径的列表

时间:2015-08-17 05:03:25

标签: algorithm tree

在由具有父指针和子指针的公共节点结构表示的通用树中,如何找到彼此没有重叠边的所有路径的列表,并以叶节点终止。

例如,给定这样的树:

          1
      /   |   \               
     2    3    4
    / \   |   / \
   5   6  7  8   9 

所需的输出将是路径列表,如下所示:

1    2    1    1    4
|    |    |    |    |
2    6    3    4    9
|         |    | 
5         7    8

或以列表形式:

[[1, 2, 5], [2, 6], [1, 3, 7], [1, 4, 8], [4, 9]]

显然,路径列出了自己,并且它们的顺序可以根据树枝的处理顺序而变化。例如,如果我们首先处理左分支,则以下是另一种可能的解决方案:

[[1, 4, 9], [4, 8], [1, 3, 7], [1, 2, 6], [2, 5]]

为了这个问题,不需要特定的订单。

4 个答案:

答案 0 :(得分:3)

您可以使用recursive DFS algorithm进行一些修改 您没有说出您使用的语言,所以,我希望C#对您来说没问题。

让我们为树节点定义一个类:

public class Node
{
    public int Id;
    public bool UsedOnce = false;
    public bool Visited = false;
    public Node[] Children;
}

看一下UsedOnce变量 - 看起来非常暧昧 如果此节点已在输出中使用过一次,则UsedOnce等于true。由于我们有一棵树,这也意味着从这个节点到它的父的边缘在输出中被使用过一次(在树中,每个节点只有一个父边缘,它是它的边缘)父母)。请仔细阅读,以免日后混淆。

这里我们有一个简单的,基本的深度优先搜索算法实现 输出法将涵盖所有魔法。

List<Node> currentPath = new List<Node>(); // list of visited nodes

public void DFS(Node node)
{
    if (node.Children.Length == 0) // if it is a leaf (no children) - output
    {
        OutputAndMarkAsUsedOnce(); // Here goes the magic...
        return;
    }

    foreach (var child in node.Children)
    {
        if (!child.Visited) // for every not visited children call DFS
        {
            child.Visited = true;
            currentPath.Add(child);
            DFS(child); 
            currentPath.Remove(child);
            child.Visited = false;
        }
    }
}

如果OutputAndMarkedAsUsedOnce刚刚输出currentPath个内容,那么我们就会得到一个简单的DFS输出:

1 2 5
1 2 6
1 3 7
1 4 8
1 4 9

现在,我们需要使用UsedOnce。让在当前路径中找到最后一次使用过的节点(已经在输出中)并输出该节点的所有路径。保证存在这样的节点,因为至少路径中的最后一个节点以前从未被满足,并且不能被标记为使用过一次。

例如,如果当前路径为“1 2 3 4 5”且1,2,3标记为使用一次 - 则输出“3 4 5”。

在你的例子中:

  1. 我们在“1 2 5”。所有这些都未使用,输出“1 2 5”并标记为1,2,5,使用一次
  2. 现在,我们在“1 2 6”。使用1,2, - 2是最后一个。输出2包括“2 6”,标记2和6使用。
  3. 现在我们在“1 3 7”,使用1,唯一和最后。输出从1包含,“1 3 7”。标记1,3,7使用。
  4. 现在我们在“1 4 8”。使用1,唯一和最后。输出“1 4 8”。
  5. 现在我们在“1 4 9”。使用1,4。输出4 - “4 9”。
  6. 它起作用,因为在树中“使用的节点”意味着“在它和它的父节点之间使用(唯一的父节点)”。因此,我们实际上标记了使用的边缘,而不是再次输出它们。

    例如,当我们使用标记2,5时 - 这意味着我们标记边1-2和2-5。然后,当我们去“1 2 6”时 - 我们不输出边缘“1-2”因为它被使用,但输出“2-6”。
    将根节点(节点1)标记为使用一次不会影响输出,因为它的值永远不会被检查。它有一个物理解释 - 根节点没有父边缘。

    对不起解释不好意思。在没有绘图的情况下在树上解释算法非常困难:)可以随意提出任何有关算法或C#的问题。

    这是工作IDEOne demo

    PS 这段代码可能不是一个好的正确的 C#代码(避免了自动属性,避免使用LINQ),以使其他编码器可以理解。

    当然,这个算法并不完美 - 我们可以删除currentPath,因为在树中路径很容易恢复;我们可以提高产量;我们可以将这个算法封装在一个类中。我只是试图展示共同的解决方案。

答案 1 :(得分:2)

这是一棵树。其他解决方案可能有效,但不必要地复杂化。用Python表示树结构。

class Node:
    def __init__(self, label, children):
        self.label = label
        self.children = children

然后是树

  1
 / \
2   3
   / \
  4   5

Node(1, [Node(2, []), Node(3, [Node(4, []), Node(5, [])])])。按如下方式进行递归过程。我们保证根出现在第一条路径中。

def disjointpaths(node):
    if node.children:
        paths = []
        for child in node.children:
            childpaths = disjointpaths(child)
            childpaths[0].insert(0, node.label)
            paths.extend(childpaths)
        return paths
    else:
        return [[node.label]]

可以优化(第一个目标:停止插入列表的前面)。

答案 2 :(得分:1)

对于所有顶点,如果顶点是leaf(没有子指针),则遍历父链,直到找到没有父节点的标记顶点或顶点。标记所有访问过的顶点。将顶点收集到中间列表,然后反转它并添加到结果中。

如果无法在顶点对象本身中添加标记,则可以将标记实现为一组单独的访问顶点,并将所有添加到集合中的顶点视为已标记。

答案 3 :(得分:1)

使用 DFS 可以非常轻松地完成此操作。

我们从root调用DFS。

DFS(root,list)

列表最初包含

list = {root}

现在算法如下:

DFS(ptr,list)
{
 if(ptr is a leaf)
  print the list and return
 else
 {
  for ith children of ptr do
  {
   if(ptr is root)
   {
    add the child to list
    DFS(ith child of ptr,list)
    remove the added child
   }
   else if(i equals 1 that is first child)
   {
    add the child to list
    DFS(ith child of ptr,list)
   }
   else
   {
    initialize a new empty list list2
    add ith child and the ptr node to list2
    DFS(ith child of ptr,list2)
   }
  }
 }
}