【问题标题】:Finding the left-most child for every node in a tree in linear time?在线性时间内为树中的每个节点找到最左边的孩子?
【发布时间】:2013-11-08 18:10:45
【问题描述】:

A paper我正在阅读声称

很容易看出有一个线性时间算法来计算函数l()

其中l() 给出最左边的孩子(输入和输出都在树的后序遍历中)。但是,我只能想到一个幼稚的O(n^2) 实现,其中n 是树中的节点数。

例如,考虑以下树:

  a
 / \
c   b

在后序遍历中,树是c b a。对应的函数l() 应该给出c b c

这是我在O(n^2) 时间的实现。

public Object[] computeFunctionL(){
    ArrayList<String> l= new ArrayList<String>();
    l= l(this, l);
    return l.toArray();
}

private ArrayList<String> l(Node currentRoot, ArrayList<String> l){
    for (int i= 0; i < currentRoot.children.size(); i++){
        l= l(currentRoot.children.get(i), l);
    }
    while(currentRoot.children.size() != 0){
        currentRoot= currentRoot.children.get(0);
    }
    l.add(currentRoot.label);
    return l;
}

树是这样的:

public class Node {
private String label;
private ArrayList<Node> children= new ArrayList<Node>();
...

【问题讨论】:

  • 有问题的树是如何存储的?
  • 论文声称在第 8 页的第一段中存在线性时间算法。
  • 我认为这是指找到给定节点的最左边的孩子?这不是从当前节点“向左走”,直到你到达一个没有左孩子的节点吗?那应该是线性的。
  • 我不确定我是否理解。您能否提供一个明确的算法作为答案?
  • @Justin,是的,只有当您考虑一个节点时,它才是线性的。但是,如果您要计算所有l(),那么它将是O(n^2)。或者您可能是说本文仅在考虑一个节点时才提及线性时间(但它总体上是O(n^2))?

标签: algorithm data-structures tree theory graph-theory


【解决方案1】:

您可以使用一种简单的递归算法,可以在每个节点的 O(1) 时间内计算此信息。由于总共有 n 个节点,这将在 O(n) 总时间内运行。

基本思想是以下递归洞察:

  • 对于任何没有左孩子的节点 n,l(n) = n。
  • 否则,如果 n 有左子 L,则 l(n) = l(L)。

这产生了这种递归算法,它用它的 l 值注释每个节点:

function computeL(node n) {
   if n is null, return.

   computeL(n.left)
   computeL(n.right)

   if n has no left child:
      set n.l = n
   else
      set n.l = n.left.l

希望这会有所帮助!

【讨论】:

    【解决方案2】:

    您可以在少于 O(n^2) 的时间内找到整棵树的 l()。这个想法是按顺序遍历树,在遍历左分支时维护您访问过的节点堆栈。当你到达一个叶子时,它就是整个分支的最左边的节点。

    这是一个例子:

    class BTreeNode
    {
        public readonly int Value;
        public BTreeNode LeftChild { get; private set; }
        public BTreeNode RightChild { get; private set; }
    }
    
    void ShowLeftmost(BTreeNode node, Stack<int> stack)
    {
        if (node.LeftChild == null)
        {
            // this is the leftmost node of every node on the stack
            while (stack.Count > 0)
            {
                var v = stack.Pop();
                Console.WriteLine("Leftmost node of {0} is {1}", v, node.Value);
            }
        }
        else
        {
            // push this value onto the stack so that
            // we can add its leftmost node when we find it.
            stack.Push(node.Value);
            ShowLeftmost(node.LeftChild, stack);
        }
        if (node.RightChild != null)
            ShowLeftmost(node.RightChild, stack);
    }
    

    复杂度显然不是 O(n^2)。相反,它是 O(n)。

    遍历树需要 O(n)。没有节点被多次放置在堆栈上。该算法的最坏情况是包含所有左节点的树。在这种情况下,遍历树是 O(n),枚举堆栈是 O(n)。最好的情况是一棵包含所有正确节点的树,在这种情况下,永远不会有任何堆栈可供枚举。

    所以 O(n) 时间复杂度,O(n) 最坏情况下的堆栈额外空间。

    【讨论】:

    • 如果我没记错的话,templatetypedef 找到了一个甚至不需要堆栈的答案。看看吧。
    • @ijkilchenko:是的,如果你可以注释节点,那效果很好。
    【解决方案3】:

    看看第 3.1 节:

    3.1。符号。令 T[i] 为从左到右的树中的第 i 个节点 后序编号,l(i) 是子树最左边叶子后代的编号 植根于 T[i]。

    鉴于关于符号的那句话,我假设函数 l() 指的是在线性时间内找到单个节点。

    可能有一种更优雅(比 O(n^2) 更好)的方法可以为整个树找到 l(),但我认为它指的是单个节点。

    【讨论】:

    • 有一个 O(n log n) 的解决方案可以找到整个树的 l()。看我的回答。
    • 其实对整棵树求l()是O(n)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-30
    • 2022-08-11
    • 2021-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多