检查两个二进制搜索树是否具有相同的密钥

时间:2016-07-11 17:07:35

标签: algorithm recursion binary-tree binary-search-tree

我正在寻找一个顺序算法(如果存在),它测试两个BST在O(n)中是否具有相同的密钥,使用O(1)内存并允许递归。
你怎么做:两个同步有序的trasversal,以便可以比较两个BST的ith元素?

2 个答案:

答案 0 :(得分:2)

如何执行此操作取决于您的语言。

在Python这样的语言中,您所做的就是使用yield编写BST。这将创建一个保持少量状态的生成器。 (根据数据结构和算法,在这种情况下,它应该是O(1)O(log(n))。众所周知,在现实世界中log(n)是一个常数。虽然对于像谷歌它是一个稍微大一点的常数...)

在没有yield的语言中,您需要安排使用您可以调用的方法创建一个关于搜索状态的对象,该方法将返回当前元素(或告诉您已完成),然后移动到下一个。当你输入方法时保持状态并恢复它无疑是一个PITA,但是Python会为你做些什么。

如果您希望感觉非常聪明,可以尝试自动完成必要的工作。有关如何使用C预处理器在C中进行的令人印象深刻的演示,请参阅http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html。 (这种技术实际上用于广泛使用的程序PyTTY。)

答案 1 :(得分:1)

注意:最初,我在O(log(n)中为空间消耗编写了这个答案,但之后提出的O(1)解决方案如下。我更喜欢保留原始背景,因为我相信这个解决方案也很有用。

在这个答案中,我将尝试为没有yield的语言开发一种方法。不幸的是,我的方法使用堆栈,所以绝对不是O(1)。如果树或多或少平衡,则堆栈消耗将趋于O(log_2(n))

我这样做的方式是基于BST上的inorder迭代器。必须在生产环境中进行改进。因此,我将定义以下迭代器类(使用c ++伪语言):

class Inorder
{
  Node * curr = nullptr; // node currentrly beeing visited
  Stack<Node*> s; // stack for storing the ancestors

  Node * advance_to_min(Node * r) // compute the leftmost node in r and stack the path
  {
    while (LLINK(r) != Node::NullPtr)
      {
        s.push(r);
        r = LLINK(r);
      }
    return r;
  }

public:

  Inorder(Node * root) : curr(advance_to_min(root)) {}

  bool has_curr() const noexcept { return curr != nullptr; }

  Node * get_curr() const { return curr; }

  void next() // get the next node inorder sense
  {
    curr = RLINK(curr);
    if (curr != nullptr)
      {
        curr = advance_to_min(curr);
        return;
      }

    if (s.is_empty())
      curr = nullptr;
    else
      curr = s.pop();
  }
};

上面的迭代器不执行验证,并假设树至少有一个节点。 LLINKRLINKKEY分别是左链接,右链接和密钥的访问者。

因此,使用迭代器,检查两棵树是否包含完全相同的树很容易:

bool same_keys(Node * t1, Node * t2)
{
  Inorder it1(t1), it2(t2);

  for (; it1.has_curr() and it2.has_curr(); it1.next(), it2.next())
    if (KEY(it1.get_curr()) != KEY(it2.get_curr()))
      return false;

  return not (it1.has_curr() or it2.has_curr()); 
}

关于空间消耗,我怀疑这种方法等同于递归方法。但是,我也怀疑它存在一种空间消耗严格为O(1)的方法。最后一个是基于临时放入正确的空指针的“线程”,以便恢复祖先。在现代架构中,您可以使用指针的最小有效位来标记它是否是线程;所以你不需要额外的空间。

已编辑:我没有看到作者评论说他已经有O(log(n))解决方案。之后,我很快意识到我的方法可以在没有堆栈的情况下进行迭代修改。因此,在下面的内容中,我给出了一个O(1)空间解决方案,该解决方案使用“线程”来存储依次感觉为后继的祖先。

首先,将以下帮助方法放在Inorder类中:

static bool is_thread(Node * p) 
{ 
  return (Node*) (((long) p) & 1); 
}

static Node * make_thread(Node * p) 
{ 
  return (Node*) (((long)p) | 1);
}

static Node * make_pointer(Node * p)  
{ 
  return (Node*) (((long) p) & -2); 
}

基本思想是使用最低有效位来区分指针是否是线程。请注意,访问最低有效位为1的指针无效。所以is_thread()用于测试指针是否有线程。当然,还不需要堆栈。

现在,方法advance_to_min()必须修改如下:

static Node * advance_to_min(Node * r) // find the leftmost node respect to r
{
  Node * p = r;
  while (LLINK(p) != nullptr)
    {
      p = LLINK(p);

      Node * q = p; // searches predecessor of r inorder
      while (RLINK(q) != nullptr)
        q = RLINK(q);

      // q is the predecessor of r
      RLINK(q) = make_thread(r); // here is put the thread
      r = p;
    }

    return r;
  }

必须重构方法next()才能将线程恢复为空指针。这可以这样做:

void next() // get the next node inorder sense
{
  if (is_thread(RLINK(curr)))
    {
      Node * p = curr;
      curr = make_pointer(RLINK(p)); 
      RLINK(p) = nullptr; // here is deleted the thread
      return;
    }

    curr = RLINK(curr);
    if (curr != nullptr)
      curr = advance_to_min(curr);
}

课程的其余部分是一样的,瞧!你有一个O(1)空间方式来检查两个BST是否是相同的密钥。

当然,完全遍历树木非常重要,否则树木将处于不稳定状态。如果树木不同,肯定会发生这种情况。我留给你一个清洁程序,确保两棵树都被清除了。