红黑树自顶向下删除算法

10
我正在实现一个具有插入、搜索和删除功能的红黑树,时间复杂度为O(log n)。插入和搜索都正常工作。但是在删除方面卡住了。我在网上找到了这张ppt幻灯片,展示了RBT删除的算法:http://www.slideshare.net/piotrszymanski/red-black-trees#btnNext ,从第56页开始。我知道我要求有些过高,但我已经卡了两个多星期,找不到问题所在。按照我理解的自顶向下删除方式,您需要旋转和重新着色节点,直到找到要删除的节点的前驱。当您找到此节点时(可能是叶子节点或仅有右子节点的节点),将要删除节点的数据替换为该节点的数据,并像普通的BST删除一样删除此节点,对吗?
这是我基于那张幻灯片学到的内容编写的代码。如果有人能够检查一下,我将不胜感激!或者,如果您认为有比我使用的更好的算法,请告诉我!
 public void delete(int element){

    if (root == null){ 
        System.out.println("Red Black Tree is Empty!");

    } else {

      Node X = root; 
      parent = null; 
      grandParent = null; 
      sibling = null; 

      if (isLeaf(X)){

          if (X.getElement() == element){
              emptyRBT();
          } 

      } else {

      if (checkIfBlack(root.getLeftChild()) && checkIfBlack(root.getRightChild())){
          root.setIsBlack(false);

          if (X.getElement() > element && X.getLeftChild() != null){ 
              X = moveLeft(X);

          } else if (X.getElement() < element && X.getRightChild() != null){
              X = moveRight(X);
          } 

          Step2(X, element);

      } else { 

          Step2B(X, element);

       } 
     }
   } 
   root.setIsBlack(true);
}

public void Step2(Node X, int element)
{
    int dir = -1;

    while (!isLeaf(X)){

      if (predecessor == null){  // still didn't find Node to delete

        if (X.getElement() > element && X.getLeftChild() != null){
            X = moveLeft(X);
            dir = 0;
        } else if (X.getElement() < element && X.getRightChild() != null){
            X = moveRight(X);
            dir = 1;
        } else if (X.getElement() == element){
            toDelete = X;
            predecessor = inorderPredecessor(X.getRightChild());
            X = moveRight(X);
        }

      } else { // if node to delete is already found and X is equal to right node of to delete
               // move always to the left until you find predecessor

          if (X != predecessor){
              X = moveLeft(X);
              dir = 0;
          } 
      }

      if (!isLeaf(X)){
        if (!hasOneNullNode(X)){

         if (checkIfBlack(X.getLeftChild()) && checkIfBlack(X.getRightChild())){
             Step2A(X, element, dir);
         } else {
             Step2B(X, element);
         }
       }
     }
   }

   removeNode(X);

   if (predecessor != null){
       toDelete.setElement(X.getElement());
   }
}

public Node Step2A(Node X, int element, int dir) {

    if (checkIfBlack(sibling.getLeftChild()) && checkIfBlack(sibling.getRightChild())) {
        X = Step2A1(X);

    } else if ((checkIfBlack(sibling.getLeftChild()) == false) && checkIfBlack(sibling.getRightChild())) {
        X = Step2A2(X);

    } else if ((checkIfBlack(sibling.getLeftChild()) && (checkIfBlack(sibling.getRightChild()) == false))) {
        X = Step2A3(X);

    } else if ((checkIfBlack(sibling.getLeftChild()) == false) && (checkIfBlack(sibling.getRightChild()) == false)) {
        X = Step2A3(X);
    }

    return X;
}

public Node Step2A1(Node X) {

    X.setIsBlack(!X.IsBlack());
    parent.setIsBlack(!parent.IsBlack());
    sibling.setIsBlack(!sibling.IsBlack());

    return X;
}

public Node Step2A2(Node X) {

    if (parent.getLeftChild() == sibling){
        LeftRightRotation(sibling.getLeftChild(), sibling, parent);

    } else RightLeftRotation(sibling.getRightChild(), sibling, parent);

    X.setIsBlack(!X.IsBlack());
    parent.setIsBlack(!parent.IsBlack());

    return X;
}

public Node Step2A3(Node X) {

    if (parent.getLeftChild() == sibling){
        leftRotate(sibling);
    } else if (parent.getRightChild() == sibling){
        rightRotate(sibling);  
    }

    X.setIsBlack(!X.IsBlack());
    parent.setIsBlack(!parent.IsBlack());
    sibling.setIsBlack(!sibling.IsBlack());
    sibling.getRightChild().setIsBlack(!sibling.getRightChild().IsBlack());

    return X;
}

