何时使用前序、后序和中序二叉搜索树遍历策略

Posted

技术标签:

【中文标题】何时使用前序、后序和中序二叉搜索树遍历策略【英文标题】:When to use Preorder, Postorder, and Inorder Binary Search Tree Traversal strategies 【发布时间】:2012-03-16 10:46:27 【问题描述】:

我最近意识到,虽然在我的生活中使用了很多 BST,但我什至从未考虑过使用除中序遍历之外的任何东西(虽然我知道并知道调整程序以使用前/后顺序是多么容易遍历)。

意识到这一点后,我拿出了一些旧的数据结构教科书,寻找前序遍历和后序遍历有用性背后的原因——不过他们并没有说太多。

什么时候实际使用预购/后购的例子有哪些?什么时候比有序更有意义?

【问题讨论】:

【参考方案1】:

如果我想简单地以线性格式打印出树的分层格式,我可能会使用前序遍历。例如:

- ROOT
    - A
         - B
         - C
    - D
         - E
         - F
             - G

【讨论】:

或在 GUI 应用程序的 TreeView 组件中。【参考方案2】:

您可以在很多地方看到这种差异发挥了真正的作用。

我要指出的一个很好的方法是为编译器生成代码。考虑以下语句:

x := y + 32

为此生成代码的方式是(当然是天真地)首先生成将 y 加载到寄存器中的代码,将 32 加载到寄存器中,然后生成将两者相加的指令。因为在你操作它之前必须在一个寄存器中(假设你总是可以做常量操作数,但无论如何)你必须这样做。

一般来说,这个问题的答案基本上可以归结为:当处理数据结构的不同部分之间存在某种依赖关系时,差异真的很重要。您在打印元素、生成代码(外部状态有所不同,当然,您也可以单子查看)或在结构上进行其他类型的计算时看到这一点,这些计算涉及取决于首先处理的子项的计算.

【讨论】:

【参考方案3】:

前序和后序分别与自上而下和自下而上的递归算法相关。如果您想以迭代方式在二叉树上编写给定的递归算法,这就是您本质上要做的事情。

进一步观察,前序序列和后序序列一起完全指定了手头的树,产生了紧凑的编码(至少对于稀疏树)。

【讨论】:

我想你是想说一些重要的事情,你能解释一下前半部分吗? @CodeYogi 您具体需要解释什么? “前序和后序与自上而下和自下而上的递归算法有关”我想你想说的是,在第一种情况下,节点在调用任何递归方法之前被处理,反之亦然- 后一种情况相反,对吧? @CodeYogi 是的,基本上。【参考方案4】:

何时使用前序、中序和后序遍历策略

在您了解在什么情况下对二叉树使用前序、中序和后序之前,您必须准确了解每种遍历策略的工作原理。使用以下树作为示例。

树的根是7,最左边的节点是0,最右边的节点是10

预购遍历

摘要:从根 (7) 开始,在最右边的节点 (10) 结束

遍历序列:7、1、0、3、2、5、4、6、9、8、10

中序遍历

总结:从最左边的节点(0)开始,到最右边的节点(10)结束

遍历序列:0、1、2、3、4、5、6、7、8、9、10

后序遍历

总结:从最左边的节点开始(0),到根节点结束(7

遍历序列:0、2、4、6、5、3、1、8、10、9、7

何时使用 Pre-Order、In-order 或 Post-Order?

程序员选择的遍历策略取决于所设计算法的具体需求。目标是速度,因此请选择能够以最快的速度为您带来所需节点的策略。

    如果您知道在检查任何叶子之前需要先探索根部,那么您选择预购,因为您会在所有叶子之前遇到所有根部。

    如果您知道需要在任何节点之前探索所有叶子,请选择 post-order,因为您不会浪费任何时间检查根来寻找叶子。

    如果您知道树在节点中具有固有序列,并且您希望将树展平回其原始序列,则应使用 有序 遍历。树将以与创建它的方式相同的方式展平。前序或后序遍历可能不会将树展开回用于创建它的序列。

前序、中序和后序的递归算法 (C++):

struct Node
    int data;
    Node *left, *right;
;
void preOrderPrint(Node *root)

  print(root->name);                                  //record root
  if (root->left != NULL) preOrderPrint(root->left);  //traverse left if exists
  if (root->right != NULL) preOrderPrint(root->right);//traverse right if exists


void inOrderPrint(Node *root)

  if (root.left != NULL) inOrderPrint(root->left);   //traverse left if exists
  print(root->name);                                 //record root
  if (root.right != NULL) inOrderPrint(root->right); //traverse right if exists


void postOrderPrint(Node *root)

  if (root->left != NULL) postOrderPrint(root->left);  //traverse left if exists
  if (root->right != NULL) postOrderPrint(root->right);//traverse right if exists
  print(root->name);                                   //record root

【讨论】:

非递归遍历呢?在我看来,与中序/后序相比,在前序中以非递归方式遍历树要容易得多,因为它不需要返回到先前的节点。 @bluenote10 你能详细说明你的意思吗?在预购中,您在处理完它的左孩子之后仍然“返回”到一个节点来处理它的右孩子。当然,您可以使用“尚未访问的节点”队列,但这实际上只是将隐式(堆栈)存储换成显式队列。在所有遍历方法中,左右孩子都必须被处理,这意味着在完成其中一个之后,您必须“返回”给父。 @JoshuaTaylor:是的,它们都是同一个复杂度类,但是如果你看一下典型的实现,post-order 可能有点棘手。 前序遍历给出插入序列中的节点值。如果要创建树的副本,则需要以这种方式遍历源树。有序遍历给出排序后的节点值。至于后序遍历,您可以使用此方法删除整个树,因为它首先访问叶子节点。 我认为即使树没有正确排序也是如此,我的意思是如果序列最初没有排序,则顺序不会给出排序序列。【参考方案5】:

预购:用于创建树的副本。例如,如果要创建树的副本,请将节点放入具有前序遍历的数组中。然后对数组中的每个值在新树上执行 Insert 操作。您最终会得到原始树的副本。

In-order: : 用于在 BST 中按非递减顺序获取节点的值。

后序::用于从叶子到根删除一棵树

【讨论】:

这是一个很好的简洁答案,帮助我理解了预购和后购用例。虽然,鉴于问题直接提到了这一点,这可能很明显,但请注意,二叉搜索树就是这种情况,不一定适用于一般二叉树。例如,您不一定要使用前序遍历来复制通用二叉树,因为复制过程中的插入逻辑不起作用。 In-order: : 以“非递减”顺序获取节点的值 - 而不是“非递增”【参考方案6】:

有序 :从任何解析树中获取原始输入字符串,因为解析树遵循运算符的优先级。首先遍历最深的子树。因此,父节点中的运算符的优先级低于子树中的运算符。

【讨论】:

以上是关于何时使用前序、后序和中序二叉搜索树遍历策略的主要内容,如果未能解决你的问题,请参考以下文章

怎么根据二叉树的前序,中序,确定它的后序

根据前序遍历和中序遍历得出后序遍历

通过遍历序列构造二叉树(扩展二叉树的先序先序和中序后序和中序层序和中序)附可执行完整代码

数据结构中已知前序序列和中序序列,怎么得出后序序列,谢谢回答!

C语言编程:已知二叉树前序和中序,如何求出后序遍历?

5.根据前序和中序得到二叉树代码实现,根据中序和后序得到二叉树代码实现(JavaScript版)