有效压缩未标记的树木


20

考虑未标记的,有根的二叉树。我们可以压缩这些树:每当有指向子树ŤŤŤ=Ť(解释=为结构相等),我们店(wlog)Ť并更换所有指针Ť与指针Ť。有关示例,请参见uli的答案

给出一种将上述意义上的树作为输入并计算压缩后剩余的(最小)节点数的算法。该算法应在时间Øñ日志ñ(在统一成本模型中)运行,输入中的节点数为ñ

这是一个考试问题,我无法提出一个很好的解决方案,也没有看到一个解决方案。


这里的基本操作是什么“成本”,“时间”?访问的节点数?遍历了多少条边?以及输入的大小如何指定?
乌里2012年

该树压缩是哈希consing的一个实例。不知道这是否会导致通用计数方法。
吉尔(Gilles)'所以

@uli我澄清了是什么。我认为“时间”足够具体。在非并行设置中,这等效于计数操作,在Landau术语中等效于计数最常发生的基本操作。ñ
拉斐尔

@Raphael当然,我可以猜测一下预期的基本操作应该是什么,并且可能会和其他人一样。但是,我知道我在这里很书呆子,每当给出“时限”时,重要的是要陈述正在计算的内容。它是交换,比较,添加,内存访问,检查的节点,遍历的边,就是您的名字。这就像在物理上省略了测量单位。是 1010ķG?而且我认为内存访问几乎总是最频繁的操作。10s
乌里2012年

@uli这些是“统一成本模型”应该传达的细节。精确定义什么是基本操作是很痛苦的,但是在99.99%的情况下(包括该操作),没有歧义。复杂度类从根本上没有单位,它们不衡量执行一个实例所花费的时间,但是这种时间随着输入的增加而变化的方式。
吉尔(Gilles)'“ SO-不要邪恶”

Answers:


10

是的,您可以在时间内执行此压缩,但这并不容易:)我们首先进行观察,然后介绍算法。我们假定树最初没有被压缩-并不是真正需要的,但是使分析更加容易。Øñ日志ñ

首先,我们归纳地描述“结构平等”。令T '是两个(子)树。如果TTŤŤŤ都是空树(根本没有顶点),则它们在结构上是等效的。如果 T T '都不是空树,则它们在结构上等效,前提是它们的左子级在结构上等效,而右子级在结构上等效。“结构对等”是这些定义上的最小固定点。ŤŤŤ

例如,任何两个叶节点在结构上都是等效的,因为它们的两个子节点都具有空树,它们在结构上是等效的。

说“他们的左孩子在结构上是等同的,而他们的右孩子在结构上是相当恼人的”,我们经常会说“他们的孩子在结构上是等效的”,并且打算相同。还要注意,当我们指的是“根于此顶点的子树”时,有时会说“此顶点”。

上面的定义立即为我们提供了如何执行压缩的提示:如果我们知道深度最大为的所有子树的结构等效性,那么我们可以轻松地计算深度为d + 1的子树的结构等效性。我们必须以一种聪明的方式来执行此计算,以避免O n 2运行时间。dd+1个Øñ2

该算法将在执行过程中为每个顶点分配标识符。的标识符是在所述组中的数目。标识符是唯一的,并且永不改变:因此,我们假设在算法开始时将一些(全局)变量设置为1,并且每次将标识符分配给某个顶点时,我们都会将该变量的当前值分配给顶点并递增该变量的值。{1个23ñ}

我们首先将输入树转换为(最多ñ)包含等深度顶点的列表以及指向其父级的指针的列表。这很容易在时间内完成。Øñ

首先,将所有叶子(可以在列表中找到深度为0的顶点的叶子)压缩为单个顶点。我们为该顶点分配一个标识符。通过将任一顶点的父级重定向为指向另一顶点来完成两个顶点的压缩。

我们有两个观察结果:首先,任何顶点的子深度都严格较小;其次,如果我们对深度小于的所有顶点执行了压缩, (并指定了标识符),则深度 d的两个顶点在结构上是等效的,并且如果他们的孩子的标识符一致,则可以压缩。最后一个观察结果来自于以下论点:两个顶点在结构上相等,如果它们的子对象在结构上相等,则经过压缩后,这意味着它们的指针指向相同的孩子,这又意味着它们的孩子的标识符相等。dd

我们遍历所有具有相同深度的节点(从小深度到大深度)的列表。对于每个级别,我们创建一个整数对列表,其中每个对都对应于该级别上某个顶点的子代的标识符。如果该层中的两个顶点在它们对应的整数对相等的情况下在结构上相等,则它们是相等的。使用字典顺序,我们可以对它们进行排序,并获得相等的整数对集合。如上所述,我们将这些集合压缩为单个顶点,并为其指定标识符。

以上观察证明,这种方法行之有效,并产生了压缩树。总运行时间为加上对我们创建的列表进行排序所需的时间。由于我们创建的整数对的总数为n,因此根据需要,总运行时间为O n log n 。计算过程结束时剩下的节点数是微不足道的(只需看看我们已经分发了多少个标识符)。ØññØñ日志ñ


我尚未详细阅读您的答案,但我认为您或多或少地重新发明了哈希约束,采用了一种针对问题的怪异方式来查找节点。
吉尔(Gilles)'所以

@Alex“严格较小的孩子degree”学位应该是depth?尽管CS树向下生长,但我发现“一棵树的高度”比“一棵树的深度”更容易混淆。
乌里2012年

好答案。我觉得应该有一种绕过排序的方法。我对@Gilles答案的第二条评论在这里也有效。
拉斐尔