public void Step2B(Node X, int element){

    if (predecessor == null){
        if (X.getElement() > element && X.getLeftChild() != null){
            X = moveLeft(X);
        } else if (X.getElement() < element && X.getRightChild() != null){
            X = moveRight(X);
        } else if (X.getElement() == element){
            Step2(X, element);
        }

    } else {

        if (X != predecessor)
            X = moveLeft(X);
        else Step2(X, element);
    }

    if (X.IsBlack()){

       if (parent.getLeftChild() == sibling){
           leftRotate(sibling);
       } else if (parent.getRightChild() == sibling){
           rightRotate(sibling);
       }

       parent.setIsBlack(!parent.IsBlack());
       sibling.setIsBlack(!sibling.IsBlack()); 

       Step2(X, element);

    } else {
        Step2B(X, element);
    }
}

public void removeNode(Node X) {

    if (isLeaf(X)) {
        adjustParentPointer(null, X);
        count--;

    } else if (X.getLeftChild() != null && X.getRightChild() == null) {
        adjustParentPointer(X.getLeftChild(), X);
        count--;

    } else if (X.getRightChild() != null && X.getLeftChild() == null) {
        adjustParentPointer(X.getRightChild(), X);
        count--;
    } 
}

public Node inorderPredecessor(Node node){

   while (node.getLeftChild() != null){
       node = node.getLeftChild();
   }

   return node;
}

public void adjustParentPointer(Node node, Node current) {

    if (parent != null) {

        if (parent.getElement() < current.getElement()) {
            parent.setRightChild(node);
        } else if (parent.getElement() > current.getElement()) {
            parent.setLeftChild(node);
        }
    } else {
        root = node;
    }
}

public boolean checkIfBlack(Node n){
    if (n == null || n.IsBlack() == true){
        return true;
    } else return false;
}

public Node leftRotate(Node n)
{  
    parent.setLeftChild(n.getRightChild());
    n.setRightChild(parent);

    Node gp = grandParent;

    if (gp != null){

        if (gp.getElement() > n.getElement()){
            gp.setLeftChild(n);
        } else if (gp.getElement() < n.getElement()){
            gp.setRightChild(n);
        }

    } else root = n;

    return n;
}

public Node rightRotate(Node n)
{  
    parent.setRightChild(n.getLeftChild());
    n.setLeftChild(parent);

    Node gp = grandParent;

    if (gp != null){

        if (gp.getElement() > n.getElement()){
            gp.setLeftChild(n);
        } else if (gp.getElement() < n.getElement()){
            gp.setRightChild(n);
        }

    } else root = n;

    return n;
}

节点正在被删除,但删除后的树将违反黑色平衡,这是非常错误的。

有人以前做过红黑树的自顶向下删除吗? - Bernice
MIT关于红黑树的讲座:http://videolectures.net/mit6046jf05_demaine_lec10/ - Kevin
1
还有维基百科上有非常好的信息:http://en.wikipedia.org/wiki/Red–black_tree - Kevin
你好 Bernice。你还有 RBT 自顶向下插入和删除的完整实现吗? - FranXh
这是我实现的符合Weiss书籍的红黑树的C语言版本,希望对您有所参考。 https://github.com/xiaodonng/vanilla/blob/master/rbtree.c - cifer
2个回答

5
永久混乱的博客有自上而下的红黑树插入和删除实现。它还逐个案例地讲解了为什么这样做。我不会在这里重复(它相当冗长)。
我已经用这篇博客作为实现C++和Java中的红黑树的参考。正如我在一个早期的回答中所述,我发现这种实现比std::map的红黑树自底向上的实现更快(无论是gcc当时附带的STL)。
这是代码的未经测试的直接翻译到Java。我强烈建议您测试并将其改变以适应您的风格。
private final static int LEFT = 0;
private final static int RIGHT = 1;

private static class Node {
    private Node left,right;
    private boolean red;
    ...

    // any non-zero argument returns right
    Node link(int direction) {
        return (direction == LEFT) ? this.left : this.right;
    }
    // any non-zero argument sets right
    Node setLink(int direction, Node n) {
        if (direction == LEFT) this.left = n;
        else this.right = n;
        return n;
    }
}

