我正在寻找一个顺序算法(如果存在),它测试两个BST在O(n)
中是否具有相同的密钥,使用O(1)
内存并允许递归。
你怎么做:两个同步有序的trasversal,以便可以比较两个BST的ith
元素?
答案 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();
}
};
上面的迭代器不执行验证,并假设树至少有一个节点。 LLINK
,RLINK
和KEY
分别是左链接,右链接和密钥的访问者。
因此,使用迭代器,检查两棵树是否包含完全相同的树很容易:
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是否是相同的密钥。
当然,完全遍历树木非常重要,否则树木将处于不稳定状态。如果树木不同,肯定会发生这种情况。我留给你一个清洁程序,确保两棵树都被清除了。