@uli:是的,您是对的,我已经更正了(不确定我为什么混淆这两个词)。高度和深度是两个微妙的不同概念,我需要后者:)我认为我会坚持常规的“深度”,而不是通过交换它们来混淆所有人。
亚历克斯十布林克

4

压缩非可变数据结构,使其不重复任何结构上相等的子项,这称为哈希精简。这是函数编程中内存管理中的一项重要技术。哈希精简是一种针对数据结构的系统性备忘。

我们将对哈希树进行哈希约束,并在哈希约束后对节点进行计数。哈希大小为的数据结构总是可以在O nn运算;最后计算节点数与节点数成线性关系。O(nlg(n))

我将认为树具有以下结构(在此处以Haskell语法编写):

data Tree = Leaf
          | Node Tree Tree

对于每个构造函数,我们需要维护一个从其可能的参数到将构造函数应用于这些参数的结果的映射。叶子是微不足道的。对于节点,我们维护一个有限的局部映射其中T是树标识符的集合,N是节点标识符的集合;Ť = Ñ { }其中nodes:T×TNTNT=N{}是唯一叶子识别符。(具体来说,标识符是指向存储块的指针。)

我们可以将对数时间数据结构用于nodes,例如平衡的二进制搜索树。下面,我将调用lookup nodesnodes数据结构中查找键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数据结构的最终大小)是最大压缩后的节点数。(如果需要,为空树添加一个。)


您是否可以为“散列始终可以在O n l g n )个操作中完成大小为的数据结构”提供参考?请注意,您将需要平衡树以实现所需的运行时。nO(nlg(n))nodes
拉斐尔

我只是考虑以结构化的方式将子结构散列为数字,以便独立计算同一棵树的散列始终会产生相同的结果。只要我们手上有可变的数据结构,您的解决方案也很好。我认为可以将其清理干净。交织,insert并且add应该明确,并提供一个实际解决问题的功能,恕我直言。
拉斐尔

1
@Raphael Hash consing依赖于指针/标识符的元组上的有限映射结构,您可以使用对数时间进行查找和添加(例如,使用平衡的二进制搜索树)。我的解决方案不需要可变性;nodes为了方便起见,我创建了一个可变变量,但您可以将其贯穿整个线程。我不会提供完整的代码,这不是SO。
吉尔斯(Gillles)“所以-别再邪恶了”

1
@Raphael Hashing结构与为它们分配任意数字相反,有点儿狡猾。在统一成本模型中,您可以将任何内容编码为一个大整数并对其执行恒定时间操作,这是不现实的。在现实世界中,您可以使用加密哈希从无限集到有限范围的整数进行事实上的一对一映射,但是它们很慢。如果使用非加密校验和作为哈希,则需要考虑冲突。
吉尔斯(Gillles)“所以-别再作恶了”

3

这是另一个想法,旨在(以内射)将树的结构编码为数字,而不是随意地对其进行标记。为此,我们认为任何数字的素数分解都是唯一的。

为了我们的目的,让表示树中的空位置,让N l r 表示具有左子树l和右子树r的节点。N E E 将是一片叶子。现在,让Ëñ[R[RñËË

FË=0Fñ[R=2F3F[R

使用F,我们可以计算树自下而上包含的所有子树的集合;在每个节点中,我们合并从子级获得的编码集,并添加一个新的数字(可以从子级编码中恒定时间计算得出)。

最后一个假设是在真实机器上的延伸;在这种情况下,我们宁愿使用类似于Cantor配对功能的方式,而不是取幂。

此算法的运行时间取决于树的结构(在平衡树上,具有任何允许线性时间并集的实现方式)。对于一般树,我们需要对数时间并集并进行简单分析。不过,也许进行复杂的分析会有所帮助。请注意,通常的最坏情况树(线性列表在此处允许On log n 时间,因此尚不清楚最坏情况可能是什么。Øñ日志ñØñ日志ñ


1

由于评论中不允许使用图片:

在此处输入图片说明

左上:输入树

右上方:以节点5和7为根的子树也是同构的。

左下和右下:压缩树不是唯一定义的。

请注意,在这种情况下,树的大小从6 + | T | 7+5|Ť|6+|Ť|


的确,这确实是所需操作的一个示例。请注意,如果您不区分原始引用和添加引用,则最终示例是相同的。
拉斐尔

-1

编辑:我读到这个问题,因为T和T'是同一个父母的孩子。我也将压缩的定义定义为递归的,这意味着您可以压缩两个先前压缩的子树。如果这不是实际问题,那么我的答案可能无效。

Øñ日志ñŤñ=2Ťñ/2+Cñ

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()的功能是在线性时间内比较两个已压缩的子树,以查看它们是否具有完全相同的结构。编写一个线性时间递归函数,该函数遍历每个递归函数,并在每次其他子树都这样时检查一个子树是否有左孩子,这并不难。

ññ[R

Ťñ=Ťñ1个+Ťñ2+Ø1个 如果 ññ[R
2Ťñ/2+Øñ 除此以外

如果子树不是兄弟姐妹怎么办?注意((T1,T1),(T2,T1))T1可以通过使用指针两次来保存两次,第三次出现。
乌里2012年

ŤŤ是同一父母的孩子。如果这不是实际问题,那么我的答案可能无效。我也将压缩的定义定义为递归的,这意味着您可以压缩两个先前压缩的子树。
2012年

这个问题很夸张地说,两个子标题被确定为同构。没有任何关于他们有相同父母的事情。如果子树T1在树中出现3次,如我的上一个示例((T1,T1),(T1,T2)),则可以通过指向第三次出现来压缩两次出现。
乌里2012年
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.