何时使用预排序,后排序和有序二进制搜索树遍历策略


97

我最近才意识到,虽然我一生都在使用BST,但我什至从未考虑过使用除顺序遍历之外的任何方法(虽然我知道并且知道使程序适应使用前/后遍历是多么容易)。

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

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

Answers:


135

何时使用订购前,订购中和订购后遍历策略

在您了解在何种情况下对二叉树使用预排序,按顺序排序和后排序之前,您必须确切地了解每种遍历策略的工作方式。以下面的树为例。

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

在此处输入图片说明

预定遍历

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

遍历顺序:7,1,0,3,2,5,4,4,9,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

何时使用预购,订购或后购?

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

  1. 如果您知道需要在检查任何叶子之前先探究根,请选择预购商品,因为您会在所有叶子之前遇到所有根。

  2. 如果知道需要在任何节点之前浏览所有叶子,则选择后置顺序,因为您不会浪费时间检查根源以寻找叶子。

  3. 如果您知道树在节点中具有固有序列,并且想要将树展平为其原始序列,则应使用有序遍历。该树将以与创建它相同的方式展平。预遍历或后遍历可能不会使树退回到用于创建树的序列中。

顺序,顺序和后顺序(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
}

3
非递归遍历又如何呢?在我看来,与按顺序/后顺序相比,按顺序非递归遍历树要容易得多,因为它不需要返回到先前的节点。
bluenote10

@ bluenote10您能否详细说明您的意思?在预订购中,您仍然可以在处理其左子节点之后“返回”节点以处理其右子节点。当然,您可以使用“尚未访问的节点”队列,但这实际上只是将隐式(堆栈)存储换为显式队列。在所有遍历方法中,必须处理左右两个子级,这意味着在执行其中一个子级之后,您必须“返回”父级。
约书亚·泰勒

@JoshuaTaylor:是的,它们都是相同的复杂度类,但是如果您查看典型的实现,则后置顺序可能有点棘手。
bluenote10

2
预遍历将按插入顺序给出节点值。如果要创建树的副本,则需要以这种方式遍历源树。按顺序遍历给出排序的节点值。对于后遍历,您可以使用此方法删除整个树,因为它首先访问叶节点。
albin

我认为即使没有正确排序树也是如此,我的意思是,如果一开始不对序列进行排序,那么按顺序就不会给出排序的序列。
CodeYogi

29

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

按顺序::用于在BST中以非降序获取节点的值。

后置订单::用于从叶子到根删除树


2
这是一个很好的简洁答案,它帮助我理解了预购和后购用例。虽然,很明显,这个问题直接提到了这个问题,但是请注意,对于二叉SEARCH树是这种情况,不一定对一般二叉树有效。例如,您不一定必须使用顺序遍历来复制常规的二叉树,因为复制过程中的插入逻辑将不起作用。
markckim

7
按顺序::以“不减少”顺序获取节点的值-不“不增加”获取节点的值
rahil008

26

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

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

4
或在TreeViewGUI应用程序的组件中。
svick

4

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

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


1
我认为您正在尝试说一些重要的事情,能否请您解释一下前半部分?
CodeYogi '16

@CodeYogi您需要具体解释什么?
拉斐尔

1
“前顺序和后顺序与自上而下和自下而上的递归算法有关”,我想您要说的是,在第一种情况下,先处理节点,然后再调用任何一种递归方法,反之亦然?
CodeYogi '16

@CodeYogi是的,基本上。
拉斐尔

2

在很多地方,您会发现这种差异起着真正的作用。

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

x := y + 32

为此生成代码的方式是(当然是天真的)首先生成代码,以将y加载到寄存器中,将32加载到寄存器中,然后生成指令以将两者相加。因为在操作某个东西之前必须先将其放入寄存器中(假设,您始终可以执行常量操作数,但是无论如何),您必须以这种方式进行操作。

通常,您可以从这个问题上得到的答案基本上可以简化为:处理数据结构的不同部分之间存在某种依赖性时,差异确实很重要。在打印元素时,生成代码时(外部状态有所不同,当然也可以单调地查看),或者在结构上进行其他类型的计算时涉及到计算,具体取决于首先处理的子代,您会看到这一点。 。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.