boolean remove(int data) {
  if ( this.root != null ) {
    final Node head = new Node(-1, null, null); /* False tree root */
    Node cur, parent, grandpa; /* Helpers */
    Node found = null;  /* Found item */
    int dir = RIGHT;

    /* Set up helpers */
    cur = head;
    grandpa = parent = null;
    cur.setLink(RIGHT, this.root);

    /* Search and push a red down */
    while ( cur.link(dir) != null ) {
      int last = dir;

      /* Update helpers */
      grandpa = parent, parent = cur;
      cur = cur.link(dir);
      dir = cur.data < data ? RIGHT : LEFT;

      /* Save found node */
      if ( cur.data == data )
        found = cur;

      /* Push the red node down */
      if ( !is_red(cur) && !is_red(cur.link(dir)) ) {
        if ( is_red(cur.link(~dir)) )
          parent = parent.setLink(last, singleRotate(cur, dir));
        else if ( !is_red(cur.link(~dir)) ) {
          Node s = parent.link(~last);

          if ( s != null ) {
            if (!is_red(s.link(~last)) && !is_red(s.link(last))) {
              /* Color flip */
              parent.red = false;
              s.red = true;
              cur.red = true;
            }
            else {
              int dir2 = grandpa.link(RIGHT) == parent ? RIGHT : LEFT;

              if ( is_red(s.link(last)) )
                grandpa.setLink(dir2, doubleRotate(parent, last));
              else if ( is_red(s.link(~last)) )
                grandpa.setLink(dir2, singleRotate(parent, last));

              /* Ensure correct coloring */
              cur.red = grandpa.link(dir2).red = true;
              grandpa.link(dir2).link(LEFT).red = false;
              grandpa.link(dir2).link(RIGHT).red = false;
            }
          }
        }
      }
    }

    /* Replace and remove if found */
    if ( found != null ) {
      found.data = cur.data;
      parent.setLink(
        parent.link(RIGHT) == cur ? RIGHT : LEFT,
        cur.link(cur.link(LEFT) == null ? RIGHT : LEFT));
    }

    /* Update root and make it black */
    this.root = head.link(RIGHT);
    if ( this.root != null )
      this.root.red = false;
  }

  return true;
}

非常感谢 @Michael .. 你帮了大忙!我测试了它并做了一些更改,以便我更好地理解,并且它完美地工作!不知道我在这里提交的代码有什么问题。有时候只是一个小错误,即使通过调试也需要很长时间才能发现!再次感谢。投票支持,授予悬赏并设置为答案 ;) - Bernice
是的,我不太喜欢直接将它翻译成Java的风格。布尔值转整型很不协调,我也不喜欢所有嵌套的if语句。 - Michael Deardeuff
不管怎样,我很高兴它有帮助。 - Michael Deardeuff
是啊,我也一样,有时候真的很困惑!嵌套的if语句让它看起来比实际上更难,尽管更短! - Bernice

1

快速链接: http://algs4.cs.princeton.edu/33balanced/RedBlackBST.java.html

--> 注意:该网站上的代码依赖于两个JAR文件。但在数据结构中,依赖关系可能很小。有时仅注释掉主方法(仅用作测试客户端)就足够了。 如果不行:这些JAR文件可在同一网站上下载。

如果您正在寻找两周并学习算法,则很可能已经知道

http://algs4.cs.princeton.edu/

这是一本与著名书籍《算法》(作者为Robert Sedgewick和Kevin Wayne)相伴的网站。

在这个网站上,有一个红黑树(平衡树)的实现:

该书籍与网站内容均与IT相关。

http://algs4.cs.princeton.edu/33balanced/RedBlackBST.java.html

我还没有深入研究它(我会在今年晚些时候),但我完全相信它是一个工作的红黑树实现。

对于这个主题的访问者可能有趣的一些附注: 麻省理工学院提供了关于算法的优秀课程在线上。其中一个关于红黑树的课程链接为http://www.youtube.com/watch?v=iumaOUqoSCk


谢谢你的回答Peter。我已经熟悉你给我的那些网站,包括MIT的讲座。然而,这些都是自下而上的实现方式,而我需要理解自上而下的实现方式,因为我用这种方式完成了插入函数,并且也更熟悉自上而下的思考方式。我在互联网上搜寻了很久,想找出比我在问题中发布链接的算法更好的算法,但似乎自下而上的方法更受欢迎!你认为我的解释清楚吗?即使进行调试,我也看不出代码有什么问题 :/ - Bernice

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接