我正在尝试在"Data Structures and Algorithms" by Granville Barnett中遵循BST算法,但我不理解它在下面描述的节点删除算法。
第3.3节(第22页)
从BST中删除节点非常简单,需要考虑四种情况:
- 要删除的值是叶节点;或
- 要删除的值有一个右子树,但没有左子树;或
- 要删除的值有一个左子树,但没有右子树;或
- 要删除的值包含左子树和右子树,在这种情况下,我们会提升左子树中的最大值。
醇>
图3.2(第22页)
23
/ \
14 31
/
7
\
9
我将上面的文字解释为#4意味着当我们删除23时,我们将14提升为root并使31为其正确的孩子:
14
/ \
7 31
\
9
...但这本书的算法(来自第23页)对于案例#4 bamboozles me(我在这里用Java重写了它):
1 boolean remove(T value) {
2 // ...
3
4 // case #4
5 Node largestValueNode = nodeToRemove.left;
6 while (largestValueNode.right != null) {
7 // find the largest value in the left subtree of nodeToRemove
8 largestValueNode = largestValueNode.right;
9 }
10
11 // delete the right child of largestValueNode's parent
12 findParent(largestValueNode.value).right = null;
13 nodeToRemove.value = largestValueNode.value;
14
15 count--;
16 return true; // successful
17}
如果我遵循算法,largestValueNode
是节点14,因此其父节点是节点23. 为什么算法会使父节点的右子节点无效?
为什么第13行会将largestValueNode
的值复制到要删除的节点中?
我希望第11-13行是:
11 if (largestValueNode != null)
12 largestValueNode.right = nodeToRemove.right;
13 nodeToRemove.right = null;
修改
这本书的算法确实有一个错误。修复方法如下:
1 boolean remove(T value) {
2 // ...
3
4 // case #4
5 Node largestValueNode = nodeToRemove.left;
6 while (largestValueNode.right != null) {
7 // find the largest value in the left subtree of nodeToRemove
8 largestValueNode = largestValueNode.right;
9 }
10
11 Node p = findParent(largestValueNode.value);
12 if (p != null) {
13 if (nodeToRemove == p)
14 nodeToRemove.left = largestValueNode.left;
15 else
16 p.right = largestValueNode.left;
17 }
18 nodeToRemove.value = largestValueNode.value;
19
20 count--;
21 return true; // successful
22}
答案 0 :(得分:3)
如果你这样做
11 if (largestValueNode != null)
12 largestValueNode.right = nodeToRemove.right;
13 nodeToRemove.right = null;
你没有考虑14
可能有正确孩子的情况。例如:
23
/ \
14 31
/ \
7 15
\
9
在删除23
15
/ \
14 31
/
7
\
9
因此,您要将15
原始父级14
的正确子级设置为null。这是第一个代码正在做的事情。
编辑:发表评论
使用您的解决方案,您将获得
23
/
14
/ \
7 15
\ \
9 31
另外,原始代码也是错误的;尝试这样的事情:
if(nodeToRemove == findParent(largestValueNode.value))
nodeToRemove.left = largestValueNode.left
else
findParent(largestValueNode.value).right = largestValueNode.left
nodeToRemove.value = largestValueNode.value
还要回答“为什么第13行将maximumValueNode的值复制到要删除的节点中?”
我们正在删除largestValueNode
,之前我们将其值存储在nodeToRemove
答案 1 :(得分:1)
似乎本书的算法对于这个特定的例子是错误的(假设你已经完美地翻译成了Java))。它正在做你提到的,但它适用于这个案例:
其中nodeToRemove = 23并且在你的BST 14中有一个正确的孩子15.这本书的算法将在这里用15替换23并将14的右子设置为null。在这种情况下,您的算法将失败。
答案 2 :(得分:0)
仔细看看这一行:
largestValueNode.right = nodeToRemove.right;
注意这一行如何导致14
看起来像这样(忽略孙子):
14
/ \
7 31
但这正是所期望的!由于14
现在有31
作为其正确的孩子,31
成为15
的正确孩子不再正确,所以为了清理,正确的孩子15
的值设置为NULL。
答案 3 :(得分:0)
很高兴知道原始代码是错误的 - 我刚刚花了几个小时就开始思考我错过了一些东西。如果传递了根元素,则存在NPE问题,并且无论如何都不考虑根元素的移除。
这是我的Java实现,可能会使用一些优化 - 建议欢迎。 O (n log n)
最糟糕的情况。测试如下。
public boolean remove(final T value0) {
BinarySearchTreeNode<T> target = findNode(value0);
// Node DNE
if (target == null) {
return false;
}
// Both children populated, no need for parent
if (target.right != null && target.left != null) {
BinarySearchTreeNode<T> max = maxChild(target.left);
findParent(max.value).right = null;
target.value = max.value;
}
// Root element targeted, parent DNE
else if (target == root) {
if (target.right == null && target.left == null) {
root = null;
}
else if (target.right == null) {
root = target.left;
}
else {
root = target.right;
}
}
// Non-root, single-child node - find if L or R child, update parent reference.
else {
BinarySearchTreeNode<T> parent = findParent(value0);
if (target.right == null && target.left != null) {
if (target.value.compareTo(parent.value) < 0) {
parent.left = target.left;
}
else {
parent.right = target.left;
}
}
else if (target.right != null && target.left == null) {
if (target.value.compareTo(parent.value) < 0) {
parent.left = target.right;
}
else {
parent.right = target.right;
}
}
}
return true;
}
单元测试(显然全部通过):
package BinarySearchTreeTests;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
public class Remove {
BinarySearchTree<Integer> tree;
@Before
public void setUp() {
tree = new BinarySearchTree<Integer>();
}
@Test
public void fromEmptyTree() {
assertFalse(tree.remove(8));
}
@Test
public void fromTreeWithOnlyRootNode() {
tree.add(10);
assertTrue(tree.remove(10));
assertNull(tree.root);
}
@Test
public void nonexistentElement() {
tree.add(10);
assertFalse(tree.remove(8));
}
/**
* N
* 10--|
* | 6
* 5--|
* 3
*/
@Test
public void nodeWithNoRightChildren() {
tree.add(10);
tree.add(5);
tree.add(6);
tree.add(3);
tree.remove(10);
assertEquals(tree.root.value, Integer.valueOf(5));
assertEquals(tree.root.left.value, Integer.valueOf(3));
assertEquals(tree.root.right.value, Integer.valueOf(6));
}
/**
* 17
* 15--|
* | 13
* 10--|
* N
*/
@Test
public void nodeWithNoLeftChildren() {
tree.add(10);
tree.add(15);
tree.add(17);
tree.add(13);
tree.remove(10);
assertEquals(tree.root.value, Integer.valueOf(15));
assertEquals(tree.root.left.value, Integer.valueOf(13));
assertEquals(tree.root.right.value, Integer.valueOf(17));
}
/**
* 19
* 17-|
* | 16
* 15-|
* | | 14
* | 13-|
* | 12
* 10--|
* N
*/
@Test
public void nodeWithLeftAndRightChildren() {
tree.add(10);
tree.add(15);
tree.add(17);
tree.add(13);
tree.add(19);
tree.add(16);
tree.add(14);
tree.add(12);
tree.remove(15);
assertEquals(tree.root.right.value, Integer.valueOf(14));
assertNull(tree.root.right.left.right);
}
/**
* 18
* 15-|
* | [ALWAYS EMPTY]
* 15-|
* | | 13
* | 12-|
* | 11
* 10--|
* N
*
@Test
public void removeDuplicate() {
Above diagram shows duplicate cases are already tested implicitly.
fail();
} */
}