从预订单和后序列表重建树

时间:2009-07-16 11:40:45

标签: algorithm language-agnostic reference tree traversal

考虑这样一种情况:你有两个节点列表,其中你知道的一个是一个树的前序遍历的表示,另一个是同一树的后序遍历的表示。

我相信可以从这两个列表中精确地重建树,我想我有一个算法来做,但还没有证明。由于这将成为硕士项目的一部分,我需要绝对确定它是可能的和正确的(数学证明)。然而,它不会是项目的重点,所以我想知道是否有一个来源(即纸张或书籍),我可以引用证明。 (也许在TAOCP?有人可能知道该部分吗?)

简而言之,我需要在可引用资源中使用经过验证的算法,该算法可以从其前后顺序遍历中重构树。


注意:有问题的树可能不是二元的,或者是平衡的,或任何使它太容易的东西。

注意2:仅使用预订单或后序列表会更好,但我认为不可能。

注3:节点可以包含任意数量的子节点。

注4:我只关心兄弟姐妹的顺序。当只有一个孩子时,左或右无关紧要。

7 个答案:

答案 0 :(得分:33)

Preorder and postorder do not uniquely define a tree.

  

通常,单个树遍历不能唯一地定义   树的结构。例如,正如我们所见,两者兼而有之   以下树,顺序遍历产生[1,2,3,4,5,6]。

    4                     3
   / \                   / \
  2   5                 2   5
 / \   \               /   / \
1   3   6             1   4   6
  

预订和后序存在同样的歧义   遍历。上面第一棵树的前序遍历是   [4,2,1,3,5,6]。这是一个具有相同预订顺序的不同树   遍历。

    4
   / \
  2   1
     / \
    3   6
     \
      5
  

同样,我们可以轻松地构建另一个后序排列的树   遍历[1,3,2,6,5,4]与上面第一棵树的匹配。

答案 1 :(得分:9)

您不能只使用一个列表,因为您将无法理解树的深度。因此,您肯定需要两个或更多列表。

这是我尝试解决方案:

使用您的前序遍历作为了解数据排序的方法。这是有道理的,因为您知道第一个节点是顶部,并且您知道遍历左侧的数据属于树的左侧,等等。

您的邮政订单遍历可以确定树的深度。例如,假设我有这样的结构:

      1
  2   5   6
 3 4  7

Where 2 is the parent of 3 and 4, and 5 is the parent of 7.

Preorder: 1 2 3 4 5 7 6
Postorder: 3 4 2 7 5 6 1

我们知道我们从1开始,因为它是前序遍历中的第一个节点。然后我们看下一个数字,2。在后期顺序中,因为数字2来自节点1之前,我们知道2必须是1的子节点。接下来我们看看3. 3来自2之前,因此3是2的孩子.4在2之前但在3之后,所以我们知道4是2的孩子但不是3的孩子。等等。

现在,如果节点不是唯一的,这可能不起作用,但至少它是解决方案的开始。

编辑:使用此解决方案保留子项的顺序,这只是因为通过前序遍历了解节点的顺序,然后通过后序遍历了解结构。

编辑2:证据可能在于:http://ieeexplore.ieee.org/Xplore/login.jsp?url=http%3A%2F%2Fieeexplore.ieee.org%2Fiel2%2F215%2F626%2F00017225.pdf%3Farnumber%3D17225&authDecision=-203

我认为您需要购买该文件,但是......

以下是一份提供解决方案的书面证明:

http://www14.informatik.tu-muenchen.de/lehre/2007WS/fa-cse/tutorials/tutorial09-solutions.pdf

答案 2 :(得分:4)

将任意树 T 视为四元组(A,B, C D ),其中A是根节点,B是第一个孩子的根节点, C 是B的任何非空孩子的向量, D 是B的任何非空兄弟的向量。 C D 的元素本身就是树。

A,B, C D 中的任何一个都可能为空。如果B为空,那么必须 C D ;如果A,那么一切。

由于节点是唯一的,因此 C D 中包含的节点集是不相交的,并且都不包含A或B.

函数 pre() post()生成以下形式的有序序列:

