这个BST节点删除算法如何工作?

时间:2012-07-07 23:27:11

标签: java algorithm binary-search-tree

我正在尝试在"Data Structures and Algorithms" by Granville Barnett中遵循BST算法,但我不理解它在下面描述的节点删除算法。

第3.3节(第22页)

  

从BST中删除节点非常简单,需要考虑四种情况:

     
      
  1. 要删除的值是叶节点;或
  2.   
  3. 要删除的值有一个右子树,但没有左子树;或
  4.   
  5. 要删除的值有一个左子树,但没有右子树;或
  6.   
  7. 要删除的值包含左子树和右子树,在这种情况下,我们会提升左子树中的最大值。
  8.   

图3.2(第22页)

    23
   /  \
  14   31
 /
7
 \
  9
  • 案例#1指向节点9.
  • 案例#2指向节点7。
  • 案例#3指向节点14.
  • 案例#4指向节点23.

我将上面的文字解释为#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}

4 个答案:

答案 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();
} */
}