考虑未标记的,有根的二叉树。我们可以压缩这些树:每当有指向子树和与(解释为结构相等),我们店(wlog)并更换所有指针与指针。有关示例,请参见uli的答案。
给出一种将上述意义上的树作为输入并计算压缩后剩余的(最小)节点数的算法。该算法应在时间(在统一成本模型中)运行,输入中的节点数为。
这是一个考试问题,我无法提出一个很好的解决方案,也没有看到一个解决方案。
考虑未标记的,有根的二叉树。我们可以压缩这些树:每当有指向子树和与(解释为结构相等),我们店(wlog)并更换所有指针与指针。有关示例,请参见uli的答案。
给出一种将上述意义上的树作为输入并计算压缩后剩余的(最小)节点数的算法。该算法应在时间(在统一成本模型中)运行,输入中的节点数为。
这是一个考试问题,我无法提出一个很好的解决方案,也没有看到一个解决方案。
Answers:
是的,您可以在时间内执行此压缩,但这并不容易:)我们首先进行观察,然后介绍算法。我们假定树最初没有被压缩-并不是真正需要的,但是使分析更加容易。
首先,我们归纳地描述“结构平等”。令和T '是两个(子)树。如果T和T都是空树(根本没有顶点),则它们在结构上是等效的。如果 T和 T '都不是空树,则它们在结构上等效,前提是它们的左子级在结构上等效,而右子级在结构上等效。“结构对等”是这些定义上的最小固定点。
例如,任何两个叶节点在结构上都是等效的,因为它们的两个子节点都具有空树,它们在结构上是等效的。
说“他们的左孩子在结构上是等同的,而他们的右孩子在结构上是相当恼人的”,我们经常会说“他们的孩子在结构上是等效的”,并且打算相同。还要注意,当我们指的是“根于此顶点的子树”时,有时会说“此顶点”。
上面的定义立即为我们提供了如何执行压缩的提示:如果我们知道深度最大为的所有子树的结构等效性,那么我们可以轻松地计算深度为d + 1的子树的结构等效性。我们必须以一种聪明的方式来执行此计算,以避免O (n 2)运行时间。
该算法将在执行过程中为每个顶点分配标识符。的标识符是在所述组中的数目。标识符是唯一的,并且永不改变:因此,我们假设在算法开始时将一些(全局)变量设置为1,并且每次将标识符分配给某个顶点时,我们都会将该变量的当前值分配给顶点并递增该变量的值。
我们首先将输入树转换为(最多)包含等深度顶点的列表以及指向其父级的指针的列表。这很容易在时间内完成。
首先,将所有叶子(可以在列表中找到深度为0的顶点的叶子)压缩为单个顶点。我们为该顶点分配一个标识符。通过将任一顶点的父级重定向为指向另一顶点来完成两个顶点的压缩。
我们有两个观察结果:首先,任何顶点的子深度都严格较小;其次,如果我们对深度小于的所有顶点执行了压缩, (并指定了标识符),则深度 d的两个顶点在结构上是等效的,并且如果他们的孩子的标识符一致,则可以压缩。最后一个观察结果来自于以下论点:两个顶点在结构上相等,如果它们的子对象在结构上相等,则经过压缩后,这意味着它们的指针指向相同的孩子,这又意味着它们的孩子的标识符相等。
我们遍历所有具有相同深度的节点(从小深度到大深度)的列表。对于每个级别,我们创建一个整数对列表,其中每个对都对应于该级别上某个顶点的子代的标识符。如果该层中的两个顶点在它们对应的整数对相等的情况下在结构上相等,则它们是相等的。使用字典顺序,我们可以对它们进行排序,并获得相等的整数对集合。如上所述,我们将这些集合压缩为单个顶点,并为其指定标识符。
以上观察证明,这种方法行之有效,并产生了压缩树。总运行时间为加上对我们创建的列表进行排序所需的时间。由于我们创建的整数对的总数为n,因此根据需要,总运行时间为O (n log n )。计算过程结束时剩下的节点数是微不足道的(只需看看我们已经分发了多少个标识符)。
degree
”学位应该是depth
?尽管CS树向下生长,但我发现“一棵树的高度”比“一棵树的深度”更容易混淆。
压缩非可变数据结构,使其不重复任何结构上相等的子项,这称为哈希精简。这是函数编程中内存管理中的一项重要技术。哈希精简是一种针对数据结构的系统性备忘。
我们将对哈希树进行哈希约束,并在哈希约束后对节点进行计数。哈希大小为的数据结构总是可以在O (n运算;最后计算节点数与节点数成线性关系。
我将认为树具有以下结构(在此处以Haskell语法编写):
data Tree = Leaf
| Node Tree Tree
对于每个构造函数,我们需要维护一个从其可能的参数到将构造函数应用于这些参数的结果的映射。叶子是微不足道的。对于节点,我们维护一个有限的局部映射其中T是树标识符的集合,N是节点标识符的集合;Ť = Ñ ⊎ { ℓ }其中ℓ是唯一叶子识别符。(具体来说,标识符是指向存储块的指针。)
我们可以将对数时间数据结构用于nodes
,例如平衡的二进制搜索树。下面,我将调用lookup nodes
在nodes
数据结构中查找键insert nodes
的操作,以及在新键下添加值并返回该键的操作。
现在,我们遍历树并添加节点。尽管我正在使用类似Haskell的伪代码进行编写,但是我会将其nodes
视为全局可变变量。我们只会添加它,但是插入需要贯穿整个线程。该add
函数在树上递归,将其子树添加到nodes
地图中,并返回根的标识符。
insert (p1,p2) =
add Leaf = $\ell$
add (Node t1 t2) =
let p1 = add t1
let p2 = add t2
case lookup nodes (p1,p2) of
Nothing -> insert nodes (p1,p2)
Just p -> p
insert
调用数(也是nodes
数据结构的最终大小)是最大压缩后的节点数。(如果需要,为空树添加一个。)
nodes
insert
并且add
应该明确,并提供一个实际解决问题的功能,恕我直言。
nodes
为了方便起见,我创建了一个可变变量,但您可以将其贯穿整个线程。我不会提供完整的代码,这不是SO。
这是另一个想法,旨在(以内射)将树的结构编码为数字,而不是随意地对其进行标记。为此,我们认为任何数字的素数分解都是唯一的。
为了我们的目的,让表示树中的空位置,让N (l ,r )表示具有左子树l和右子树r的节点。N (E ,E )将是一片叶子。现在,让
使用,我们可以计算树自下而上包含的所有子树的集合;在每个节点中,我们合并从子级获得的编码集,并添加一个新的数字(可以从子级编码中恒定时间计算得出)。
最后一个假设是在真实机器上的延伸;在这种情况下,我们宁愿使用类似于Cantor配对功能的方式,而不是取幂。
此算法的运行时间取决于树的结构(在平衡树上,具有任何允许线性时间并集的实现方式)。对于一般树,我们需要对数时间并集并进行简单分析。不过,也许进行复杂的分析会有所帮助。请注意,通常的最坏情况树(线性列表)在此处允许O(n log n )时间,因此尚不清楚最坏情况可能是什么。
编辑:我读到这个问题,因为T和T'是同一个父母的孩子。我也将压缩的定义定义为递归的,这意味着您可以压缩两个先前压缩的子树。如果这不是实际问题,那么我的答案可能无效。
def Comp(T):
if T == null:
return 0
leftCount = Comp(T.left)
rightCount = Comp(T.right)
if leftCount == rightCount:
if hasSameStructure(T.left, T.right):
T.right = T.left
return leftCount + 1
else
return leftCount + rightCount + 1
哪里 hasSameStructure()
的功能是在线性时间内比较两个已压缩的子树,以查看它们是否具有完全相同的结构。编写一个线性时间递归函数,该函数遍历每个递归函数,并在每次其他子树都这样时检查一个子树是否有左孩子,这并不难。