2个二叉树是否相等

时间:2011-10-12 00:18:35

标签: algorithm data-structures equality binary-search-tree

  

可能重复:
  Determine if two binary trees are equal

昨天接受采访,一个问题让我知道,这里是:

描述

  

2 binary trees,检查它们是否相等。

     

当且仅当tree1->child == tree2->child和1时,它们是相等的   树的左右children can be swapped with each other

例如:

    5     6
   / \   / \           they are equal.
   1 2   2  1

    5         6
   / \       / \           they are equal.
  1   2     2   1
 /     \   /    / 
3       4 4     3

任何想法都表示赞赏。

6 个答案:

答案 0 :(得分:9)

等式算子是可传递的:如果A = B,B = C,则A = B = C,所以A = C.

等式算子是自反的:A = A,B = B,C = C,无论它们的值是什么。

等式运算符是对称的。如果A = B,那么B = A. (它们的顺序并不重要。)

现在,看看他们给你的定义:

如果孩子是平等的,树等于另一棵树。让我们来看看。我们可以假设节点在底部进行比较,否则定义很无用。但他们并不打算告诉你如何解决这种比较,他们给你的整个定义取决于它。

简而言之,这是一个糟糕的问题。

让我们看看如果我们决定尝试解开这个问题会发生什么。

但是等等,他们还告诉你,任何树的两个孩子都可以交换。这增加了任何树等于其他任何(包括其自身)的树必须等于其镜像的约束。并且其子树的任何变化都会被换掉。

请记住,这应该是搜索树。因此,我们可以假设由相同算法处理的两个不同搜索树必须在它们相等时给出相同的结果。因此,如果我们切换树的元素,那么搜索时间将受到影响。因此,没有每个节点的树彼此不相等。

将它与这种相等的“可交换”属性放在一起,我们可以看出它不是一个有效的平等定义。 (如果我们尝试应用它,那么事实证明,只有在特定级别的每个节点具有相同节点的树是相等的,并且仅对自身而言,这会破坏相等运算符的反身性部分。)

答案 1 :(得分:7)

我认为这不是一个无理的问题。一个简单的递归解决方案是

boolean equals(x, y)
{
  if (x == null)
  {
    return y == null;
  }
  if (y == null)
  {
    return false;
  }
  if (x.val != y.val)
  {
    return false;
  }
  if (equals(x.left, y.left) && equals(x.right, y.right))
  {
    return true;
  }
  if (equals(x.left, y.right) && equals(x.right, y.left))
  {
    return true;
  }
  return false;
}

这可能非常昂贵,例如在我们有两个相似形状的大树的情况下,其中所有非叶节点具有相同的关联值,并且一个的叶节点是另一个的叶节点的排列。

为了超越这个,你可以首先根据需要左右改变,以便左边<对,某些递归定义<。这也可能是昂贵的,但远不如检查每个排列,我认为选择<的定义。有助于。这样就可以检查是否与普通定义相等。

http://en.wikipedia.org/wiki/Canonicalization后来普通平等的概念也解决了你是否真的有等价关系的问题。等价关系等同于分区。普通的平等显然是一个分区。如果通过比较f(x)和f(y)后跟等价关系比较x和y,则得到x和y的分区,因此是等价关系。

更多地考虑这一点,我认为使规范化或相等测试合理有效的方法是从下到上工作,用一个标记来注释每个节点,该标记的值反映了与其他节点的比较结果,这样你就可以了可以比较节点,以及它们下面的子树,只是比较令牌。

所以平等的第一步是例如使用哈希表来使用令牌注释每个叶子,只有当叶子上的值相等时才相等。然后,对于唯一的孩子是叶子的节点,使用例如一个哈希表,用于分配更多的令牌,以便只有当这些节点下的叶子(如果有的话)匹配时,这些节点中的令牌才相等。然后你可以再向上一步,这次你可以在子节点上比较令牌而不是在那里递归树。以这种方式分配令牌的成本应该与所涉及的树的大小成线性关系。在顶部,您可以通过比较根处的标记来比较树。

答案 2 :(得分:3)

如果用flip-invariance实现它们的“相等”定义,则会违反相等的定义。该定义甚至没有意义,因为这不是二进制搜索树是如何相等的(除非每个节点都有一个指向哪个子树是“更大”并且“更小”的指针)。

