【问题标题】:How do I iterate over Binary Tree?如何迭代二叉树?
【发布时间】:2011-02-25 22:00:45
【问题描述】:

我现在有

 private static void iterateall(BinaryTree foo) {
    if(foo!= null){
    System.out.println(foo.node);
    iterateall(foo.left);
    iterateall(foo.right);
   }
  }

你能把它改成迭代而不是递归吗?

【问题讨论】:

  • @Michael : 我在家,但这与我的工作无关。
  • 我对上一条评论投了赞成票,因为它充满了 WIN :)
  • 您的没有堆栈的 DFS 完全损坏了。你需要堆栈(或者,一个队列——但我们现在先忽略它),没有办法绕过它。
  • @Konrad:删除它。感谢您指出。
  • 大多数人使用二叉树来进行高效的插入排序。否则,他们将使用集合。要按排序顺序打印,您应该向左迭代,打印节点,然后向右迭代。

标签: java algorithm recursion traversal binary-tree


【解决方案1】:

我有一棵树(不是二进制的),最终用这个非常简单的算法解决了它。其他解决方案使用了 leftright,它们在示例中并不相关,甚至没有实现。

我的结构是:每个父节点都包含子节点列表,每个子节点都包含一个指向父节点的指针。很常见...

经过一堆重构,我想出了以下使用 Kotlin 的示例。转换为您选择的语言应该很简单。

辅助函数

首先,节点必须提供 2 个简单的功能。这将根据您的 Node 类的实现而有所不同:

leftMost - 这是第一个子节点。如果该节点有子节点,它是第一个子节点,等等。如果没有子节点,则返回 this

fun leftMost(): Node {
        if (children.isEmpty()) {
            return this
        }
        var n = this
        while (n.children.isNotEmpty()) {
            n = n.children[0]
        }
        return n
}

nextSibling - 此节点的下一个兄弟节点,或 NULL

fun nextSibling(): Node? {
    if (parent == null) return null
    val siblingIndex = parent.children.indexOf(this) + 1
    return if (siblingIndex < parent.children.size) {
        parent.children[siblingIndex]
    } else {
        null
    }
}

迭代

迭代从根的最左端开始。

然后检查下一个兄弟。

  • 如果不为 NULL 则兄弟的 leftMostChild
  • 如果为 NULL,则为父级,如果父级为 NULL,则完成。

就是这样。

这是一个 Kotlin 迭代器函数。

fun iterator(): Iterator<Node> {
    var next: Node? = this.leftMost()

    return object : Iterator<Node> {

        override fun hasNext(): Boolean {
            return next != null
        }

        override fun next(): Node {
            val ret = next ?: throw NoSuchElementException()
            next = ret.nextSibling()?.leftMost() ?: ret.parent
            return ret
        }
    }
}

这里是相同的 next() 函数,但没有用于处理 NULL 值的 Kotlin 速记,适用于那些不符合语法的函数。

fun next(): Node {
    val ret = next
    if (ret == null) throw NoSuchElementException()
    val nextSibling = ret.nextSibling()
    if (nextSibling != null) {
        next = nextSibling.leftMost()
    }
    else {
        next = ret.parent
    }
    return ret
}

