使用红黑树的字典 - 删除错误

时间:2016-09-04 21:06:31

标签: java algorithm tree binary-tree red-black-tree

我尝试使用红黑树实现字典 我已经测试了插入方法,它似乎运行良好,RBtree似乎保持正确的形状和颜色。执行二叉树节点删除的方法似乎是正确的,但是我在删除结束时调用的deleteFixUp方法上遇到了很大的问题。

你想帮我搞清楚我做错了什么吗?当然,如果您有任何改进我的代码的建议,我们将非常感激。

RBTreeWParentDictionary.java(这里我实现了RedBlackTree)

package dictionary;

import java.util.Comparator;

public class RBTreeWParentDictionary<K, V> implements IDictionary<K, V> {
  /**
   * The root node of the RBTreeWParentDictionary
   */
  public RBTreeWParentNode<K, V> root;

  /**
   * Object used to compare two T objects.
   */
  private Comparator<K>          comparator;

  private int                    length;

  /**
   * Creates the dictionary based on red/black tree with null root
   * 
   * @param comparator
   *          The comparator for keys
   */
  public RBTreeWParentDictionary(Comparator<K> comparator) {
    this.root = null;
    this.comparator = comparator;
    this.length = 0;
  }

  /**
   * Checks if the tree is empty
   * 
   * @return True if the tree is empty
   */
  public boolean isEmpty() {
    return this.root == null;
  }

  /**
   * Returns the number of elements in the tree
   * 
   * @return The number of elements in the tree
   */
  public int length() {
    return this.length;
  }

  /**
   * Performs a left rotation on the tree node
   * 
   * @param node
   *          The node on which rotate
   */
  private void rotateLeft(RBTreeWParentNode<K, V> node) {
    RBTreeWParentNode<K, V> y = node.getRight();
    node.setRight(y.getLeft());
    if (y.hasLeft()) {
      y.getLeft().setParent(node);
    }
    y.setParent(node.getParent());
    if (!node.hasParent()) { // = this.isEmpty()
      this.root = y;
    }
    else {
      if (node.equals(node.getParent().getLeft())) {
        node.getParent().setLeft(y);
      }
      else {
        node.getParent().setRight(y);
      }
    }
    y.setLeft(node);
  }

  /**
   * Performs a right rotation on the tree node
   * 
   * @param node
   *          The node on which rotate
   */
  private void rotateRight(RBTreeWParentNode<K, V> node) {
    RBTreeWParentNode<K, V> y = node.getLeft();
    node.setLeft(y.getRight());
    if (y.hasRight()) {
      y.getRight().setParent(node);
    }
    y.setParent(node.getParent());
    if (!node.hasParent()) {
      this.root = y;
    }
    else {
      if (node.equals(node.getParent().getRight())) {
        node.getParent().setRight(y);
      }
      else {
        node.getParent().setLeft(y);
      }
    }
    y.setRight(node);
  }

  /*
   * Uses for first tests, now removed
   * 
   * public void testRotateLeft() { this.rotateLeft(this.root); }
   * 
   * public void testRotateRight() { this.rotateRight(this.root); }
   */

  /**
   * Performs all the needed work to the tree under the 3 main rules of R/BTree
   * 
   * @param node
   *          The current node that needs to be checked
   */
  private void treeFixUp(RBTreeWParentNode<K, V> node) {
    RBTreeWParentNode<K, V> u;
    if (!node.hasParent()) {
      return;
    }
    while (node.getParent().isRed()) {
      if (node.getParent().equals(node.getParent().getParent().getLeft())) {
        u = node.getParent().getParent().getRight();
        if (u != null && u.isRed()) {
          node.getParent().setBlack();
          u.setBlack();
          node.getParent().getParent().setRed();
          node = node.getParent().getParent();
        }
        else {
          if (node.equals(node.getParent().getRight())) {
            node = node.getParent();
            rotateLeft(node);
          }
          node.getParent().setBlack();
          node.getParent().getParent().setRed();
          rotateRight(node.getParent().getParent());
        }
      }
      else {
        u = node.getParent().getParent().getLeft();
        if (u != null && u.isRed()) {
          node.getParent().setBlack();
          u.setBlack();
          node.getParent().getParent().setRed();
          node = node.getParent().getParent();
        }
        else {
          if (node.equals(node.getParent().getLeft())) {
            node = node.getParent();
            rotateRight(node);
          }
          node.getParent().setBlack();
          node.getParent().getParent().setRed();
          rotateLeft(node.getParent().getParent());
        }
      }
      if (!node.hasParent()) {
        node.setBlack();
        break;
      }
    }
  }

