从前序和后序列表重构树

考虑一下你有两个节点列表,你知道的是一个是某棵树的前序遍历的表示,另一个是同一棵树的后序遍历的表示。

我相信有可能从这两个列表中完全重构树,我想我有一个algorithm来做,但没有certificate它。 因为这将是一个硕士项目的一部分,我需要绝对肯定,这是可能的和正确的(mathcertificate)。 然而,这不是项目的重点,所以我想知道是否有一个源(即纸或书),我可以引用的证据。 (也许在TAOCP?有人可能知道该段?)

简而言之,我需要一个经过validation的algorithm,使用可引用的资源从前后遍历中重构树。


注意:有问题的树可能不是二进制的,或者是平衡的,或者任何会使它变得太简单的东西。

注2:只使用前序或后序列表会更好,但我不认为这是可能的。

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

注4:我只关心兄弟姐妹的顺序。 只有一个孩子的时候,左边或右边不重要。

你不能只使用一个列表,因为你不会感觉到树的深度。 因此,你肯定需要两个或更多的列表。

这是我的一个解决scheme的尝试:

使用前序遍历作为知道数据sorting的手段。 这是有道理的,因为您知道第一个节点是顶部,而且您知道遍历左侧的数据属于树的左边等。

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

  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的孩子。

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

编辑:这个解决scheme保存了孩子的顺序,只是由于通过前序遍历知道节点的顺序,然后通过后序遍历知道结构。

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

我认为你需要购买文件,但是…

这是一个书面certificate是一个解决scheme:

lehre/2007WS/fa-cse/tutorials/tutorial09-solutions.pdf

预购和后订单不要唯一定义一棵树。

一般来说,一次树遍历不会唯一地定义树的结构。 例如,正如我们所看到的,对于下面的两棵树,一个遍历产生[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]与上面第一棵树相匹配。

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

A,B, CD中的任何一个都可以是空的。 如果B是空的,那么必须是CD ; 如果A,那么一切。

由于节点是唯一的,包含在CD中任何地方的节点集合是不相交的,并且都不包含A或B.

函数pre()post()生成表单的有序序列:

pre(T) = [A,B, pre( Cpre( D ]

post(T) = [ post( C ,B, post( D ,A]

其中应用于vector的函数被定义为将函数依次应用于每个元素所得到的序列的连接。

现在考虑一下情况:

  • 如果A为空,则两个函数的输出都是空序列[]
  • 如果B是空的,那么这两个函数的输出只是[A]
  • 如果CD是空的, pre(T) = [A,B]和post(T) = [B,A]
  • 如果只有C是空的, pre(T) = [A,B, D' ]和post(T) = [B, D“ ,A](其中素数表示D中包含的节点的一些置换)
  • 如果只有D是空的,则pre(T) = [A,B, C' ]和post(T) = [ C'' ,B,A]
  • (T) = [A,B, C'D' ]和后(T) = [ C“ ,B, D” ,A]

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

那么问题是,我们是否也可以划分vector序列? 如果可以,那么每个都可以recursion处理,我们就完成了。

由于pre()的结果总是以A节点开始的序列链,而post()的结果将始终是以A节点结尾的序列链,所以我们确实可以将它们分开, 只要 A节点永远不会是空的

在二进制(或者甚至是任何)带有固定的孩子的树可以独立地为空的情况下,这个过程就会落在这里。 在我们的例子中,我们已经定义了CD只包含非空的节点,所以重构是有保证的。

呃,无论如何,我都这么认为。 显然这只是一个说法,不是一个正式的certificate!

创build一个具有至less一个节点的限制的二叉树,该节点只有一个子节点(右侧或左侧,没有区别)。

现在,写下它的预订和邮购列表。 然后尝试从这些列表重构树。 你意识到在那个节点上你不能决定它的孩子是正确的还是左边的。

正如其他人已经指出的,二叉树不能仅使用前后遍历来重构。 单个子节点具有不明确的遍历,不能识别是左边还是右边的孩子,例如考虑下面的前序和后序遍历:前序:a,b后序b,a

它可以生产以下两种树木

aa \ bb根本不可能知道b是a的左边还是右边的孩子,没有像inorder遍历那样的附加信息。

假定节点是唯一命名的,则前序遍历和后序遍历足以重构树。 创buildalgorithm的关键是理解这一点

X是Y的祖先,如果X在先序中Y先于后序中Y。

鉴于此,我们总是可以find任何节点的所有后代。 X的后代总是紧跟在X的前面,在后面的X之前。 因此,一旦我们知道我们有兴趣生成以X为根的子树,我们就可以提取以X为根的子树的前序遍历和后序遍历。一旦我们意识到X之后的节点必须是它的最左边的孩子,如果它是一个后裔的话。

还有一个基于堆栈的实现,它通过预定义节点进行迭代,并将堆栈中的任何节点保留为下一个预定义节点的直接父节点。 对于每个预订节点,重复popup堆栈中不是下一个预订节点的父节点的所有节点。 使该节点成为堆栈顶层节点的子节点,然后将子节点推入堆栈。

从前序遍历和后序遍历来构造一般的二叉树是不可能的(参见本文)。 但是如果知道二叉树是满的,我们就可以构造出没有歧义的树。 让我们通过下面的例子来理解这个。

让我们考虑两个给定的数组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是左边的孩子。 如何find左子树中的所有节点? 我们知道2是左子树中所有节点的根。 在post []之前的所有节点必须在左子树中。 现在我们知道1是根,元素{8,9,4,5,2}在左子树中,元素{6,7,3}在右子树中。

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

我们recursion地按照上面的方法得到下面的树。

  1 / \ 2 3 / \ / \ 

4 5 6 7 / \
8 9