使用预订和有序字符串确定二叉树是否是另一个二叉树的子树

时间:2013-05-05 04:06:27

标签: algorithm tree binary-tree

我想知道二叉树T2是否是二叉树T1的子树。我读到可以使用预订和有序遍历为T2和T1构建字符串表示,如果T2字符串是T1字符串的子字符串,则T2是T1的子树。

我对这种方法感到有点困惑,不确定它的正确性。

来自wiki:“树的子树T是由T中的节点及其在T中的所有后代组成的树。”

在以下示例中:

T2:
  1
 / \
2   3

T1:
  1
 / \
2   3
     \
      4

如果我们为T2和T1构建字符串:

预购T2:“1,2,3”
预购T1:“1,2,3,4”
T2:“2,1,3” inorder T1:“2,1,3,4”

T2字符串是T1的子字符串,因此使用上述子字符串匹配方法,我们应该得出结论T2是T1的子树。

但是,根据定义,T2不应该是T1的子树,因为它没有T1根节点的所有后代。

有一个相关的讨论here,似乎总结出这个方法是正确的。

我在这里错过了什么吗?

6 个答案:

答案 0 :(得分:8)

非常有趣的问题。你似乎是正确的。我想你提到的问题是由math (graph theory)和计算机科学中不同的子树定义引起的。在图论中,T2是T1的适当子树。

答案 1 :(得分:7)

假设你从“Cracking the Coding Interview”这本书中得到了这一点,作者还提到要区分具有相同值的节点,还应该打印出空值。

这也可以解决你对子树定义的问题(正如书中所描述的那样正确)

预购T2:“1,2,null,null,3,null,null” 预订T1:“1,2,null,null,3,null,4,null,null” inorder T2:“null,2,null,1,null,3,null” inorder T1:“null,2,null,1,null,3,null,4,null”

如您所见,T2不是T1的子树

答案 2 :(得分:0)

树的子树的定义应该与字符串的子串的定义相同。可以这样想: 1.子字符串具有开始,包含和结束。 2.树也应该具有相同的定义,但是通用化以适应树数据结构。 3.泛化是从1维的字符串到2维的树。

答案 3 :(得分:0)

我正在阅读同一本书,并对其解决方案表示怀疑。我提出了另一个反例,它不属于用户icepack提到的潜在解释(不一定需要所有后代的子树)。

考虑以下树

T2:
  B
 / \
A   C

T1:
    C
   / \
  B   C
 /
A

预购T2:'BAC'
预购T1:'CBAC'
T2:'ABC' inorder T1:'ABCC'

同样,T2字符串是其T1对应物的子串,但T2绝不是T1的子树。也许在作者已经排除重复,并特别提到他对子树的定义它可能是正确的,但遗漏这些信息使我们别无选择,只能认为它是一个不正确的解决方案。

答案 4 :(得分:0)


首先

问题也出在书<Cracking the coding interview 6th>的{​​{1}}部分| IX Interview Questions | 4. Trees and graphs

(这是Google的一位软件工程师Question 4.10写的一本好书,他采访了很多人。)


算法

  • (A)搜索根和匹配子树算法。

    复杂度:

    • 时间

      • Gayle Laakmann McDowell
        最糟糕的是,很少发生。
      • O(n + m*k)
        平均,可以小于O(n2 + m2*k2) (来自其他算法),但要视情况而定。
    • 空格:O(n + m)
      (主要由递归调用的方法堆栈使用。)

    位置:

    • O(lg(m) + lg(n))
      它是大树的大小。
    • n
      这是小树的大小。
    • m
      小树的根出现在大树中的情况。
    • k
      在找到子树之前,在较大的树中搜索节点的次数。
      n2之间。
    • [1, n]
      找到根后匹配的平均计数。
      对于随机输入,该值应该很小。
    • m2
      在找到子树之前搜索根的出现。
  • (B)将两棵树分别遍历到列表,并找到子列表。

    复杂度:

    • 时间:k2

    • 空格:O(n + m)

    位置:

    • O(n + m))
      它是大树的大小。
    • n
      这是小树的大小。

    提示

    • 应使用m遍历,还需要将null节点作为特殊值放入列表中。
      否则,不同的树可能会输出相同的列表。
    • pre-order遍历不起作用。
      因为它可能会为包含相同元素的树以不同顺序输出相同的有序列表,而这些树包含不同顺序的元素,即使空节点用特殊值表示。