  /**
   * Inserts a node with give key/value
   * 
   * @param key
   *          The key of the node to be inserted
   * @param value
   *          The value of the node to be inserted
   */
  @Override
  public void insert(K key, V value) {
    int res;
    RBTreeWParentNode<K, V> insertedNode = new RBTreeWParentNode<K, V>(key,
        value);
    if (this.isEmpty()) {
      this.root = insertedNode;
      this.root.setBlack();
    }
    else {
      RBTreeWParentNode<K, V> node = this.root;
      while (node != null) {
        res = comparator.compare(key, node.getKey());
        if (res < 0) {
          if (node.hasLeft()) {
            node = node.getLeft();
          }
          else break;
        }
        else if (res > 0) {
          if (node.hasRight()) {
            node = node.getRight();
          }
          else break;
        }
        else { // duplicate key, overwriting
          node.setValue(value);
          return;
        }
      }
      res = comparator.compare(key, node.getKey());
      if (res < 0) {
        node.setLeft(insertedNode);
      }
      else {
        node.setRight(insertedNode);
      }
      treeFixUp(insertedNode);
      this.length++;
    }
  }

  @Override
  public V get(K key) {
    // TODO Auto-generated method stub
    return null;
  }

  @Override
  public void delete(K key) {
    RBTreeWParentNode<K, V> node = root;
    boolean oldColor;
    int res;

    while (node != null
        && (res = comparator.compare(key, node.getKey())) != 0) {
      if (res < 0) node = node.getLeft();
      else node = node.getRight();
    }    
    if (node == null)
      return;
    oldColor = node.getColor();
    // key found, work with children
    if (!node.hasParent()) {//In root
      root = null;
      return;
    }
    else if(node.hasLeft() && !node.hasRight()) {//left child
      node.getLeft().setParent(node.getParent());
      node.getParent().setLeft(node.getLeft());
    }
    else if (!node.hasLeft() && node.hasRight()) {//right child
      node.getRight().setParent(node.getParent());
      node.getParent().setRight(node.getRight());
    }
    else if (node.hasLeft() && node.hasRight()) {//both children
      RBTreeWParentNode<K, V> tmp = node;
      node = min(tmp.getRight());
      //fix parent node of node
      node.setParent(tmp.getParent());
      if (tmp.getParent().getLeft().equals(tmp)) {
        node.getParent().setLeft(node);
      }
      else node.getParent().setRight(node);

      node.setRight(deleteMin(tmp.getRight()));
      node.setLeft(tmp.getLeft());
      tmp = null;
    }
    else { // is a leaf
      if (node.equals(node.getParent().getLeft()) ) {
        node.getParent().setLeft(null);
      }
      else node.getParent().setRight(null);
    }
    if (oldColor == false) {
      deleteFixUp(node);
    }
  }

  private RBTreeWParentNode<K, V> deleteMin(
      RBTreeWParentNode<K, V> node) {
    if (node.getLeft() == null) {
      return node.getRight();
    }
    node.setLeft(deleteMin(node.getLeft()));
    return node;
  }

  private RBTreeWParentNode<K, V> min(RBTreeWParentNode<K, V> node) {
    if (node.getLeft() == null) {
      return node;
    }
    else return min(node.getLeft());
  }


