我最近才意识到,虽然我一生都在使用BST,但我什至从未考虑过使用除顺序遍历之外的任何方法(虽然我知道并且知道使程序适应使用前/后遍历是多么容易)。
意识到这一点后,我抽出了一些旧的数据结构教科书,并寻找了在顺序遍历和后序遍历的有用性背后的原因-尽管它们并没有说太多。
什么时候实际使用预购/后购的一些示例?什么时候比有秩序更有意义?
我最近才意识到,虽然我一生都在使用BST,但我什至从未考虑过使用除顺序遍历之外的任何方法(虽然我知道并且知道使程序适应使用前/后遍历是多么容易)。
意识到这一点后,我抽出了一些旧的数据结构教科书,并寻找了在顺序遍历和后序遍历的有用性背后的原因-尽管它们并没有说太多。
什么时候实际使用预购/后购的一些示例?什么时候比有秩序更有意义?
Answers:
在您了解在何种情况下对二叉树使用预排序,按顺序排序和后排序之前,您必须确切地了解每种遍历策略的工作方式。以下面的树为例。
树的根是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
程序员选择的遍历策略取决于所设计算法的特定需求。目标是速度,因此请选择能为您带来最快所需节点的策略。
如果您知道需要在检查任何叶子之前先探究根,请选择预购商品,因为您会在所有叶子之前遇到所有根。
如果知道需要在任何节点之前浏览所有叶子,则选择后置顺序,因为您不会浪费时间检查根源以寻找叶子。
如果您知道树在节点中具有固有序列,并且想要将树展平为其原始序列,则应使用有序遍历。该树将以与创建它相同的方式展平。预遍历或后遍历可能不会使树退回到用于创建树的序列中。
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
}
前顺序和后顺序分别与自上而下和自下而上的递归算法有关。如果要以迭代方式在二叉树上编写给定的递归算法,这实际上就是您要做的。
进一步观察到,前顺序和后顺序序列一起完全指定了手头的树,从而产生了紧凑的编码(至少对于稀疏树而言)。
在很多地方,您会发现这种差异起着真正的作用。
我要指出的一个很棒的方面是编译器的代码生成。考虑以下语句:
x := y + 32
为此生成代码的方式是(当然是天真的)首先生成代码,以将y加载到寄存器中,将32加载到寄存器中,然后生成指令以将两者相加。因为在操作某个东西之前必须先将其放入寄存器中(假设,您始终可以执行常量操作数,但是无论如何),您必须以这种方式进行操作。
通常,您可以从这个问题上得到的答案基本上可以简化为:处理数据结构的不同部分之间存在某种依赖性时,差异确实很重要。在打印元素时,生成代码时(外部状态有所不同,当然也可以单调地查看),或者在结构上进行其他类型的计算时涉及到计算,具体取决于首先处理的子代,您会看到这一点。 。