红黑树

简介: 红黑树概述
  1. 红黑树概述

红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:

【1】性质1. 节点是红色或黑色。
【2】性质2. 根节点是黑色。
【3】性质3 每个叶节点(NIL)是黑色的。
【4】性质4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
【5】性质5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
复制代码

2.HashMap源码分析
hashmap实现树化代码如下:
final void treeifyBin(Node<K,V>[] tab, int hash) {

    int n, index; Node<K,V> e;
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
    // resize()扩容。
        resize();
    // 通过hash求出bucket的位置。
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        TreeNode<K,V> hd = null, tl = null;
        do {
        // 将每个节点包装成TreeNode。
            TreeNode<K,V> p = replacementTreeNode(e, null);
            if (tl == null)
                hd = p;
            else {
            // 将所有TreeNode连接在一起此时只是链表结构。
                p.prev = tl;
                tl.next = p;
            }
            tl = p;
        } while ((e = e.next) != null);
        if ((tab[index] = hd) != null)
        // 对TreeNode链表进行树化。
            hd.treeify(tab);
    }
}
    TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
    return new TreeNode<>(p.hash, p.key, p.value, next);
}

复制代码
这个方法所做的事情就是将刚才九个项以链表的方式连接在一起,然后通过它构建红黑树。可以看出出真正的维护红黑树结构的方法并没有在HashMap中,全部都在TreeNode类内部。
final void treeify(Node<K,V>[] tab) {

        TreeNode<K,V> root = null;
        // 以for循环的方式遍历刚才我们创建的链表。
        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;
            }
            // x即为当前访问链表中的项。
            else {
                K k = x.key;
                int h = x.hash;
                Class<?> kc = null;
                //此时红黑树已经有了根节点,上面获取了当前加入红黑树的功Bkey和hash值进入核心循环,这里从root开始,是以一个自质向下的方式遍历添加 for循环设有控制条件,由代码内reak.跳出循环 
                for (TreeNode<K,V> p = root;;) {
                    int dir, ph;
                    K pk = p.key;
                    if ((ph = p.hash) > h)
                        dir = -1;
                    else if (ph < h)
                        dir = 1;
                    else if ((kc == null &&
                              (kc = comparableClassFor(k)) == null) ||
                             (dir = compareComparables(kc, k, pk)) == 0)
                        dir = tieBreakOrder(k, pk);

                    TreeNode<K,V> xp = p;
                    // 找到符合x添加条件的节点。
                    if ((p = (dir <= 0) ? p.left : p.right) == null) {
                        x.parent = xp;
                        // 如果xp的hash值大于x的hash值,将x添加在xp的左边。
                        if (dir <= 0)
                            xp.left = x;
                        else //反之则在右边
                            xp.right = x;
                            // 维护添加后红黑树的红黑结构。
                        root = balanceInsertion(root, x);
                        break;
                    }
                }
            }
        }
        moveRootToFront(tab, root);
    }

