【问题标题】:List of all edge disjoint paths in a tree树中所有边缘不相交路径的列表
【发布时间】:2015-08-18 00:08:32
【问题描述】:

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

例如,给定一棵这样的树:

          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]]

对于这个问题,不需要特定的顺序。

【问题讨论】:

  • 您是在寻找正式的、可工作的代码,还是只是一种算法?
  • 工作代码当然更好,我通常使用 Python 而不是伪代码,因为它更容易测试。
  • 提示:添加一个约束,使解是唯一的。
  • @BaselShishani 既然你没有提到任何语言,我写了一个算法。希望对您有所帮助。

标签: algorithm tree


【解决方案1】:

您可以使用 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。让我们在当前路径中找到最后一个 used-once-node(它已经在一个输出中),并输出该节点的所有路径。保证这样的节点存在是因为,至少路径中的最后一个节点以前从未遇到过,并且不能被标记为使用一次。

例如,如果当前路径是“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”。

之所以有效,是因为在树中“使用的节点”意味着“它与其父节点之间的已使用(唯一父节点)边”。因此,我们实际上标记了已使用的边缘并且不再输出它们。

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

抱歉,解释不佳。不画图就很难解释树上的算法 :) 如有任何关于算法或 C# 的问题,请随时提出。

这是工作的IDEOne demo

P.S. 这段代码可能不是一个好的正确 C# 代码(避免了自动属性,避免了 LINQ),以便其他编码人员可以理解。

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

【讨论】:

  • 他真的应该列出一种实现语言。
  • @TimBiegeleisen 是的。当 OP 提到 Python 作为 cmets 中理想的语言时,我几乎已经完成了 C# 代码。反正我不懂Python :)
【解决方案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]]

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      这可以使用 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)
         }
        }
       }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-01-26
        • 1970-01-01
        • 2016-08-22
        • 1970-01-01
        • 2013-07-23
        • 1970-01-01
        • 2023-01-16
        • 1970-01-01
        相关资源
        最近更新 更多