Словарь с использованием Red-Black tree - ошибка удаления

Я пытаюсь реализовать словарь с использованием дерева Red-Black.
Я протестировал метод вставки и, похоже, работает хорошо, RBtree, похоже, сохраняет правильную форму и цвета. Метод, который выполняет удаление двоичного дерева node, кажется правильным, но у меня возникли огромные проблемы в методе 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(Вот node 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 → Мой тестовый класс

Обновление 09/07/2016 Я обновил свой код, потому что обнаружил, что я не обновлял курсор node до корня после исправления, и я не вызывал исправление, только когда удаленный node черный.
Учитывая мой тестовый пример testDeleteDoubles, я выяснил, что я выбираю неправильного кандидата для переключения с элементом, который нужно удалить, когда у него есть брат.
Если смотреть этот симулятор, кандидат должен быть максимальным node в левой ветки удалённого элемента, но не должен быть преемником, поэтому элемент min на правой ветке?

Ответ 1

В delete() вам нужно запомнить дочерний элемент node удаляемого node, потому что после удаления могут быть нарушены свойства red-black. Пусть говорят, что RBTreeWParentNode<K, V> childOfDeletedNode;

Затем для случая левого ребенка вы обновляете childOfDeletedNode = node.getLeft();

В случае правильного ребенка вы обновляете childOfDeletedNode = node.getRight();

Для обоих детей вам необходимо добавить следующее после вызова min():

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

Для листа возьмите любой дочерний элемент childOfDeletedNode = node.getRight();

Затем вы устанавливаете цвет дочернего элемента node с помощью deleteFixUp(childOfDeletedNode);

Теперь, поскольку childOfDeletedNode может быть null, вам нужно обработать этот случай в deleteFixUp, добавив проверку в node != null в условие цикла и добавив if-инструкцию перед установкой цвета в черный цвет в последняя строка.

В любом случае, симулятор, на который вы ссылаетесь, находит максимальный node левого поддерева. Ваше решение найдет минимум node правого поддерева. Оба правильные, но приведут к разным результатам. Вам понадобится исправить ваш тестовый пример.

Чтобы проиллюстрировать, перед удалением:

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

После удаления 8 вы замените его 9 минимальным node правого поддерева. Цвет изменяется на красный.

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