复制代码
第一次循环会将链表中的首节点作为红黑树的根,而后的循环会将链表中的的项通过比较hash值然后连接到相应树节点的左边或者右边,插入可能会破坏树的结构所以接着执行balanceInsertion
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,

                                                TreeNode<K,V> x) {
x.red = true; // 新插入的节点标为红色

/*
 * 这一步即定义了变量,又开起了循环,循环没有控制条件,只能从内部跳出
 * xp:当前节点的父节点、xpp:爷爷节点、xppl:左叔叔节点、xppr:右叔叔节点
 */
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) { 

    // 如果父节点为空、说明当前节点就是根节点,那么把当前节点标为黑色,返回当前节点
    if ((xp = x.parent) == null) { // L1
        x.red = false;
        return x;
    }

    // 父节点不为空
    // 如果父节点为黑色 或者 【(父节点为红色 但是 爷爷节点为空) -> 这种情况何时出现?】
    else if (!xp.red || (xpp = xp.parent) == null) // L2
        return root;
    if (xp == (xppl = xpp.left)) { // 如果父节点是爷爷节点的左孩子  // L3
        if ((xppr = xpp.right) != null && xppr.red) { // 如果右叔叔不为空 并且 为红色  // L3_1
            xppr.red = false; // 右叔叔置为黑色
            xp.red = false; // 父节点置为黑色
            xpp.red = true; // 爷爷节点置为红色
            x = xpp; // 运行到这里之后,就又会进行下一轮的循环了,将爷爷节点当做处理的起始节点 
        }
        else { // 如果右叔叔为空 或者 为黑色 // L3_2
            if (x == xp.right) { // 如果当前节点是父节点的右孩子 // L3_2_1
                root = rotateLeft(root, x = xp); // 父节点左旋,见下文左旋方法解析
                xpp = (xp = x.parent) == null ? null : xp.parent; // 获取爷爷节点
            }
            if (xp != null) { // 如果父节点不为空 // L3_2_2
                xp.red = false; // 父节点 置为黑色
                if (xpp != null) { // 爷爷节点不为空
                    xpp.red = true; // 爷爷节点置为 红色
                    root = rotateRight(root, xpp);  //爷爷节点右旋,见下文右旋方法解析
                }
            }
        }
    }
    else { // 如果父节点是爷爷节点的右孩子 // L4
        if (xppl != null && xppl.red) { // 如果左叔叔是红色 // L4_1
            xppl.red = false; // 左叔叔置为 黑色
            xp.red = false; // 父节点置为黑色
            xpp.red = true; // 爷爷置为红色
            x = xpp; // 运行到这里之后,就又会进行下一轮的循环了,将爷爷节点当做处理的起始节点 
        }
        else { // 如果左叔叔为空或者是黑色 // L4_2
            if (x == xp.left) { // 如果当前节点是个左孩子 // L4_2_1
                root = rotateRight(root, x = xp); // 针对父节点做右旋,见下文右旋方法解析
                xpp = (xp = x.parent) == null ? null : xp.parent; // 获取爷爷节点
            }
            if (xp != null) { // 如果父节点不为空 // L4_2_4
                xp.red = false; // 父节点置为黑色
                if (xpp != null) { //如果爷爷节点不为空
                    xpp.red = true; // 爷爷节点置为红色
                    root = rotateLeft(root, xpp); // 针对爷爷节点做左旋
                }
            }
        }
    }
}

}
复制代码
第一次循环会将链表中的首节点作为红黑树的根,而后的循环会将链表中的的项通过比较hash值然后连接到相应树节点的左边或者右边,插入可能会破坏树的结构所以接着执行balanceInsertion

  1. 左旋

以结点p作为支点进行左旋,其左子结点不变,右子结点变为p的右子节点的左子结点,且p的父结点变为左子结点。 支点为p,其父结点为pp,右子结点和左子结点为r和l,r的左右孩子分别为rl和rr。则示意图如下:

 源码如下
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,

                                          TreeNode<K,V> p) {
TreeNode<K,V> r, pp, rl;
if (p != null && (r = p.right) != null) { // 要左旋的节点以及要左旋的节点的右孩子不为空
    if ((rl = p.right = r.left) != null) // 要左旋的节点的右孩子的左节点 赋给 要左旋的节点的右孩子 节点为:rl
        rl.parent = p; // 设置rl和要左旋的节点的父子关系【之前只是爹认了孩子,孩子还没有答应,这一步孩子也认了爹】

    // 将要左旋的节点的右孩子的父节点  指向 要左旋的节点的父节点,相当于右孩子提升了一层,
    // 此时如果父节点为空, 说明r 已经是顶层节点了,应该作为root 并且标为黑色
    if ((pp = r.parent = p.parent) == null) 
        (root = r).red = false;
    else if (pp.left == p) // 如果父节点不为空 并且 要左旋的节点是个左孩子
        pp.left = r; // 设置r和父节点的父子关系【之前只是孩子认了爹,爹还没有答应,这一步爹也认了孩子】
    else // 要左旋的节点是个右孩子
        pp.right = r; 
    r.left = p; // 要左旋的节点  作为 他的右孩子的左节点
    p.parent = r; // 要左旋的节点的右孩子  作为  他的父节点
}
return root; // 返回根节点

}
复制代码
步骤1.如果结点p为空或者p不存在右子结点r,则直接返回 
步骤2.如果rl不为空,则使p的右边等于rl。否则如果rl为空则不用操作
 步骤3.如果p没有父结点,即他本身就是根节点,那么设置r为根节点,并设置r为黑色.如果p父结点存在且p为父结点的左子结点,则父结点的左子结点设置为r。如果p父结点存在且p为父结点的右子节点,则父结点的右子结点设置为r。
 步骤4.最后把r的左子节点设置为p,p的父结点设置为r
