我相信我有一个O(n+m)时间复杂度,O(n+m)空间复杂度的算法来解决这个问题,假设树的大小分别为n和m。该算法假设树中的值是唯一的(即每个元素最多在每棵树中出现一次),但它们不需要是二叉搜索树。
该算法基于动态规划,并具有以下直觉:假设我们有一棵根为r,子树为T1和T2的树T。假设另一棵树为S。现在,假设我们知道T1和S的最大公共子树以及T2和S的最大公共子树。那么T和S的最大子树
- 完全包含在T1和r中。
- 完全包含在T2和r中。
- 同时使用T1、T2和r。
因此,我们可以按照以下方式计算最大公共子树(简称 MCS)。如果 MCS(T1, S) 或 MCS(T2, S) 其中一个的根是 T1 或 T2 的根,则从 T 和 S 中得到的 MCS 是由 MCS(T1, S) 和 MCS(T2, S) 中较大的那个给出的。如果仅有 MCS(T1, S) 和 MCS(T2, S) 中的一个具有 T1 或 T2 的根作为根(假设 w.l.o.g. 是 T1),则在 S 中查找 r。如果 r 有 T1 的根作为子节点,则我们可以通过添加一个节点来扩展该树,并且 MCS 给出的是这个增强树和 MCS(T2, S) 中较大的那个。否则,如果 MCS(T1, S) 和 MCS(T2, S) 都具有 T1 和 T2 的根作为根,则在 S 中查找 r。如果它有 T1 的根作为子节点,我们可以通过添加 r 来扩展树。如果它有 T2 的根作为子节点,我们可以通过添加 r 来扩展该树。否则,我们只需取 MCS(T1, S) 和 MCS(T2, S) 中较大的那个。
算法的正式版本如下:
- 创建一个新的哈希表,将树S中的节点从其值映射到树中相应的节点。然后通过标准的树遍历在O(m)时间内填充此表与S的节点。
- 创建一个新的哈希表,将T中的节点从它们的值映射到以该节点为根的树和S的最大公共子树的大小。请注意,这意味着存储在此表中的MCS必须直接根据给定节点进行。保留此表为空。
- 使用后序遍历创建T的节点列表。这需要O(n)时间。请注意,这意味着我们将始终在处理节点本身之前处理所有节点的子节点;这非常重要!
- 对于后序遍历中按访问顺序的每个节点v:
- 查找S节点哈希表中相应的节点。
- 如果未找到节点,则将以v为根的MCS的大小设置为0。
- 如果在S中找到了节点v':
- 如果v'的任何一个子节点都不匹配v的子节点,则将以v为根的MCS的大小设置为1。
- 如果v'的一个子节点恰好匹配v的一个子节点,则将以v为根的MCS的大小设置为1加上以该子节点为根的子树的MCS的大小。
- 如果v'的两个子节点都匹配v的子节点,则将以v为根的MCS的大小设置为1加上左子树和右子树的MCS的大小。
- (请注意,步骤(4)的运行时间预期为O(n),因为它仅访问S中的每个节点一次,进行O(n)哈希表查找,进行n个哈希表插入,并对每个节点进行恒定数量的处理)。
- 遍历哈希表并返回其包含的最大值。此步骤也需要O(n)时间。如果哈希表为空(S的大小为零),则返回0。
总的来说,运行时间预期为O(n + m),两个哈希表的空间也是O(n + m)。
为了证明正确性,我们通过对树T的高度进行归纳来进行。作为基本情况,如果T的高度为零,则我们只返回零,因为(4)中的循环不会向哈希表中添加任何内容。如果T的高度为一,则它存在于T中或不存在于T中。如果它存在于T中,则它不能有任何子节点,因此我们执行分支4.3.1并说它的高度为一。然后步骤(6)报告MCS的大小为一,这是正确的。如果它不存在,则我们执行4.2,将零放入哈希表中,因此步骤(6)报告MCS的大小为零,正如预期的那样。
在归纳步骤中,假设该算法适用于所有高度为k'
哇! 这是一个很棒的问题。 我希望这个解决方案是正确的!
编辑:正如@jonderry所指出的那样,这将找到两棵树的最大公共子图,而不是最大公共完整子树。但是,您可以很容易地限制算法仅在子树上工作。为此,您将修改算法的内部代码,以便仅在两个子树都不存在非零大小的情况下记录大小为0的子树。类似的归纳论证将表明,这将找到最大的完整子树。
尽管如此,我确实更喜欢“最大公共子图”问题。 :-)