在由具有父指针和子指针的公共节点结构表示的通用树中,如何找到彼此没有重叠边的所有路径的列表,并以叶节点终止。
例如,给定这样的树:
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]]
为了这个问题,不需要特定的订单。
答案 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”。
在你的例子中:
它起作用,因为在树中“使用的节点”意味着“在它和它的父节点之间使用(唯一的父节点)”。因此,我们实际上标记了使用的边缘,而不是再次输出它们。
例如,当我们使用标记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)
}
}
}
}