红黑树学习笔记

红黑树是平衡二叉查找树的一种

BST(Binary Search Tree)是一棵二叉树,它的左子节点的值比父节点的值要小,右节点的值要比父节点的值大。它的高度决定了它的查找效率。

二叉查找树增删查改的时间复杂度为O(logN)(其中N为节点数),最坏的情况下为O(N)。

平衡二叉查找树

倾斜的二叉查找树

 

public class BSTTest {
    static class Node{
        public String content;
        public Node parent;
        public Node left;
        public Node right;

        public Node(String content) {
            this.content = content;
        }
    }
    public Node root;
    //BST查询操作
    public Node search(String content){
        Node r=root;
        while (r!=null){
            if(r.content.equals(content)){
                return r;
            }else  if (content.compareTo(r.content)>1){
                r=r.right;
            }else if(content.compareTo(r.content)<=1){
                r=r.left;
            }
        }
        return null;
    }
    
    //插入的操作
    public void insert(String content){
        Node node = new Node(content);
        Node r=root;
        Node parent=null;
        
        if(r==null){
            root=node;
            return;
        }
        while(r!=null){
            parent=r;
            if(node.content.compareTo(r.content)>1){
                r=r.right;
            }else if (node.content.compareTo(r.content)<=1){
                r=r.left;
            }else{
                r=r.left;
            }
        }
        
        if(parent.content.compareTo(node.content)>1){
            parent.left=node;
            node.parent=parent;
        }else {
            parent.right=node;
            node.parent=parent;
        }
    }
    
}

红黑树-RBTree

基于BST存在的问题,一种新的树——平衡二叉查找树(Balanced BST)产生了。平衡树在插入和删除的时候,会通过旋转操作将高度保持在logN。其中两款具有代表性的平衡树分别为AVL树和红黑树。AVL树由于实现比较复杂,而且插入和删除性能差,在实际环境下的应用不如红黑树。

红黑树(Red-Black Tree,以下简称RBTree)的实际应用非常广泛,比如Linux内核中的完全公平调度器、高精度计时器、ext3文件系统等等,各种语言的函数库如Java的TreeMap和TreeSet,C++ STL的map、multimap、multiset等。

红黑树的定义如下:

  1. 每个结点或是红的,或是黑的
  2. 根节点是黑的
  3. 每个叶结点是黑的
  4. 如果一个结点是红的,则它的两个儿子都是黑的
  5. 对每个结点,从该结点到其子孙节点的所有路径上包含相同数目的黑结点

插入数据

向红黑树中插入新的结点。具体做法是,将新结点的 color 赋为红色,然后以BST的插入方法插入到红黑树中去。之所以将新插入的结点的颜色赋为红色,是因为:如果设为黑色,就会导致根到叶子的路径上有一条路上,多一个额外的黑结点,这个是很难调整的。但是设为红色结点后,可能会导致出现两个连续红色结点的冲突,那么可以通过颜色调换和树旋转来调整,这样简单多了。

设要插入的结点为N,其父结点为P,其 祖父结点为G,其父亲的兄弟结点为U(即P和U 是同一个结点的两个子结点)。如果P是黑色的,则整棵树不必调整就已经满足了红黑树的所有性质。如果P是红色的(可知,其父结点G一定是黑色的),则插入N后,违背了红色结点只能有黑色孩子的性质,需要进行调整。

调整时分以下三种情况:

新结点N的叔叔结点U是红色的

处理方式是:将P和U修改为黑色,G修改为红色

现在新结点N有了一个黑色的父结点P,因为通过父结点P或叔父结点U的任何路径都必定通过祖父结点G,在这些路径上的黑结点数目没有改变。

但是,红色的祖父结点G的父结点也有可能是红色的,这就违反了性质3。

 

新结点N的叔叔结点U是黑色的,且N是左孩子。

处理方式:对祖父结点G进行一次右旋转

父节点是黑色的,不用做出调整

父节点是红色的

        1、叔叔是空的,旋转+变色(G和P进行变色)

        2、叔叔是红色的,父节点+叔叔节点变为黑色,祖父节点变为红色

        3、叔叔是黑色(是存在子树进行调整的时候),旋转+变色

java中的关于红黑树

红黑树的插入

