您可以使用 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 2 5”。全部未使用,输出“1 2 5”并将1、2、5标记为使用过一次
- 现在,我们在“1 2 6”。使用 1、2 - 2 是最后一个。从 2(含)输出,“2 6”,将 2 和 6 标记为已使用。
- 现在我们在“1 3 7”,使用了 1,唯一的也是最后一个。从 1 输出,包括“1 3 7”。将 1、3、7 标记为已使用。
- 现在我们在“1 4 8”。 1 被使用,唯一和最后一个。输出“1 4 8”。
- 现在我们在“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,因为在树中路径很容易恢复;我们可以提高输出;我们可以将此算法封装在一个类中。我只是试图展示常见的解决方案。