  private void deleteFixUp(RBTreeWParentNode<K, V> node) {
    while (!node.equals(this.root) && node.isBlack()) {
      if (node.equals(node.getParent().getLeft())) {
        if (node.getParent().hasRight()) {
          RBTreeWParentNode<K, V> w = node.getParent().getRight();
          if (w.isRed()) {
            w.setBlack();
            node.getParent().setRed();
            rotateLeft(node.getParent());
            w=node.getParent().getRight();
          }
          if (w.hasLeft() && w.hasRight() && w.getLeft().isBlack() && w.getRight().isBlack()) {
            w.setRed();
            node = node.getParent();
          }
          else {
            if (w.hasRight() && w.getRight().isBlack()) {
              w.getLeft().setBlack();
              w.setRed();
              rotateRight(w);
              w = node.getParent().getRight();
            }
            w.setColor(node.getParent().getColor());
            node.getParent().setBlack();
            w.getRight().setBlack();
            rotateLeft(node.getParent());
            node = this.root;
          }          
        }
      }
      else {
        //Repeat up changing left with right
        if (node.getParent().hasLeft()) {
          RBTreeWParentNode<K, V> w = node.getParent().getLeft();
          if (w.isRed()) {
            w.setBlack();
            node.getParent().setRed();
            rotateRight(node.getParent());
            w=node.getParent().getLeft();
          }
          if (w.hasLeft() && w.hasRight() && w.getLeft().isBlack() && w.getRight().isBlack()) {
            w.setRed();
            node = node.getParent();
          }
          else {
            if (w.hasLeft() && w.getLeft().isBlack()) {
              w.getRight().setBlack();
              w.setRed();
              rotateLeft(w);
              w = node.getParent().getLeft();
            }
            w.setColor(node.getParent().getColor());
            node.getParent().setBlack();
            w.getLeft().setBlack();
            rotateRight(node.getParent());
            node = this.root;
          }          
        }
      }
    }
    node.setBlack();
  }

  @SuppressWarnings("unused")
  @Override
  public boolean equals(Object other) {
    if (!(other instanceof RBTreeWParentDictionary)) {
      return false;
    }
    if ((this == null && other != null) || (this != null && other == null)) {
      return false;
    }
    if (this == null && other == null) {
      return true;
    }
    else {
      @SuppressWarnings("unchecked")
      RBTreeWParentDictionary<K, V> oth = (RBTreeWParentDictionary<K, V>) other;
      return equalsNodes(this.root, oth.root);
    }
  }

  private boolean equalsNodes(RBTreeWParentNode<K, V> node1,
      RBTreeWParentNode<K, V> node2) {
    if ((node1 == null && node2 != null) || (node1 != null && node2 == null)) {
      return false;
    }
    else if (node1 == null && node2 == null) {
      return true;
    }
    else return node1.equals(node2)
        && equalsNodes(node1.getLeft(), node2.getLeft())
        && equalsNodes(node1.getRight(), node2.getRight());
  }

}

RBTreeWParentNode.java(这是RedBlackTree的节点)

package dictionary;

public class RBTreeWParentNode<K, V> {
  private K                    key;
  private V                    value;
  private boolean              color;
  private RBTreeWParentNode<K, V>  left, right, parent;

  private static final boolean RED   = true;
  private static final boolean BLACK = false;

  public RBTreeWParentNode(K key, V value, RBTreeWParentNode<K, V> left,
      RBTreeWParentNode<K, V> right, RBTreeWParentNode<K, V> parent) {
    this.key = key;
    this.value = value;
    this.color = RED;
    this.left = left;
    if (this.hasLeft())
      this.getLeft().setParent(this);
    this.right = right;
    if (this.hasRight())
      this.getRight().setParent(this);
    this.parent = parent;
  }

  public RBTreeWParentNode(K key, V value) {
    this.key = key;
    this.value = value;
    this.color = RED;
  }

  public RBTreeWParentNode() {
  }

  public K getKey() {
    return key;
  }

  public V getValue() {
    return value;
  }

  public boolean getColor() {
    return color;
  }

  public RBTreeWParentNode<K, V> getLeft() {
    return left;
  }

  public RBTreeWParentNode<K, V> getRight() {
    return right;
  }

  public RBTreeWParentNode<K, V> getParent() {
    return parent;
  }

  public RBTreeWParentNode<K, V> getBrother() {
    if (this.hasParent()) {
      if (this.getParent().getLeft().equals(this)) {
        return this.getParent().getRight();
      }
      else return this.getParent().getLeft();
    }
    else return null;
  }

  public boolean isRed() {
    return this.color == RED;
  }

  public boolean isBlack() {
    return this.color == BLACK;
  }

  public boolean hasLeft() {
    return this.getLeft() != null;
  }

  public boolean hasRight() {
    return this.getRight() != null;
  }

