2个二叉树是否相等

发布于 2024-12-09 05:38:33 字数 637 浏览 0 评论 0原文

可能的重复:
判断两个二叉树是否相等

昨天面试,一道题明白了,这是:

描述

2个二叉树,检查它们是否相等。

当且仅当tree1->child == tree2->child时它们相等,并且一个 树的左右子节点可以互相交换

例如:

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

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

任何想法都会受到赞赏。

Possible Duplicate:
Determine if two binary trees are equal

Got an interview yesterday, a question got me, here it is:

Description

There are 2 binary trees, check if they are equal.

They are equal if and only if tree1->child == tree2->child, and one
tree's left and right children can be swapped with each other.

For example:

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

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

Any ideas are appreciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

青丝拂面 2024-12-16 05:38:33

等式运算符具有传递性:如果 A=B,且 B=C,则 A=B=C,因此 A=C。

相等运算符是自反的:A=A、B=B 和 C=C,无论它们的值是什么。

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

现在,看一下他们给您的定义:

如果子树相等,则一棵树与另一棵树相等。让我们来看看。我们可以假设节点是在底部进行比较的,否则这个定义就毫无用处。但他们懒得告诉你如何解决这种比较,他们给你的整个定义取决于它。

简而言之,这是一个蹩脚的问题。

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

但是等等,他们还告诉您任何树的两个孩子都可以交换。这增加了这样的约束:任何与其他任何东西(包括其自身)相同的树都必须等于其镜像。其子树的子树的任何变体都被交换。

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

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

Equality operators are transitive: If A=B, and B=C, then A=B=C so A=C.

Equality operators are reflexive: A=A, B=B, and C=C no matter what their values are.

Equality operators are symmetric. If A=B, then B=A. (It doesn't matter what order they are in.)

Now, taking a look at the definition they gave you:

A tree is equal to another tree if the children are equal. Let's see. We can assume that the nodes are being compared at the bottom, or else the definition is pretty useless. But they don't bother to tell you how to resolve that comparison, and the whole definition they gave you hinges on it.

In short, it's a crappy question.

Let's see what happens if we decide we want to try and unravel the question, though.

But wait, they also tell you that the two children of any tree can be swapped. This adds the constraint that any tree that is equal to anything else (including itself) must be equal to its mirror image. And any variations of its subtrees' children being swapped.

And remember that this is supposed to be a search tree. Therefore, we can probably assume that two different search trees that are processed by the same algorithm must give the same result if they are equal. So, if we switch around the elements of a tree, then the search time would be affected. So, trees that do not have every node in place are not equal to each other.

Putting that together with the "swappable" property of this equality, we can see it's not a valid definition of equality. (If we try to apply it, then it turns out that only trees that have the same node for every node at a particular level are equal, and only to themselves, which breaks the reflexivity part of an equality operator.)

习ぎ惯性依靠 2024-12-16 05:38:33

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

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;
}

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

为了解决这个问题,您可以首先根据需要更改 left 和 right ,以便 left <对,对于 < 的一些递归定义。这也可能很昂贵,但比检查每个排列要便宜得多,而且我认为选择 << 的定义。会有帮助的。这将允许您检查与普通定义的相等性。

http://en.wikipedia.org/wiki/Canonicalization 的概念也遵循普通的平等解决有关是否确实存在等价关系的问题。等价关系相当于划分。普通的平等显然是一种划分。如果通过比较 f(x) 和 f(y) 以及等价关系来比较 x 和 y,则您将得到 x 和 y 的划分,从而得到等价关系。

进一步思考这一点,我认为使规范化或相等测试相当有效的方法是自下而上工作,用一个标记注释每个节点,该标记的值反映与其他节点比较的结果,以便您可以比较节点,以及它们下面的子树,只是比较标记。

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

I don't think this is an unreasonable question. A simple recursive solution is

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;
}

This can be very expensive, for example in the case when we have two large trees of similar shape where all the non-leaf nodes have the same associated value and the leaf nodes of one are a permutation of the leaf nodes of another.

To get past this you could first of all change left and right as required so that left < right, for some recursive definition of <. This could also be expensive, but much less so than checking every permutation, and I think a choice of definition of < would help. This would then allow you to check for equality with an ordinary definition.

This notion of http://en.wikipedia.org/wiki/Canonicalization followed by ordinary equality also resolves questions about whether you really do have an equivalence relation. An equivalence relation is equivalent to a partition. Ordinary equality is obviously a partition. If you compare x and y by comparing f(x) and f(y) followed by an equivalence relation you have a partition of x and y, and therefore an equivalence relation.