您有两种合理定义的选择:

  1. 拓扑(翻转不可知)等价(在这种情况下,您不能将其称为“二叉搜索树”,因为它没有排序):

    tree1==tree2表示set(tree1.children)==set(tree2.children)

  2. 普通搜索树(flip-caring)等价:

    tree1==tree2表示list(tree1.children)==list(tree2.children)

  3. 对于二叉树,上述定义将按照支持listset数据类型的任何语言编写(但是python集会在不可用的数据类型上阻塞)。然而,下面是一些更冗长,更丑陋的C / Java定义:

    1. 拓扑等价:

      t1==t2表示(t1.left==t2.left and t1.right==t2.right) or (t1.left==t2.right and t1.right==t2.left)

    2. 排序树等价:

      t1==t2表示(t1.left==t2.left and t1.right==t2.right)

    3. 上面的定义是递归的;也就是说,他们假设已经为子树和基本案例定义了相等性。


      旁注:

        

      引用: tree1-> child == tree2-> child

      这不是有效的语句,因为树节点没有单个子节点。

答案 3 :(得分:1)

使用@mcdowella建议的标准化方法比较树。区别在于我的方法不需要O(N)额外的内存w.r.t树中的节点数:

# in Python
from collections import namedtuple
from itertools import chain

# Tree is either None or a tuple of its value and left, right trees
Tree = namedtuple('Tree', 'value left right')

def canonorder(a, b):
    """Sort nodes a, b by their values.

    `None` goes to the left
    """
    if (a and b and a.value > b.value) or b is None:
        a, b = b, a # swap
    return a, b

def canonwalk(tree, canonorder=canonorder):
    """Yield all tree nodes in a canonical order.

    Bottom-up, smaller children first, None is the smallest
    """
    if tree is not None:
        children = tree[1:]
        if all(t is None for t in children): return # cut None leaves
        children = canonorder(*children)            
        for child in chain(*map(canonwalk, children)):
            yield child
    yield tree 

canonwalk()需要O(N*M)个步骤和O(log(N)*M)内存来生成树中的所有节点,其中N是节点总数,M个子节点数每个节点都有(二进制树为2)。

对于任何节点表示和任意数量的子节点,

canonorder()可以很容易地推广。 canonwalk()只要求树可以作为序列访问其直接子项。

调用canonwalk()的比较函数:

from itertools import imap, izip_longest

unset = object() 
def cmptree(*trees):
    unequal = False # allow root nodes to be unequal
    # traverse in parallel all trees under comparison
    for nodes in izip_longest(*imap(canonwalk, trees), fillvalue=unset):
        if unequal:
            return False # children nodes are not equal
        if any(t is unset for t in nodes):
            return False # different number of nodes
        if all(t is not None for t in nodes):
            unequal = any(nodes[-1].value != t.value for t in nodes)
        else: # some are None
            unequal = any(t is not None for t in nodes)
    return True # equal

实施例

    5         6
   / \       / \           they are equal.
  1   2     2   1
 /     \   /    / 
3       4 4     3

tree1 = Tree(5, 
             Tree(1, 
                  Tree(3, None,None), None), 
             Tree(2, 
                  None, Tree(4, None, None)))
tree2 = Tree(6, 
             Tree(2, Tree(4, None, None), None),
             Tree(1, Tree(3, None, None), None))
print cmptree(tree1, tree2)

输出

True

答案 4 :(得分:0)

我把这些问题看作:给出两个二叉树,对于树中的每个深度,找出他们孩子的集合是否相互覆盖。

这可以编码相对容易。

答案 5 :(得分:0)

Ruby中没有递归的解决方案

def same? top_t1, top_t2
  for_chek << [top_t1, top_t2]   # (1) put task for check into queue

  while t1,t2 = for_check.shift  # (2)
    return false unless t1.children.count == t2.children.count  # generally for non-binary tree, but also needed for controlling of nil children
    break if t1.children.empty?

    t1_children = t1.children.sort # this is sorted arrays
    t2_children = t2.children.sort # of childrens      
    return false unless t1_children == t2_children  # (3)

    0.upto(t1_children.count - 1) do |i|
      for_check << [t1_children[i], t2_children[i]]  # put equivalent child pairs into queue
    end
  end
  return true
end

Ruby语法提示:

  • (1)将元素放入数组:arr << elem;在这种情况下,for_check是数组数组
  • (2)并行分配:t1,t2 = [item1, item2]。与arr = [item1, item2]; t1 = arr[0]; t2 = arr[1]
  • 相同
  • (3)t1_children == t2_children假设这种对象的对应行为==。更详细的将是t1_children.map { |el| el.val } == t2_children.map { |el| el.val } - 这里map生成val数组。