删除binary tree中所有节点的标准算法在这些行的节点上使用 postorder traversal :
if (root is not null) {
recursively delete left subtree
recursively delete right subtree
delete root
}
此算法使用O(h)辅助存储空间,其中h是树的高度,因为在递归调用期间存储堆栈帧所需的空间。但是,它在时间O(n)中运行,因为每个节点只访问一次。
是否有算法在不牺牲运行时间的情况下仅使用O(1)辅助存储空间删除二叉树中的所有节点?
答案 0 :(得分:17)
使用基于 tree rotations 的算法,确实可以使用O(n)和O(1)辅助存储空间删除二叉树中的所有节点。
给定具有以下形状的二叉树:
u
/ \
v C
/ \
A B
此树的右旋转将节点v拉到节点u上方,并生成以下树:
v
/ \
A u
/ \
B C
注意,可以在O(1)时间和空间中完成树旋转,只需将树的根更改为v,将u的左子设置为v的前右子,然后将v的右子设置为u
树旋转在此上下文中很有用,因为右旋转总是会将树的左子树的高度减一。这很有用,因为它有一个聪明的观察:如果它没有左子项,那么删除树的根是非常容易的。特别是,如果树的形状如下:
v
\
A
然后我们可以通过删除节点v删除树中的所有节点,然后删除其子树A中的所有节点。这导致了一个非常简单的算法,用于删除树中的所有节点:
while (root is not null) {
if (root has a left child) {
perform a right rotation
} else {
delete the root, and make the root's right child the new root.
}
}
该算法显然只使用O(1)存储空间,因为它最多需要一定数量的指针来进行旋转或更改根,这些指针的空间可以在循环的所有迭代中重复使用。
此外,可以证明该算法也在O(n)时间内运行。直观地,通过查看给定边缘可以旋转多少次,可以看到这一点。首先,请注意,每当执行右旋转时,从节点到其左子节点的边将转换为从前一个子节点运行回其父节点的右边缘。接下来,请注意,一旦我们执行将节点u移动到节点v的右子节点的旋转,我们将永远不会再触摸节点u,直到我们删除节点v和所有v的左子树。因此,我们可以通过注意树中的每个边缘最多旋转一次来限制将要完成的总旋转次数。因此,最多完成O(n)次旋转,每次旋转花费O(1)时间,并且完成n次删除。这意味着算法在时间O(n)中运行并且仅使用O(1)空间。
如果它有帮助,我有 a C++ implementation of this algorithm ,以及对算法行为的更深入分析。它还包括算法所有步骤的正确性的正式证明。
希望这有帮助!
答案 1 :(得分:3)
让我从一个严肃的笑话开始:如果您将BST的根设置为null,则可以有效地删除树中的所有节点(垃圾收集器将使空间可用)。虽然措辞是针对Java的,但这个想法适用于其他编程语言。我提到这个,以防你在面试或参加考试。
否则,您所要做的就是使用DSW algorithm
的修改版本。基本上将树转换为骨干,然后像linked list
一样删除。空间O(1)和时间O(n)。你应该在任何教科书或网上找到DSW的讲座。
基本上DSW用于平衡BST。但是对于你的情况,一旦你获得主干,而不是平衡,你就像链接列表一样删除。
答案 2 :(得分:1)
算法1 , O(n)时间和 O(1)空间: 除非有两个子节点,否则立即删除节点。否则,转到最左边的节点,反转“左”链接,以确保所有节点都可以访问 - 最左边的节点成为新的根:
void delete_tree(Node *node) {
Node *p, *left, *right;
for (p = node; p; ) {
left = p->left;
right = p->right;
if (left && right) {
Node *prev_p = nullptr;
do {
p->left = prev_p;
prev_p = p;
p = left;
} while ((left = p->left) != nullptr);
p->left = p->right;
p->right = prev_p; //need it on the right to avoid loop
} else {
delete p;
p = (left) ? left : right;
}
}
}
算法2 , O(n)时间和 O(1)空间:遍历节点深度优先,用链接替换子链接到了父母。在上升的过程中删除每个节点:
void delete_tree(Node *node) {
Node *p, *left, *right;
Node *upper = nullptr;
for (p = node; p; ) {
left = p->left;
right = p->right;
if (left && left != upper) {
p->left = upper;
upper = p;
p = left;
} else if (right && right != upper) {
p->right = upper;
upper = p;
p = right;
} else {
delete p;
p = upper;
if (p)
upper = (p->left) ? p->left : p->right;
}
}
}
答案 3 :(得分:1)
我对以上所有需要复杂操作的答案感到惊讶。
可以通过简单地将所有递归调用替换为搜索节点并跟踪当前节点父节点的循环,从而用O(1)从BST中删除节点,以增加存储空间。使用递归只会更简单,因为递归调用会自动将搜索到的节点的所有祖先存储在堆栈中。但是,没有必要存储所有祖先。只需要存储搜索到的节点及其父节点,这样就可以取消链接搜索到的节点。存储所有祖先只是浪费空间。
以下是Python 3中的解决方案。不要被看似对delete
的递归调用所抛弃--这里的最大递归深度为2,因为保证第二次delete调用会导致delete基本情况(包含搜索值的根节点)
class Tree(object):
def __init__(self, x):
self.value = x
self.left = None
self.right = None
def remove_rightmost(parent, parent_left, child):
while child.right is not None:
parent = child
parent_left = False
child = child.right
if parent_left:
parent.left = child.left
else:
parent.right = child.left
return child.value
def delete(t, q):
if t is None:
return None
if t.value == q:
if t.left is None:
return t.right
else:
rightmost_value = remove_rightmost(t, True, t.left)
t.value = rightmost_value
return t
rv = t
while t is not None and t.value != q:
parent = t
if q < t.value:
t = t.left
parent_left = True
else:
t = t.right
parent_left = False
if t is None:
return rv
if parent_left:
parent.left = delete(t, q)
else:
parent.right = delete(t, q)
return rv
def deleteFromBST(t, queries):
for q in queries:
t = delete(t, q)
return t