pre(T) = [A,B, pre( C pre( D

帖子(T) = [帖子( C ,B,帖子( D ,A]

其中应用于向量的函数被定义为依次将函数应用于每个元素所产生的序列的串联。

现在考虑一下案例:

  • 如果A为空,则两个函数的输出都是空序列[]
  • 如果B为空,则两个函数的输出仅为[A]
  • 如果 C D 为空, pre(T) = [A,B]和 post(T)< / em> = [B,A]
  • 如果 C 为空, pre(T) = [A,B, D']和帖子(T ) = [B, D'',A](其中素数表示 D 中包含的节点的某些排列)
  • 如果 D 为空, pre(T) = [A,B, C']和帖子(T ) = [ C'',B,A]
  • 如果没有为空, pre(T) = [A,B, C' D']和帖子(T) = [ C'',B, D'',A]

在所有情况下,我们可以通过使用A和B(如果存在)作为分隔符,明确地将两个输出序列的成员划分为适当的子序列。

那么问题是,我们还可以对矢量序列进行分区吗?如果可以,那么每个都可以递归处理,我们就完成了。

由于 pre()的结果将始终是带有A节点的开始的序列链,以及 post()的结果将永远是一个带有A节点的结尾的序列链,我们确实可以将它们分开,提供 A节点永远不会为空。

对于具有固定子项的二进制(或实际上任何)树,可以独立地为空,这是进程失败的地方。然而,在我们的例子中,我们已经定义 C D 仅包含非空节点,因此保证重建可以正常工作。

嗯,我想是的,无论如何。显然这只是一个论点,而不是一个正式的证明!

答案 3 :(得分:1)

使用此限制创建一个二叉树,该二叉树至少有一个节点,该节点只有一个子节点(右侧或左侧,没有区别)。

现在,写下预订单和后序列表。然后尝试从这些列表重建树。并且你意识到在那个节点上你不能确定它的孩子是对还是左。

答案 4 :(得分:1)

正如其他人已经指出的那样,仅使用前后顺序遍历不能重建二叉树。单个子节点具有模糊的遍历,其无法识别它是左还是右孩子,例如考虑遵循预订和后序遍历: 预购:a,b 后序b,a

它可以生成以下两个树

a  \ / \   b b 根本不可能知道b是左或右孩子,而没有任何其他信息,如顺序遍历。

答案 5 :(得分:0)

假设节点具有唯一名称,前序和后序遍历足以重建树。创建算法的关键是理解

  

X是Y的祖先,如果X在前序中位于Y之前,则在后序中位于Y之后。

鉴于此,我们总能找到任何节点的所有后代。 X的后代总是紧跟在预订中的X之后,并且在后序中的X之前。因此,一旦我们知道我们对生成以X为根的子树感兴趣,我们就可以提取以X为根的子树的前序和后序遍历。这一直导致递归算法,一旦我们意识到X之后的节点必须是它最左边的孩子,如果它是后代的话。

还有一个基于堆栈的实现,它遍历预订节点,并在堆栈上保留任何候选节点作为下一个预订节点的直接父节点。对于每个预订节点,重复弹出堆栈外的所有节点,这些节点不是下一个预订节点的父节点。使该节点成为堆栈顶部节点的子节点,并将子节点推入堆栈。

答案 6 :(得分:0)

无法从预订和后序遍历构建一般二叉树(请参阅此内容)。但是如果知道二叉树是完整的,我们就可以构造没有歧义的树。让我们借助以下示例来理解这一点。

让我们考虑两个给定的数组为pre [] = {1,2,4,8,9,5,3,6,7}和post [] = {8,9,4,5,2, 6,7,3,1}; 在pre []中,最左边的元素是树的根。由于树已满且数组大小超过1. pre []中的1旁边的值必须是root的子级。所以我们知道1是根,2是小孩。如何在左子树中找到所有节点?我们知道2是左子树中所有节点的根。 post []中2之前的所有节点必须位于左子树中。现在我们知道1是根,元素{8,9,4,5,2}在左子树中,元素{6,7,3}在右子树中。

              1
            /   \
           /      \
 {8, 9, 4, 5, 2}     {6, 7, 3}

我们递归地遵循上述方法并得到以下树。

      1
    /   \
  2       3
/  \     /  \

4 5 6 7   / \
 8 9