  public boolean hasParent() {
    return this.getParent() != null;
  }

  public boolean hasBrother() {
    if (this.hasParent()) {
      if (this.getParent().getLeft().equals(this)) {
        return this.getParent().getRight() != null;
      }
      else return this.getParent().getLeft() != null;
    }
    else return false;
  }

  public void setKey(K key) {
    this.key = key;
  }

  public void setValue(V value) {
    this.value = value;
  }

  public void setRed() {
    this.color = RED;
  }

  public void setBlack() {
    this.color = BLACK;
  }

  public void setParent(RBTreeWParentNode<K, V> node) {
    this.parent = node;
  }

  public void setLeft(RBTreeWParentNode<K, V> node) {
    this.left = node;
    if (this.hasLeft())
      this.left.setParent(this);
  }

  public void setRight(RBTreeWParentNode<K, V> node) {
    this.right = node;
    if (this.hasRight())
      this.right.setParent(this);
  }

  public void setColor(boolean color) {
    this.color = color;
  }

  @Override
  public boolean equals(Object other) {
    if (!(other instanceof RBTreeWParentNode)) {
      return false;
    }
    if ((this == null && other != null) || (this != null && other == null)) {
      return false;
    }
    @SuppressWarnings("unchecked")
    RBTreeWParentNode<K, V> oth = (RBTreeWParentNode<K, V>) other;
    return checkFieldsEquals(oth);
  }

  private boolean checkFieldsEquals(RBTreeWParentNode<K, V> oth) {
    //Check keys
    if ((this.getKey() == null && oth.getKey() != null)
        || (this.getKey() != null && oth.getKey() == null)) {
      return false;
    }
    else {
      if ((this.getKey() == null && oth.getKey() == null)
          || this.getKey().equals(oth.getKey())) {
        if ((this.getValue() == null && oth.getValue() != null)
            || (this.getValue() != null && oth.getValue() == null)) {
          return false;
        }
        else {
          if ((this.getValue() == null && oth.getValue() == null)
              || (this.getValue().equals(oth.getValue()))) {
            if (this.getColor() != oth.getColor()) {
              return false;
            }
            else {
              return (this.getKey() == null && oth.getKey() == null)
                  || this.getKey().equals(oth.getKey());
            }
          }
          else return false;
        }
      }
      else {
        return false;
      }
    }
  }

}

RBTreeWParentDictionaryTest.java -> My test class

2016年7月7日更新 我已经更新了我的代码,因为我发现在修复后我没有将节点光标更新为root,并且只有当删除的节点为黑色时我才调用修复程序。
考虑到我的测试用例testDeleteDoubles我已经发现我选择了一个错误的候选人来切换当有兄弟的项目时要删除。 看到this simulator候选人应该是已删除项目左侧分支的最大节点,但不应该是后继者,所以右侧分支上的最小项目?

1 个答案:

答案 0 :(得分:5)

delete()中,您需要记住已删除节点的子节点,因为删除后可能会违反红黑属性。我们说我们宣布RBTreeWParentNode<K, V> childOfDeletedNode;

然后,对于左孩子的情况,您更新childOfDeletedNode = node.getLeft();

对于正确的孩子,请更新childOfDeletedNode = node.getRight();

对于这两个孩子,您需要在调用min()后添加以下

oldColor = node.getColor();
childOfDeletedNode = node.getLeft();
node.setColor(tmp.getColor());

对于叶子,请带任何孩子childOfDeletedNode = node.getRight();

然后,使用deleteFixUp(childOfDeletedNode);

修复子节点的颜色

既然childOfDeletedNode可以是null,您需要在deleteFixUp中通过在循环条件中添加node != null检查并添加if语句来处理该情况在最后一行将颜色设置为黑色之前。

无论如何,您引用的模拟器找到左子树的最大节点。您的解决方案找到右侧子树的最小节点。两者都是正确的,但会产生不同的结果。您需要修复测试用例。

为了说明,在删除之前:

      10(B)
     /    \
    8(R)  100(B)
   /   \
  5(B) 9(B)
 /   \
2(R) 6(R)

删除8后,将其替换为9,即右子树的最小节点。颜色变为红色。

      10(B)
     /    \
    9(R)  100(B)
   /
  5(B)
 /   \
2(R) 6(R)