红黑树Java实现

纯比着葫芦画着瓢打出来的代码,没啥实际意义,当自己熟悉代码用了,注释里写了一些理解,不很透彻,错误也很多.


package Search;

import edu.princeton.cs.algs4.Queue;
import java.util.*;

//红黑二叉查找树的实现.这个和二三查找树之间的连接不太好找,总是不能一一对应起来,不知道详细的对应步骤.
public class redblackSearchtree<Key extends Comparable<Key>, Value> {
    private static final boolean RED = true;
    private static final boolean BLACK = false;
    Node root;

    public class Node {
        Key key;
        Value val;
        Node left, right;
        int N;
        boolean color;

        Node(Key key, Value val, int N, boolean color) {
            this.key = key;
            this.val = val;
            this.N = N;
            this.color = color;
        }
    }

    // 判断指向一个结点的链接是否是红色的.
    private boolean isRed(Node x) {
        if (x == null)
            return false;
        return x.color == RED;
    }

    // 旋转的实现,这是算法的核心部分.
    // 向左旋转,将右边的红链接的树旋转到左边
    Node rotateLeft(Node h) {
        Node x = h.right;
        h.right = x.left;
        x.left = h;
        x.color = h.color; // 将原根节点的链接颜色赋给新 根节点,以确保其保持不变.
        h.color = RED;
        x.N = h.N;
        h.N = 1 + size(h.left) + size(h.right); // 需要重写一下size方法,因为继承过来的方法对现在的Node
                                                // 不适用
        return x;
    }

    // 向右旋转
    Node rotateRight(Node h) {
        Node x = h.left;
        h.left = x.right;
        x.left = h;
        x.color = h.color;
        h.color = RED;
        x.N = h.N;
        h.N = 1 + size(h.left) + size(h.right); // 需要重写一下size方法,因为继承过来的方法对现在的Node
                                                // 不适用
        return x;
    }

    // 转换颜色,作用于左右链接都是红色的时候.将他们全部转换为黑色.
    // 颜色取反
    void flipColors(Node h) {
        h.color = !h.color;
        h.left.color = !h.left.color;
        h.right.color = !h.right.color;
        // 将父节点的链接设为红色(其实不太懂为什么),百度得知这是为了将一个4-结点的中键上移,将其转化为红色后,就和上面的2-结点组合成一个新的3-结点.这就和2-3树中的内容相对应起来了
    }

    // 插入操作
    public Node put(Node h, Key key, Value val) {
        if (h == null) // 只要Node h 未实例化,那么他就是null,每进行一次插入都会进行这个操作,并且默认将链接设置为红色.
            return new Node(key, val, 1, RED);

        // 先进行查找,查找到进行更新,直到查找到一个null值,递归调用,使得树可以成功构造
        int cmp = key.compareTo(h.key);
        if (cmp < 0)
            h.left = put(h.left, key, val);
        else if (cmp > 0)
            h.right = put(h.right, key, val);
        else
            h.val = val;

        // 进行旋转操作,使一个普通二叉树转换为红黑二叉树.
        if (isRed(h.right) && !isRed(h.left))
            h = rotateLeft(h); // 如果右链接是红色的,左转
        if (isRed(h.left) && isRed(h.left.left))
            h = rotateRight(h); // 如果出现两层红链接,右转.
        if (isRed(h.left) && isRed(h.right))
            flipColors(h); // 如果出现4-结点,转换为三个2-结点(左右都是红色的)

        h.N = size(h.left) + size(h.right) + 1; // 更新结点总数
        return h;
    }

    // 删除最小键的代码
    // 这是难点,一直没有搞太清楚

    // 这个方法定义了两种情况,第一种情况直接颜色反转,使两个黑链接变为红链接,此时就变成了4-结点.
    // 第二种情况,在这个结点是2-结点的前提下,如果这个结点的右子树的左子树为红色,则将这个结点借给当时查找的左子树.
    private Node moveRedLeft(Node h) {
        // 假设结点h为红色,h.left和h.left.left都是黑色.表示当前结点就是根节点,最小结点是这个结点的左子树
        // 在满足这个的前提下首先flipColors(h);使其变为3-结点,此时就可以进行删除了
        flipColors(h);
        // flipColors完成之后,还需要进行下一步检查,即判断时候情况更为复杂,无论哪种情况 flipColors都是要执行的.
        if (isRed(h.right.left)) {
            h.right = rotateRight(h.right);
            h = rotateLeft(h);

            // 原来一直在这一步上很困惑,不知道怎么弄.看了源码才想明白.
            // 书本上少打了这一步,这是挺重要的一步,使借来链之后的树恢复正常状态.因为前面做了一步反转颜色
            // 另外,结点和他的链接是对应在一起的..即一个结点的链接是红色的,那么无论怎么旋转他都是红色的.
            flipColors(h);
        }
        return h;

    }

