从最初的感动开始--JAVA算法【3】--不简单的红黑树

红黑树细节颇多,这里我可能没法子全部展开。

红黑数和普通二叉查找树的重大性能改进在于,它可以保持任何空节点至根节点的黑链数量一致。换句话说,在进行查找树种的任何元素时,总能在o(lgN)时间复杂度内完成,且与元素插入或删除操作的顺序无关。二叉查找树的主要限制问题,在于树结构会随着插入次序改变而改变,如果树结构偏向一边而不平衡,查找效率将急剧下降。

红黑数在当前分布式大规模集群的环境中依然有其用武之地,从3-节点结构向多节点结构转变,便能进一步极大地压缩树的深度(改变的是对数的底数),而代价是对树的一系列操作将会变得更为复杂。

最好的理解红黑数的方法,是类比2-3树的结构,并由此套用到红黑树当中。直接阅读红黑树的源代码过于抽象而很容易迷失其中。

在红黑树插入过程中,大体的插入逻辑如图:
红黑树插入逻辑
插入过程中,最重要的思想是从叶子节点插入后,不断向上迭代,直至遇见一个2-节点或者根节点。这一部分的主要代码如下:

    //put   
    public void put(Key key, Value val){
        root = put(root, key, val);
        root.color = BLACK; //root node is always black
    }

    private Node put(Node h, Key key, Value val){
        //create new node
        if (h == null) 
            return new Node(key, val, 1, RED);

        //find the place to insert
        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;

        //modify the color 
        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);

        //update the N of each node
        h.N = 1 + size(h.left) + size(h.right);
        return h;
    }

此外,红黑树中比插入逻辑更困难的是删除逻辑。
书本中所介绍的删除逻辑分成了三个步骤:1.首先实现删除全树最小节点 2.接着实现删除全树最大节点 3.在前两步的基础上,实现删除任意节点的函数。

其中,第三步的主要思想如下:
1.如果需要删除的节点比当前节点小,调用moveRedLeft()函数后,保证需要删除的节点不是一个2-节点,并不断向其靠近,直至当前节点即为需要删除的节点
2.当需要删除的节点大于当前节点,同理调用deleteMax()当中的moveRedRight(),并不断向其靠近,直至当前节点即为需要删除的节点
3.当当前节点为所需删除的节点时,若节点右节点为空,则直接删除(之前的算法能够保证该节点的左节点同样为空,将该节点直接删除不会引起其他节点的丢失);若该节点右节点非空,则将该节点与其右子树中的最小元素的值赋值给该节点,并对该节点的右子树调用deleteMin(h.right),将其最小元素删除,这样变实现了制定元素的删除任务。

    //********************
    //deleteMin()
    private Node moveRedLeft(Node h){
        // Assuming that h is red and both h.left and h.left.left
        // are black, make h.left or one of its children red.
        //flip the colors so that we could build a temp 4-node
        h.color = BLACK;
        h.left.color = RED;
        h.right.color = RED; //step2 step5

        if (isRed(h.right.left)){
            h.right = rotateRight(h.right); //step3
            h = rotateLeft(h); //step4
        }

        //this statement not included in the book
        //it could reduce the modify times in balance()
        h.left.color = BLACK;

        return h;
    }
    public void deleteMin(){

        bala_cnt = 0;

        if (!isRed(root.left) && !isRed(root.right))
            root.color = RED;  //step1 (use the data in graph3.3.24, the right one,p282
        root = deleteMin(root);
        if (!isEmpty()) root.color = BLACK;

        //print out the modify times in balance()
        System.out.println("balance modify cnt: "+bala_cnt);
    }
    private Node deleteMin(Node h){
        if (h.left == null)
            return null;
        if (!isRed(h.left) && !isRed(h.left.left))
            h = moveRedLeft(h); 
        h.left = deleteMin(h.left);
        return balance(h);
    }

    private Node balance(Node h){
        if (isRed(h.right)) {h = rotateLeft(h); bala_cnt++;} //deleteMin: step6 step7

        if (isRed(h.right) && !isRed(h.left)) {h = rotateLeft(h); bala_cnt++;}
        if (isRed(h.left) && isRed(h.left.left)) {h = rotateRight(h); bala_cnt++; } //deleteMin: step8
        if (isRed(h.left) && isRed(h.right)) {flipColors(h); bala_cnt++;}//deleteMin: step9

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


    //********************
    //deleteMax()
    private Node moveRedRight(Node h){ 
        // Assuming that h is red and both h.right and h.right.left
        // are black, make h.right or one of its children red.
        h.color = BLACK;
        h.left.color = RED;
        h.right.color = RED; 

        if (!isRed(h.left.left))
            h = rotateRight(h);

        //this statement not included in the book
        //it could reduce the modify times in balance()
        h.right.color = BLACK;

        return h;
    }
    public void deleteMax(){
        if (!isRed(root.left) && !isRed(root.right))
            root.color = RED;
        root = deleteMax(root);
        if (!isEmpty()) root.color = BLACK;
    }
    private Node deleteMax(Node h){

        //not like in deleteMin(), following statement will make sure the root will not be removed at first
        //and lead to losing its child(ren) when the tree only has two nodes
        if (isRed(h.left))  
            h = rotateRight(h);//step2 step3

        if (h.right == null)
            return null;
        if (!isRed(h.right) && !isRed(h.right.left))
            h = moveRedRight(h);
        h.right = deleteMax(h.right); //step1
        return balance(h);
    }



    //********************
    //delete()
    public void delete(Key key){
        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{
            if (isRed(h.left))
                h = rotateRight(h); //step1
            if (key.compareTo(h.key) == 0 && (h.right == null))
                return null;
            if (!isRed(h.right) && !isRed(h.right.left))
                h = moveRedRight(h);
            if (key.compareTo(h.key) == 0){ //step2
                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);
    }

不得不说,红黑树是本书所有例子当中逻辑最为复杂的数据结构了,尤其是删除算法,非常讲究技巧。
值得一提的是,我在书本的基础上,对moveRedRight()和moveRedLeft()做了细微的改动,使得在删除任务完成,由底向上拆解之前所生成的临时4-节点过程中(调用balance()),需要调整节点数量有所下降。全局变量bala_cnt便是用来计算balance()中的调整次数而建立的,在改进后其累加次数减少,验证了改进效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值