Thinking more about this, I think the way to make either canonicalisation or equality-testing reasonably efficient is to work from the bottom up, annotating each node with a token whose value reflects the result of comparisons with other nodes, so that you can compare nodes, and the subtrees below them, just be comparing tokens.

So the first step for equality is to e.g. use a hash table to annotate each leaf with tokens which are equal only when the values at the leaves are equal. Then, for nodes whose only children are leaves, use e.g. a hash table to assign further tokens so that the tokens in those nodes are equal only when the leaves, if any, beneath those nodes match. Then you can go one more step up, and this time you can compare tokens at child nodes instead of recursing down the tree there. The cost of assigning tokens in this way should be linear in the size of the trees involved. At the top you can compare trees just by comparing the tokens at the root.

请帮我爱他 2024-12-16 05:38:33

如果你用翻转不变性来实现他们对“平等”的定义,你将违反平等的定义。这个定义甚至没有意义,因为二叉搜索树不是相等的(除非每个节点都有一个指针,指向哪个子树“更大”,哪个子树“更小”)。

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

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

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

  2. 普通搜索树(翻转关怀)等价:

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

对于二叉树,上述定义将以任何支持 list 和 set 数据类型的语言按原样工作(但是 python 集在不可散列的数据类型上会阻塞)。尽管如此,下面是一些更冗长和丑陋的类似 C/Java 的定义:

  1. 拓扑等效:

    t1==t2 表示 (t1.left==t2.left and t1.right==t2.right) 或 (t1.left==t2.right and t1.右==t2.左)

  2. 排序树等价:

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

上面的定义是递归的;也就是说,他们假设已经为子树和基本情况定义了相等性,确实如此。


旁注:

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

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

If you implement their definition of "equality" with flip-invariance, you will violate the definition of equality. The definition doesn't even make sense, because that's not how binary search trees are equal (unless each node has a pointer to which subtree is "greater" and which is "lesser").

You have two choices of reasonable definitions:

  1. topological (flip-agnostic) equivalence (in which case you can't call it a "binary search tree" because it's not sorted):

    tree1==tree2 means set(tree1.children)==set(tree2.children)

  2. normal search tree (flip-caring) equivalence:

    tree1==tree2 means list(tree1.children)==list(tree2.children)

For binary trees, the above definitions will work as-written in any language which supports the list and set datatypes (python sets will choke however on unhashable datatypes). Nevertheless, below are some more verbose and ugly C/Java-like definitions:

  1. topological equivalence:

    t1==t2 means (t1.left==t2.left and t1.right==t2.right) or (t1.left==t2.right and t1.right==t2.left)

  2. sorted tree equivalence:

    t1==t2 means (t1.left==t2.left and t1.right==t2.right)

The definitions above are recursive; that is, they assume equality has been defined for the subtrees and base-cases already, which it has.


sidenote:

quote: tree1->child == tree2->child

This is not a valid statement, because a tree node does not have a single child.

绝影如岚 2024-12-16 05:38:33

使用 @mcdowella 建议的规范化方法比较树。不同之处在于,我的方法不需要 O(N) 额外的内存(关于树中节点的数量):

# 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

Compare trees using canonization approach suggested by @mcdowella. The difference is that my approach doesn't require O(N) additional memory w.r.t number of nodes in the tree:

# 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() requires O(N*M) steps and O(log(N)*M) memory to yield all nodes in a tree, where N is total number of nodes, M number of children each node has (it is 2 for binary trees).

canonorder() could be easily generalized for any node representation and any number of children. canonwalk() requires only that a tree can access its immediate children as a sequence.

The comparison function that calls 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

Example

    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)

Output

True
太阳公公是暖光 2024-12-16 05:38:33

我将问题读为:给定两个二叉树,对于树中的每个深度,找出它们的子集是否相互覆盖。

这可以相对容易地编码。

I read the questions as: given two binary trees, for each depth in the tree, find out whether their children's set are covered in each other.

This can be coded relatively easy.

尛丟丟 2024-12-16 05:38:33

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 <<元素;在本例中,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 生成 vals 数组。

Solution without recursion in 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 syntax tips:

  • (1) putting element into array: arr << elem; in this case for_check is array of arrays
  • (2) parallel assignment: t1,t2 = [item1, item2]. Same as arr = [item1, item2]; t1 = arr[0]; t2 = arr[1]
  • (3) t1_children == t2_children assumed corresponding behavior of == for this kind of objects. More verbose will be t1_children.map { |el| el.val } == t2_children.map { |el| el.val } - here map produces array of vals.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文