static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                            TreeNode<K,V> x) {
	// 新节点默认为红色
    x.red = true;
    // xp表示x的父结点,xpp表示x的祖父结点,xppl表示xpp的左孩子结点,xppr表示xpp的右孩子结点
    for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
        // 如果x没有父结点,则表示x是第一个结点,自动为根节点,根节点为黑色
        if ((xp = x.parent) == null) {
            x.red = false;
            return x;
        }
        // 如果父结点不是红色(就是黑色),或者x没有祖父节点,那么就证明x是第二层节点,父节点为根节点
        // 这种情况无需就行操作
        else if (!xp.red || (xpp = xp.parent) == null)
            return root;
        
        // 进入到这里,表示x的父节点为红色
        
        // 如果x的父节点是祖父结点的左孩子
        if (xp == (xppl = xpp.left)) {
            // 祖父结点的右孩子,也就是x的叔叔节点不为空,且为红色
            if ((xppr = xpp.right) != null && xppr.red) {
                // 父节点和叔叔节点都为红色,只需要变色,且将x替换为祖父节点然后进行递归
                xppr.red = false;
                xp.red = false;
                xpp.red = true;
                x = xpp;
            }
            // 如果叔叔节点为空,或者为黑色
            else {
                // 如果x节点为xp的右孩子
                if (x == xp.right) {
                    // 先进行左旋,并且把x替换为xp进行递归,在左旋的过程中产生了新的root节点
                    root = rotateLeft(root, x = xp);
                    // x替换后,修改xp和xpp
                    xpp = (xp = x.parent) == null ? null : xp.parent;
                }
                // 如果x本来是左孩子,或者已经经过了上面的左旋之后,进行变色加右旋
                if (xp != null) {
                    xp.red = false;
                    if (xpp != null) {
                        xpp.red = true;
                        root = rotateRight(root, xpp);
                    }
                }
            }
        }
        // 如果x的父节点是祖父结点的右孩子
        else {
            if (xppl != null && xppl.red) {
                xppl.red = false;
                xp.red = false;
                xpp.red = true;
                x = xpp;
            }
            else {
                if (x == xp.left) {
                    root = rotateRight(root, x = xp);
                    xpp = (xp = x.parent) == null ? null : xp.parent;
                }
                if (xp != null) {
                    xp.red = false;
                    if (xpp != null) {
                        xpp.red = true;
                        root = rotateLeft(root, xpp);
                    }
                }
            }
        }
    }
}

红黑树的左右旋操作

static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
                                      TreeNode<K,V> p) {
    // pp是祖父结点
    // p是待旋转结点
    // r是p的右孩子结点
    // rl是r的左孩子结点
    TreeNode<K,V> r, pp, rl;
    if (p != null && (r = p.right) != null) {
        // 如果rl不为空,则设置p.right=rl
        if ((rl = p.right = r.left) != null)
            rl.parent = p;
        // 如果祖父结点为null,那么r设置为黑色,r左旋之后即为root节点
        if ((pp = r.parent = p.parent) == null)
            (root = r).red = false;
        // 如果待旋转结点是左孩子节点
        else if (pp.left == p)
            pp.left = r;
        // 如果待旋转结点为右孩子
        else
            pp.right = r;
        r.left = p;
        p.parent = r;
    }
    return root;
}

static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
                                       TreeNode<K,V> p) {
    TreeNode<K,V> l, pp, lr;
    if (p != null && (l = p.left) != null) {
        if ((lr = p.left = l.right) != null)
            lr.parent = p;
        if ((pp = l.parent = p.parent) == null)
            (root = l).red = false;
        else if (pp.right == p)
            pp.right = l;
        else
            pp.left = l;
        l.right = p;
        p.parent = l;
    }
    return root;
}

HashMap中的树化

final void treeify(Node<K,V>[] tab) {
    TreeNode<K,V> root = null;
    // 遍历当前链表
    for (TreeNode<K,V> x = this, next; x != null; x = next) {
        next = (TreeNode<K,V>)x.next;
        x.left = x.right = null;
        if (root == null) {
            x.parent = null;
            x.red = false;
            root = x;
        }
        else {
            K k = x.key;
            int h = x.hash;
            Class<?> kc = null;
            // 每遍历一个链表上的元素就插入到红黑树中
            for (TreeNode<K,V> p = root;;) {
                int dir, ph;
                K pk = p.key;
                
                // 判断待插入结点应该插入在左子树还是右子树
                // 先比较hash值
                if ((ph = p.hash) > h)
                    dir = -1;
                else if (ph < h)
                    dir = 1;
                // 如果hash值相等,然后比较k.compareTo(pk)
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0)
                    // 如果还相等则再比较identityHashCode
                    dir = tieBreakOrder(k, pk);

                // 根据dir的值就知道了待插入结点该插在左子树还是右子树了
                TreeNode<K,V> xp = p;
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    x.parent = xp;
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;
                    root = balanceInsertion(root, x);
                    break;
                }
            }
        }
    }
    moveRootToFront(tab, root);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值