【讨论】:

    【解决方案2】:

    当然,您有两种通用算法,depth first searchbreadth first search

    如果遍历顺序对您不重要,请先考虑广度,迭代更容易实现。你的算法应该是这样的。

    LinkedList queue = new LinkedList();
    
    queue.add(root);
    
    while (!queue.isEmpty()){
        Object element = queue.remove();
    
        queue.add(element.left);
        queue.add(element.right);
    
        // Do your processing with element;
    }
    

    【讨论】:

    • 我想你的意思是while (!queue.isEmpty()) {
    【解决方案3】:

    你能把它改成迭代而不是递归吗?

    您可以使用显式堆栈。伪代码:

    private static void iterateall(BinaryTree foo) {
        Stack<BinaryTree> nodes = new Stack<BinaryTree>();
        nodes.push(foo);
        while (!nodes.isEmpty()) {
            BinaryTree node = nodes.pop();
            if (node == null)
                continue;
            System.out.println(node.node);
            nodes.push(node.right);
            nodes.push(node.left);
        }
    }
    

    但这并不真正优于递归代码(代码中缺少基本条件除外)。

    【讨论】:

    • “不优秀”:我同意。虽然很高兴知道所有递归算法都可以转换为迭代算法以及它是如何完成的,但如果实际完成它通常不会带来任何显着优势。
    • 你不想先推送 node.right(所以它会最后弹出)吗?
    • @ILMTitan:我的代码没有保留遍历的顺序。这样做是对的,我需要颠倒推送顺序。或者实际上使用队列而不是堆栈。
    • @JoachimSauer : it often doesn't bring any significant advantages if it's actually done -- 这在各个方面都明显是错误的。在几乎所有语言和环境中,递归算法按设计要慢得多。它们还使用 更多 更多可用空间,并且很容易使应用程序崩溃。基本事实。与迭代算法相比,递归算法的唯一优势是可读性和易于实现……一些数据结构需要相当多的工程才能迭代遍历,而使用递归可以更容易地遍历它们。
    • @specializt 基本上你说的每一件事都是错的。您似乎将用迭代替换递归等同于用更复杂的算法替换幼稚算法。这有时是很自然的事情(计算斐波那契数是典型的例子),但这不是必然的结果。迭代算法可能(内存/时间)效率低下。相反,递归可以是有效的。例如,斐波那契数的自然递归计算可以与规范迭代版本一样高效(在 O(n) 时间内运行,使用 O(1) 空间)。
    【解决方案4】:

    您正在寻找的是后继算法。

    这是如何定义的:

    • 第一条规则:树中的第一个节点是树中最左边的节点。
    • 下一条规则:一个节点的后继是:
      • Next-R 规则:如果有右子树,则在右子树中最左边的节点。
      • Next-U 规则:否则,向上遍历树
        • 如果你右转(即这个节点是一个左孩子),那么那个父节点就是后继节点
        • 如果您左转(即此节点是右子节点),请继续向上。
        • 上不去,就没有继任者

    如您所见,要使其工作,您需要一个父节点指针。


    示例:

    • 第一条规则:树中的第一个节点是树中最左边的节点:(1)
    • Next-U 规则:由于(1) 没有右子树,所以我们上到(3)。这是右转,接下来是(3)
    • Next-R 规则:由于(3) 有一个右子树,因此该子树中最左边的节点是下一个:(4)
    • Next-U 规则:由于(4) 没有右子树,所以我们上到(6)。这是右转,所以接下来是(6)
    • Next-R 规则:由于(6) 有一个右子树,该子树中最左边的节点就是下一个:(7)
    • Next-U 规则:由于(7) 没有右子树,所以我们上到(6)。这是左转,所以我们继续上(3)。这是左转,所以我们继续上(8)。这是右转,所以接下来是(8)
    • Next-R 规则:由于(8) 有一个右子树,该子树中最左边的节点就是下一个:(10)
    • Next-R 规则:由于(10) 有一个右子树,该子树中最左边的节点就是下一个:(13)
    • Next-U 规则:由于(13) 没有右子树,所以我们上到(14)。这是右转,所以接下来是(14)
    • Next-U 规则:由于(14) 没有右子树,所以我们上到(10)。这是左转,所以我们继续上(8)。这是一个左转,所以我们想继续往上走,但是由于(8) 没有父级,所以我们已经走到了尽头。 (14) 没有继任者。

    伪代码

    Node getLeftMost(Node n)
      WHILE (n.leftChild != NULL)
        n = n.leftChild
      RETURN n
    
    Node getFirst(Tree t)
      IF (t.root == NULL) RETURN NULL
      ELSE
         RETURN getLeftMost(t.root);
    
    Node getNext(Node n)
      IF (n.rightChild != NULL)
         RETURN getLeftMost(n.rightChild)
      ELSE
         WHILE (n.parent != NULL AND n == n.parent.rightChild)
            n = n.parent;
         RETURN n.parent;
    
    PROCEDURE iterateOver(Tree t)
      Node n = getFirst(t);
      WHILE n != NULL
         visit(n)
         n = getNext(n)
    

    Java 代码

    这是上述算法的简单实现:

    public class SuccessorIteration {
        static class Node {
            final Node left;
            final Node right;
            final int key;
            Node parent;
            Node(int key, Node left, Node right) {
                this.key = key;
                this.left = left;
                this.right = right;
                if (left != null) left.parent = this;
                if (right != null) right.parent = this;
            }
            Node getLeftMost() {
                Node n = this;
                while (n.left != null) {
                    n = n.left;
                }
                return n;
            }
            Node getNext() {
                if (right != null) {
                    return right.getLeftMost();
                } else {
                    Node n = this;
                    while (n.parent != null && n == n.parent.right) {
                        n = n.parent;
                    }
                    return n.parent;
                }
            }
        }
    }
    

    然后你可以有一个这样的测试工具:

    static Node C(int key, Node left, Node right) {
        return new Node(key, left, right);
    }
    static Node X(int key)             { return C(key, null, null);  }
    static Node L(int key, Node left)  { return C(key, left, null);  }
    static Node R(int key, Node right) { return C(key, null, right); }
    public static void main(String[] args) {
        Node n =
            C(8,
                C(3,
                    X(1),
                    C(6,
                        X(4),
                        X(7)
                    )
                ),
                R(10,
                    L(14,
                        X(13)
                    )
                )
            );
        Node current = n.getLeftMost();
        while (current != null) {
            System.out.print(current.key + " ");
            current = current.getNext();
        }
    }
    

    打印出来:

    1 3 4 6 7 8 10 13 14 
    

    另见

    【讨论】:

    • 你能不使用递归写一些东西吗?
    • @kunjaan:这不使用递归。
    【解决方案5】:

    与每次递归一样,您可以使用额外的数据结构 - 即堆栈。 解决方案草图

    private static void visitall(BinaryTree foo) {
      Stack<BinaryTree> iterationStack = new Stack<BinaryTree>();
      iterationStack.push(foo);
    
      while (!iterationStack.isEmpty()) {
          BinaryTree current = iterationStack.pop();
          System.out.println(current.node);
          current.push(current.right);        // NOTE! The right one comes first
          current.push(current.left);
       }
    
    }
    

    【讨论】:

    • 嗯...保持相同的访问顺序?如果你先推左边的然后再推右边的,你会先弹出右边的然后再弹出左边的。 LIFO,对吧?
    【解决方案6】:

    是的,您可以将其更改为迭代而不是递归,但它会变得更加复杂,因为您需要有一些方法来记住从当前节点返回的位置返回。在递归的情况下,Java 调用堆栈会处理这个问题,但在迭代解决方案中,您需要构建自己的堆栈,或者可能将反向指针存储在节点中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-11-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-18
      • 2016-02-20
      相关资源
      最近更新 更多