4.右旋
以结点p作为支点右旋,其右子结点不变,左子结点变为其左子结点的右子结点,且p的父结点变为右子结点。
如图所示

源码如下
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
        lr.parent = p; // 设置lr和要右旋的节点的父子关系【之前只是爹认了孩子,孩子还没有答应,这一步孩子也认了爹】

    // 将要右旋的节点的左孩子的父节点  指向 要右旋的节点的父节点,相当于左孩子提升了一层,
    // 此时如果父节点为空, 说明l 已经是顶层节点了,应该作为root 并且标为黑色
    if ((pp = l.parent = p.parent) == null) 
        (root = l).red = false;
    else if (pp.right == p) // 如果父节点不为空 并且 要右旋的节点是个右孩子
        pp.right = l; // 设置l和父节点的父子关系【之前只是孩子认了爹,爹还没有答应,这一步爹也认了孩子】
    else // 要右旋的节点是个左孩子
        pp.left = l; // 同上
    l.right = p; // 要右旋的节点 作为 他左孩子的右节点
    p.parent = l; // 要右旋的节点的父节点 指向 他的左孩子
}
return root;

}
复制代码
步骤1.如果结点p为空或者p不存在左子结点r,则直接返回 
步骤2.如果lr不为空,则使p的左边等于lr。否则如果lr为空则不用操作
 步骤3.如果p没有父结点,即他本身就是根节点,那么设置l为根节点,并设置l为黑色.如果p父结点存在且p为父结点的左子结点,则父结点的左子结点设置为l。如果p父结点存在且p为父结点的右子节点,则父结点的右子结点设置为l。 
步骤4.最后把l的左子节点设置为p,p的父结点设置为l

相关文章
|
7月前
|
关系型数据库 容器
红黑树的简单介绍
红黑树的简单介绍
51 0
|
7月前
|
存储 应用服务中间件 调度
随处可见的红黑树详解
随处可见的红黑树详解
74 0
|
7月前
|
存储 调度
红黑树总结
红黑树总结
70 0
|
16天前
|
存储 C++
【C++】红黑树
红黑树是一种自平衡二叉搜索树,通过节点颜色(红或黑)及特定规则维持平衡,确保操作效率。其性质包括:每个节点非红即黑,根节点必黑,红节点的子节点必黑,从任一节点到其每个叶子的所有路径含相同数量的黑节点。实现上,通过节点结构定义、基本操作(插入、删除、旋转等)、维护平衡性质等步骤完成。代码示例展示了节点定义、插入操作及旋转调整方法。
21 2
【C++】红黑树
|
2月前
|
应用服务中间件 Linux 调度
红黑树
红黑树
23 0
|
6月前
|
关系型数据库 C++
【c++】红黑树
【c++】红黑树
24 0
|
7月前
|
C++ 容器
【C++】红黑树(上)
【C++】红黑树(上)
|
7月前
|
Linux C++
红黑树的实现
红黑树的实现
43 2
|
7月前
|
调度
随处可见的红黑树
随处可见的红黑树
|
7月前
|
存储 Linux 调度
C++【红黑树】
C++【红黑树】
64 0