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

Data structures 何时使用前序、后序和按序二叉搜索树遍历策略,data-structures,computer-science,binary-tree,preorder,Data Structures,Computer Science,Binary Tree,Preorder,我最近意识到,虽然在我的生活中使用了大量的BST,但我从来没有想过使用除顺序遍历之外的任何东西(尽管我知道并知道将程序调整为使用前/后顺序遍历是多么容易) 意识到这一点后,我拿出一些旧的数据结构教科书,寻找前序和后序遍历有用性背后的原因——尽管它们没有说太多 什么时候可以实际使用前序/后序?什么时候它比按顺序更有意义?如果我想简单地以线性格式打印出树的层次结构格式,我可能会使用预排序遍历。例如: - ROOT - A - B - C - D

我最近意识到,虽然在我的生活中使用了大量的BST,但我从来没有想过使用除顺序遍历之外的任何东西(尽管我知道并知道将程序调整为使用前/后顺序遍历是多么容易)

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


什么时候可以实际使用前序/后序?什么时候它比按顺序更有意义?

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

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

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

我要指出的一个很好的例子是编译器的代码生成。考虑以下陈述:

x := y + 32
生成代码的方法是(当然是天真的)首先生成代码,将y加载到寄存器中,将32加载到寄存器中,然后生成一条指令将两者相加。因为在操作某个寄存器之前,它必须在寄存器中(让我们假设,您总是可以执行常量操作数,但不管什么),所以必须这样做


一般来说,这个问题的答案基本上可以归结为:当处理数据结构的不同部分之间存在某种依赖性时,差异确实很重要。在打印元素时,在生成代码时,您会看到这一点(外部状态会有所不同,当然,您也可以一元查看这一点),或者在结构上执行其他类型的计算时,根据首先处理的子级进行计算。

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

此外,请注意,前序和后序序列一起完全指定了手头的树,产生了紧凑的编码(至少对于稀疏树)。

何时使用前序、顺序和后序遍历策略 在了解在什么情况下使用二叉树的预序、顺序和后序之前,您必须准确地了解每个遍历策略是如何工作的。以下面的树为例

树的根是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

何时使用预订单、按订单或后订单? 程序员选择的遍历策略取决于所设计算法的特定需求。目标是速度,因此选择能够以最快的速度为您提供所需节点的策略

  • 如果您知道在检查任何叶子之前需要探索根,请选择预排序,因为您将在所有叶子之前遇到所有根

  • 如果您知道需要在任何节点之前浏览所有叶,请选择post order,因为在搜索叶时不会浪费任何时间检查根

  • 如果您知道树在节点中具有固有序列,并且您希望将树展平回其原始序列,则应使用按顺序遍历。这棵树将以与创建时相同的方式被压扁。预订单或后订单遍历可能不会将树放回到用于创建它的序列中

  • 预排序、按顺序和后排序的递归算法(C++):
    预订单:用于创建树的副本。例如,如果要创建树的副本,请将节点放置在具有预顺序遍历的数组中。然后对数组中的每个值在新树上执行插入操作。您将得到原始树的副本

    顺序::用于在BST中以非递减顺序获取节点的值


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

    或在GUI应用程序的
    TreeView
    组件中删除树。非递归遍历如何?在我看来,与按顺序/按后顺序相比,按前顺序以非递归方式遍历树要容易得多,因为它不需要返回到以前的节点。@bluenote10您能详细说明一下您的意思吗?在预排序中,在处理其左子节点后,仍然“返回”到节点以处理其右子节点。当然,您可以使用“尚未访问的节点”队列,但这实际上只是用隐式(堆栈)存储换取显式队列。在所有遍历方法中,都必须处理左、右子项,这意味着在执行其中一个之后,必须“返回”到父项。@JoshuaTaylor:是的,它们都是相同的复杂度类,但如果查看典型的实现,这可能有点棘手。预顺序遍历以插入序列的形式给出节点值。如果要创建树的副本,需要以这种方式遍历源树。按顺序遍历给出排序的节点值。至于后期订单遍历,您可以使用
    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
    }