    public void deleteMin() {
        if (!isRed(root.left) && !isRed(root.right))
            root.color = RED;
        root = deleteMin(root);
        if (!isEmpty())
            root.color = BLACK;
    }

    private Node deleteMin(Node h) {
        // 直到遇到h.left== null,这说明没有比当前键更小的了,返回null,表示这个已经被删除了.
        if (h.left == null)
            return null;

        // 这是重点方法,所做的转换都在这一步,这是从上向下的转换,如果碰见一个2-结点,则将他们转换成3-或者4-结点
        if (!isRed(h.left) && !isRed(h.left.left))
            h = moveRedLeft(h);

        // 递归调用这个方法,直到遇见null值.
        h.left = deleteMin(h.left);

        // 返回一个平衡操作后的树,此时树就被重构了一次.每递归一次就平衡一次.
        return balance(h);
    }

    // 删除最大键的代码
    private Node moveRedRight(Node h) {
        // 首先反转颜色.
        flipColors(h);

        // 和删除最小键一样,如果最大的那个子树为2-,那么由于上面的反转过了,
        //如果left.left是红色的,那么说明左边是一个4-结点,右边是一个1-几点,需要将两个都变成3-结点.这次从左边借来一个链
        if (isRed(h.left.left)) {
            h = rotateRight(h);
            flipColors(h);
        }
        return h;
    }
    private void deleteMax() {
        //如果为空
        if(isEmpty()) throw new NoSuchElementException("BST underflow");
        //如果左右子树都不为红色
        if(!isRed(root.left) && !isRed(root.right))
            root.color = RED;

        root = deleteMax(root);
        if(!isEmpty()) root.color = BLACK;

    }
    private Node deleteMax(Node h){
        //只要是红色的链接都进行右旋转
        if(isRed(h.left))
            h = rotateRight(h);
        //这是已经找到并删除最大键
        if(h.right == null)
            return null;
        //如果当前结点的右结点和右结点的左节点的链接都是黑色,则从左边树中借一个到右边树中
        if (!isRed(h.right) && !isRed(h.right.left))
            h = moveRedRight(h);
        h.right = deleteMax(h.right);
        return balance(h);
        }

    //删除具体键的方法
    //这个实在太难,必须找时间好好看一下.
    public void delete(Key key){
        if(key == null) throw new  IllegalArgumentException("argument to delete() is null");
        //如果不存在这个key,返回
        if(!contains(key)) return;

        //先初始化根节点,将根节点转化为红色(后面可以balance回来)
        if(!isRed(root.left) && !isRed(root.right))
            root.color = RED;

        root = delete(root,key);
        if(!isEmpty) root.color = BLACK;
    }
    private Node delete(Node h,Key key){
        //如果查找的键小于当前键,向左移动,继续查找.
    if(key.compareTo(h.key)<0){
        if(!isRed(h.left) && !isRed(h.left.left))
            h = moveRedLeft(h);
        h.left = delete(h.left,key);
        //如果查找的值大于等于当前键
    }else{
        //这一步旋转是因为要删除比当前键更大的key,在右子树中必须有红色链接.
        if(isRed(h.left))
            h = rotateRight(h);
        //这是已经在树底查找到了该键,删除了这个,那么只有左节点可以继任
        //将这一步放在前面可能也是因为如果到达树底,那么左右子树就是黑色的.无法判断第三步
        if(key.compareTo(h.key) == 0 && (h.right ==null))
            return null;
        //待删除的键不在树底的情况下.先进行旋转,确保右边必有红子树,因为如果刚好下一个就是要删除的2-结点,就必须借来一个结点使他成为一个3-结点,如此才能删除.
        if(!isRed(h.right) && !isRed(h.right.left))
            h = moveRedRight(h);
        if(key.compareTo(h.key )==0){
            Node x = min(h.right);
            h.key = x.key;
            h.key = x.key;
            h.val = x.val;
            // h.val = get(h.right, min(h.right).key);
            // h.key = min(h.right).key;
            h.right = deleteMin(h.right);
        }

        //在右子树中继续搜索.
         else h.right = delete(h.right, key);
    }
     return balance(h);
}

    // 返回平衡后的树,递归调用.
    private Node balance(Node h) {
        // assert (h != null);

        if (isRed(h.right))
            h = rotateLeft(h);
        if (isRed(h.left) && isRed(h.left.left))
            h = rotateRight(h);
        if (isRed(h.left) && isRed(h.right))
            flipColors(h);

        h.size = size(h.left) + size(h.right) + 1;
        return h;
    }
}

其他的方法与普通的二叉树相同.
错误很多,我备忘一下.

猜你喜欢

转载自blog.csdn.net/Ad5226236/article/details/78762368