比较2种算法:

  • 使用更少的内存,因此对于非常大的输入具有更大的可伸缩性。
  • 不确定A的速度,但平均而言,它仍可能比B快。
  • B的算法更简单。

代码

(以下是in-order中的一种实现,其中包含两者算法和测试用例。)

CheckSubtree.java

Java

CheckSubtreeTest.java
(单元测试,通过import java.util.Collections; import java.util.LinkedList; import java.util.List; /** * Given 2 binary tree T1 & T2, they are very large, and T1 is much larger than T2. * <p>Check whether T2 is a subtree of T1, * * @author eric * @date 2/9/19 1:48 PM */ public class CheckSubtree { /** * Check whether small tree is subtree of large tree, via searching root & match. * <p>Return on first match. * * @param largeTree large tree, * @param smallTree small tree, * @param <T> * @return true if small tree is subtree of large tree, or small tree is empty, */ public static <T extends Comparable> boolean checkViaRootAndMatch(BST<T> largeTree, BST<T> smallTree) { if (smallTree.size() == 0) return true; // small tree is empty, BST.BSTNode<T> matchNode = searchAndMatch(largeTree.getRoot(), smallTree); // search root & try match, return matchNode != null; } /** * Search root, and check whether the subtree there match small tree. * * @param largeCurrent subtree of large tree, * @param smallTree small tree, * @param <T> * @return node from large tree that match small tree, or null if not found, */ protected static <T extends Comparable> BST.BSTNode<T> searchAndMatch(BST.BSTNode<T> largeCurrent, BST<T> smallTree) { if (largeCurrent == null) return null; // subtree of large is empty, T smallRoot = smallTree.getRoot().getValue(); // value of small tree's root, if (largeCurrent.getValue().compareTo(smallRoot) == 0) { // found root of small tree, if (match(largeCurrent, smallTree)) return largeCurrent; // also match the whole small tree, } BST.BSTNode<T> leftFoundNode = searchAndMatch(largeCurrent.getLeft(), smallTree); // search in left subtree, if (leftFoundNode != null) return leftFoundNode; // match in left subtree of current, return searchAndMatch(largeCurrent.getRight(), smallTree); // search in right subtree, } /** * Check whether small tree match at given subtree of large tree. * * @param largeCurrent subtree of large tree, * @param smallTree small tree, * @param <T> * @return */ protected static <T extends Comparable> boolean match(BST.BSTNode<T> largeCurrent, BST<T> smallTree) { return match(largeCurrent, smallTree.getRoot()); } /** * Check whether subtree of small tree match at given subtree of large tree. * * @param largeCurrent subtree of large tree, * @param smallCurrent subtree of small tree, * @param <T> * @return true if subtree of small is subtree of large, or subtree of small is empty, */ protected static <T extends Comparable> boolean match(BST.BSTNode<T> largeCurrent, BST.BSTNode<T> smallCurrent) { if (smallCurrent == null) return true; // smaller reach leaf, if (largeCurrent == null) return false; // larger is empty, while smaller is not, if (largeCurrent.getValue().compareTo(smallCurrent.getValue()) != 0) return false; // current value is different, if (!match(largeCurrent.getLeft(), smallCurrent.getLeft())) return false; // check whether left subtree match, return match(largeCurrent.getRight(), smallCurrent.getRight()); // check whether right subtree match, } // traverse both tree and generate a list representation, then check whether the small list is sub list of large list, /** * Check whether small tree is subtree of large tree, via traverse tree to list & find sublist. Use given null value. * <p>Return on first match. * * @param largeTree * @param smallTree * @param <T> * @return */ public static <T extends Comparable> boolean checkViaTraverseAndSublist(BST<T> largeTree, BST<T> smallTree) { return checkViaTraverseAndSublist(largeTree, smallTree, null); } /** * Check whether small tree is subtree of large tree, via traverse tree to list & find sublist. Use given special value. * <p>Return on first match. * * @param largeTree * @param smallTree * @param special special value to represent value of null node in tree, * @param <T> * @return */ public static <T extends Comparable> boolean checkViaTraverseAndSublist(BST<T> largeTree, BST<T> smallTree, T special) { if (smallTree.size() == 0) return true; // small tree is empty, // tree to list, List<T> largeList = treeToList(largeTree, special); List<T> smallList = treeToList(smallTree, special); // System.out.printf("large list: %s\n", largeList); // System.out.printf("small list: %s\n", smallList); int idx = Collections.lastIndexOfSubList(largeList, smallList); // find sublist, return idx >= 0; } /** * Traverse tree and add nodes to list, with pre-order, use special value to represent null node. * * @param tree * @param special special value to represent value of null node in tree, * @param <T> * @return */ protected static <T extends Comparable> List<T> treeToList(BST<T> tree, T special) { List<T> list = new LinkedList<>(); treeToList(tree.getRoot(), special, list); return list; } /** * Traverse subtree and add nodes to list, with pre-order, use special value to represent null node. * * @param current * @param special special value to represent value of null node in tree, * @param list * @param <T> */ protected static <T extends Comparable> void treeToList(BST.BSTNode<T> current, T special, List<T> list) { if (current == null) { list.add(special); // represent null with special value, return; } list.add(current.getValue()); // current, treeToList(current.getLeft(), special, list); // left subtree, treeToList(current.getRight(), special, list); // right subtree, } }

TestNG

所有测试用例都会通过。

提示:

  • import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; /** * CheckSubtree test. * * @author eric * @date 2/9/19 4:18 PM */ public class CheckSubtreeTest { private int n = 10; // trees, via minimal BST, private BST<Integer> largeTree; // large tree, private BST<Integer> smallTree; // small tree, subtree of large tree, private BST<Integer> smallTree2; // small tree, not subtree of large tree, private BST<Integer> emptyTree; // empty tree, @BeforeMethod public void init() { // init - large tree, largeTree = CreateMinimalBST.createRangeNum(0, n); // init - small tree, smallTree = CreateMinimalBST.createRangeNum(8, 10); smallTree2 = CreateMinimalBST.createRangeNum(2, 5); // init empty BST, emptyTree = new BST<>(); } // checkViaRootAndMatch(), @Test public void testViaRootAndMatch() { Assert.assertTrue(CheckSubtree.checkViaRootAndMatch(largeTree, smallTree)); // subtree, Assert.assertFalse(CheckSubtree.checkViaRootAndMatch(largeTree, smallTree2)); // not subtree, Assert.assertFalse(CheckSubtree.checkViaRootAndMatch(smallTree, largeTree)); // not subtree, // empty tree, Assert.assertTrue(CheckSubtree.checkViaRootAndMatch(largeTree, emptyTree)); Assert.assertTrue(CheckSubtree.checkViaRootAndMatch(smallTree, emptyTree)); Assert.assertTrue(CheckSubtree.checkViaRootAndMatch(emptyTree, emptyTree)); } // checkViaTraverseAndSublist(), @Test public void testViaTraverseAndSublist() { // Integer special = null; // Integer special = Integer.MAX_VALUE; Assert.assertTrue(CheckSubtree.checkViaTraverseAndSublist(largeTree, smallTree)); // subtree, Assert.assertFalse(CheckSubtree.checkViaTraverseAndSublist(largeTree, smallTree2)); // not subtree, Assert.assertFalse(CheckSubtree.checkViaTraverseAndSublist(smallTree, largeTree)); // not subtree, // empty tree, Assert.assertTrue(CheckSubtree.checkViaTraverseAndSublist(largeTree, emptyTree)); Assert.assertTrue(CheckSubtree.checkViaTraverseAndSublist(smallTree, emptyTree)); Assert.assertTrue(CheckSubtree.checkViaTraverseAndSublist(emptyTree, emptyTree)); } } 是一个简单的二叉树实现。
  • BSTBST.BSTNode的节点。
  • BST是一个工具,可帮助构建最小高度的二进制搜索树。

答案 5 :(得分:-2)

Na ......这种方法不正确。因为不同的树可以有相同的遍历。例如,在给定的示例中,树是

         26
        /   \
      10     3
    /    \     \
  4      6      3
   \
    30

候选子树

10
/ \
4 6
\
30

30
/ \
4 10
\
6

具有与4,30,10,6相同的顺序遍历 但第二个不是子树