如何在给定的两个二叉搜索树中找到最大的公共子树?

时间:2011-07-13 04:11:12

标签: algorithm tree binary-tree

给出了两个BSTs(二进制搜索树)。如何在给定的两个binary trees中找到最大的公共子树?

编辑1: 这就是我的想法:

设,r1 =第一棵树的当前节点      r2 =第二棵树的当前节点

There are some of the cases I think we need to consider:

Case 1 : r1.data < r2.data
     2 subproblems to solve:
     first, check r1 and r2.left 
     second, check r1.right and r2

Case 2 : r1.data > r2.data
     2 subproblems to solve:
       - first, check r1.left and r2
       - second, check r1 and r2.right

Case 3 : r1.data == r2.data
     Again, 2 cases to consider here:
     (a) current node is part of largest common BST
         compute common subtree size rooted at r1 and r2
     (b)current node is NOT part of largest common BST
         2 subproblems to solve:
         first, solve r1.left and r2.left 
         second, solve r1.right and r2.right

我可以想一想我们需要检查的案例,但截至目前我无法对其进行编码。这不是一个家庭作业问题。它看起来像吗?

4 个答案:

答案 0 :(得分:5)

只需散列每个节点的子节点和键,然后查找重复项。这将给出线性预期时间算法。例如,请参阅以下伪代码,它假定没有哈希冲突(处理冲突会很简单):

ret = -1
// T is a tree node, H is a hash set, and first is a boolean flag
hashTree(T, H, first):
  if (T is null):
    return 0 // leaf case
  h = hash(hashTree(T.left, H, first), hashTree(T.right, H, first), T.key)
  if (first):
    // store hashes of T1's nodes in the set H
    H.insert(h)
  else:
    // check for hashes of T2's nodes in the set H containing T1's nodes
    if H.contains(h):
      ret = max(ret, size(T)) // size is recursive and memoized to get O(n) total time
  return h

H = {}
hashTree(T1, H, true)
hashTree(T2, H, false)
return ret

请注意,这是假定BST子树的标准定义,即子树由节点及其所有后代组成。

答案 1 :(得分:3)

假设树中没有重复值:

LargestSubtree(Tree tree1, Tree tree2)
    Int bestMatch := 0
    Int bestMatchCount := 0
    For each Node n in tree1  //should iterate breadth-first
        //possible optimization:  we can skip every node that is part of each subtree we find
        Node n2 := BinarySearch(tree2(n.value))
        Int matchCount := CountMatches(n, n2)
        If (matchCount > bestMatchCount)
            bestMatch := n.value
            bestMatchCount := matchCount
        End
    End

    Return ExtractSubtree(BinarySearch(tree1(bestMatch)), BinarySearch(tree2(bestMatch)))
End

CountMatches(Node n1, Node n2)
    If (!n1 || !n2 || n1.value != n2.value)
        Return 0
    End
    Return 1 + CountMatches(n1.left, n2.left) + CountMatches(n1.right, n2.right)
End

ExtractSubtree(Node n1, Node n2)
    If (!n1 || !n2 || n1.value != n2.value)
        Return nil
    End

    Node result := New Node(n1.value)
    result.left := ExtractSubtree(n1.left, n2.left)
    result.right := ExtractSubtree(n1.right, n2.right)
    Return result
End

简要解释一下,这是解决问题的一种蛮力解决方案。它做了第一棵树的广度优先步行。对于每个节点,它执行第二个树的BinarySearch以定位该树中的相应节点。然后使用这些节点,它评估根植于那里的公共子树的总大小。如果子树大于以前找到的任何子树,则会在以后记住它,以便在算法完成时构造并返回最大子树的副本。

此算法不处理重复值。可以通过使用BinarySearch实现来扩展它,该实现返回具有给定值的所有节点的列表,而不是仅返回单个节点。然后算法可以迭代此列表并评估每个节点的子树,然后照常进行。

此算法的运行时间为O(n log m)(它遍历第一个树中的n个节点,并为每个节点执行log m二进制搜索操作),使其与标准值相同最常见的排序算法。运行时空间复杂度为O(1)(除了几个临时变量之外没有分配),而O(n)返回结果时(因为它创建了子树的显式副本,可能不需要,具体取决于究竟该算法应如何表达其结果)。因此,即使这种强力方法也应该表现得相当好,尽管正如其他答案所指出的那样,O(n)解决方案是可行的。

还可以对此算法应用可能的优化,例如跳过先前评估的子树中包含的任何节点。因为树行走是广度优先的,所以我们知道,作为某个先前子树的一部分的任何节点都不能成为更大子树的根。在某些情况下,这可以显着提高算法的性能,但最坏情况下的运行时间(没有共同子树的两棵树)仍然是O(n log m)

答案 2 :(得分:1)

答案 3 :(得分:1)

以下算法计算两个二叉树的所有最大公共子树(不假设它是二叉搜索树)。设S和T为两个二叉树。该算法从树的底部起作用,从树叶开始。我们首先确定具有相同值的叶子。然后考虑他们的父母并识别具有相同孩子的节点。更一般地,在每次迭代时,我们识别节点,只要它们具有相同的值并且它们的子节点是同构的(或者在交换左右儿童之后是同构的)。该算法终止于T和S中所有最大子树对的集合。

以下是更详细的说明:

让S和T成为两个二叉树。为简单起见,我们可以假设对于每个节点n,左子节点具有值&lt; =右子节点。如果节点n中只有一个子节点为NULL,则假定右节点为NULL。 (一般来说,我们认为两个子树是同构的,如果它们取决于每个节点的左/右孩子的排列。)

(1)查找每棵树中的所有叶节点。

(2)定义一个二分图B,其边缘从S中的节点到T中的节点,最初没有边。设R(S)和T(S)为空集。设R(S)_next和R(T)_next也是空集。

(3)对于S中的每个叶节点和T中的每个叶节点,如果节点具有相同的值,则在B中创建边。对于从S中的nodeS到T中的nodeT创建的每个边,将nodeS的所有父节点添加到集合R(S),并将nodeT的所有父节点添加到集合R(T)。

(4)对于R(S)中的每个节点nodeS和T(S)中的每个节点nodeT,如果它们具有相同的值,则在B中绘制它们之间的边缘 {    (i):nodeS-&gt; left连接到nodeT-&gt; left和nodeS-&gt; right连接到nodeT-&gt; right,OR    (ii):nodeS-&gt; left连接到nodeT-&gt; right和nodeS-&gt; right连接到nodeT-&gt; left,OR    (iii):nodeS-&gt; left连接到nodeT-&gt; right和nodeS-&gt; right == NULL和nodeT-&gt; right == NULL

(5)对于步骤(4)中创建的每条边,将其父节点添加到R(S)_next和R(T)_next。

(6)如果(R(S)_next)是非空的{    (i)交换R(S)和R(S)_next并交换R(T)和R(T)_next。
   (ii)清空R(S)_next和R(T)_next的内容    (iii)返回步骤(4)。    }

当此算法终止时,R(S)和T(S)包含S和T中所有最大子树的根。此外,二分图B标识S中的所有节点对和T中的节点,它们给出同构子树。

我相信这个算法的复杂度是O(n log n),其中n是S和T中节点的总数,因为集合R(S)和T(S)可以按值排序存储在BST中但是我有兴趣看到证据。