Java高阶数据结构之红黑树


提示:以下是本篇文章正文内容,Java系列学习将会持续更新

数据结构动态模型https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

一、红黑树的概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是RedBlack通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的

在这里插入图片描述

红黑树的性质:

  1. 最长路径最多是最短路径的2倍
    原因:每条路径黑色节点数相同,则最短路径 = 没有红色节点的路径(一般不会出现这种极端情况);最长路径 = 红色节点最多的路径(由于红色节点不能连续,所以最多也就是和黑色节点数相同)。所以, 最长路径 = 2 x 最短路径 = 2 x 黑色节点数
  2. 每个结点不是红色就是黑色
  3. 根节点是黑色的
  4. 如果一个节点是红色的,则它的两个孩子结点是黑色的【没有2个连续的红色节点
  5. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
  6. 每个叶子结点都是黑色的 (此处的叶子结点指的是空结点)

回到目录…

二、插入和调整

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

  1. 按照二叉搜索的树规则插入新节点
  2. 检测新节点插入后,红黑树的性质是否造到破坏
    因为新节点的默认颜色是红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三不能有连在一起的红色节点,此时需要对红黑树分情况来讨论。

约定: cur为当前节点,p为父节点,u为叔叔节点,g为祖父节点。

情况一: cur为红,p为红,g为黑,u存在且为红

在这里插入图片描述

解决方式:将p,u改为黑,g改为红,然后把g当成cur,继续向上调整。

回到目录…

情况二: cur为红,p为红,g为黑,u不存在/u为黑

在这里插入图片描述

说明: u的情况有两种

  1. 如果u节点不存在,则cur- -定是新插入节点,因为如果cur不是新插入节点,则cur和p一定有一个节点的颜色是黑色,就不满足性质4:每条路径黑色节点个数相同。
  2. 如果u节点存在,则其-定是黑色的,那么cur节点原来的颜色一定是黑色的,现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色改成红色。

解决方式:
p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,
p为g的右孩子,cur为p的右孩子,则进行左单旋转
p、g变色 —— p变黑,g变红

回到目录…

情况三: cur为红,p为红,g为黑,u不存在/u为黑

在这里插入图片描述

解决方式:
p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反,
p为g的右孩子,cur为p的左孩子,则针对p做右单旋转
则转换成了情况2

回到目录…

四、删除

博客园:红黑树的删除
CSDN:红黑树

五、性能分析

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O(logN),红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多

红黑树的应用:

  1. java集合框架中的:TreeMap、TreeSet底层使用的就是红黑树
  2. C++ STL库 – map/set、mutil_map/mutil_set
  3. linux内核:进程调度中使用红黑树管理进程控制块,epoll在内核中实现时使用红黑树管理事件块
  4. 其他一些库:比如nginx中用红黑树管理timer等

回到目录…

六、完整代码

public class RBTree {

    class RBTreeNode {
        RBTreeNode left = null;
        RBTreeNode right = null;
        RBTreeNode parent = null;
        COLOR color; // 节点的颜色
        int val;

        public RBTreeNode(int val) {
            this.val = val;
            // 默认新增节点为红色
            this.color = COLOR.RED;
        }
    }

    public RBTreeNode root;

    // 插入
    public boolean insert(int val) {
        RBTreeNode node = new RBTreeNode(val);
        if (root == null) {
            this.root = node;
            root.color = COLOR.BLACK;
            return true;
        }

        RBTreeNode parent = null;
        RBTreeNode cur = root;
        while (cur != null) {
            if (val == cur.val) {
                return false;
            } else if (val < cur.val) {
                parent = cur;
                cur = cur.left;
            } else {
                parent = cur;
                cur = cur.right;
            }
        }
        // 此时,cur = null
        if (val < parent.val) {
            parent.left = node;
        } else {
            parent.right = node;
        }
        node.parent = parent;
        cur = node;

        // 调整颜色
        // 新节点插入后,如果parent节点的颜色是红色,一定违反性质三
        while (parent != null && parent.color == COLOR.RED) {
            RBTreeNode grandFather = parent.parent;
            if (parent == grandFather.left) {
                RBTreeNode uncle = grandFather.right;
                if (uncle != null && uncle.color == COLOR.RED) {
                    // 情况一:叔叔节点存在且为红,
                    // 解决方式:将叔叔和父节点改为黑色,祖父节点改为红色
                    // 如果祖父的双亲节点的颜色是红色,需要继续往上调整
                    parent.color = COLOR.BLACK;
                    uncle.color = COLOR.BLACK;
                    grandFather.color = COLOR.RED;
                    // 把 g当成cur,继续向上调整
                    cur = grandFather;
                    parent = cur.parent;
                } else { // 此时,叔叔节点不存在 || 叔叔节点存在,但是颜色是黑色
                    // 再讨论cur是左孩子还是右孩子 ?
                    if (cur == parent.left) {
                        // 情况二
                        rotateR(grandFather);
                        parent.color = COLOR.BLACK;
                        grandFather.color = COLOR.RED;
                    } else {
                        // 情况三
                        rotateL(parent);
                        RBTreeNode temp = parent;
                        parent = cur;
                        cur = temp;
                    }
                }
            } else {
                // parent == grandFather.right
                // 以上情况是插入左边,此时是插入到右边,原理一样
                RBTreeNode uncle = grandFather.left;
                if (uncle != null && uncle.color == COLOR.RED) {
                    // 情况一:叔叔节点存在且为红,
                    // 解决方式:将叔叔和父节点改为黑色,祖父节点改为红色
                    // 如果祖父的双亲节点的颜色是红色,需要继续往上调整
                    parent.color = COLOR.BLACK;
                    uncle.color = COLOR.BLACK;
                    grandFather.color = COLOR.RED;
                    // 把 g当成cur,继续向上调整
                    cur = grandFather;
                    parent = cur.parent;
                } else { // 此时,叔叔节点不存在 || 叔叔节点存在,但是颜色是黑色
                    // 再讨论cur是左孩子还是右孩子 ?
                    if (cur == parent.right) {
                        // 情况二
                        rotateL(grandFather);
                        parent.color = COLOR.BLACK;
                        grandFather.color = COLOR.RED;
                    } else {
                        // 情况三
                        rotateR(parent);
                        RBTreeNode temp = parent;
                        parent = cur;
                        cur = temp;
                    }
                }
            }
        }
        // 在上述循环更新期间,可能会将根节点给成红色,因此此处必须将根节点改为黑色
        root.color = COLOR.BLACK;
        return true;
    }

    // 左单旋
    private void rotateL(RBTreeNode p) {
        // p 的母节点
        RBTreeNode pp = p.parent;
        // p 的右孩子
        RBTreeNode subR = p.right;
        // subR 的左孩子,可能不存在
        RBTreeNode subRL = subR.left;

        // subR 提上去
        if (pp == null) {
            this.root = subR;
        } else if (pp.left == p) {
            pp.left = subR;
        } else {
            // pp.right == parent
            pp.right = subR;
        }
        subR.parent = pp;
        // p 作为 subR 的左孩子
        subR.left = p;
        p.parent = subR;
        // p 与 subRL 连接
        p.right = subRL;
        if (subRL != null) {
            subRL.parent = p;
        }
    }

    // 右单旋
    private void rotateR(RBTreeNode p) {
        // p 的父节点
        RBTreeNode pp = p.parent;
        // p 的左孩子
        RBTreeNode subL = p.left;
        // subL 的右孩子,可能不存在
        RBTreeNode subLR = subL.right;

        if (pp == null) {
            this.root = subL;
        } else if (pp.left == p) {
            pp.left = subL;
        } else {
            pp.right = subL;
        }
        subL.parent = pp;
        subL.right = p;
        p.parent = subL;
        p.left = subLR;
        if (subLR != null) {
            subLR.parent = p;
        }
    }

    /**
     * 打印二叉树, 中序遍历的结果
     **/
    @Override
    public String toString() {
        List<Integer> list = new ArrayList<>();
        inOrder(list, root);
        return list.toString() + "\n" + "是否标准红黑树: " + isValidRBTree();
    }
    // 中序遍历
    private void inOrder(List<Integer> list, RBTreeNode root) {
        if (root == null) {
            return;
        }
        inOrder(list, root.left);
        list.add(root.val);
        inOrder(list, root.right);
    }
    /**
     * 检验是否符合红黑树的性质
     */
    private boolean isValidRBTree() {
        // 空树也是红黑树
        if (root == null) {
            return true;
        }
        // 根节点是黑色
        if (root.color != COLOR.BLACK) {
            System.err.println("违反了性质2:根节点不是黑色");
            return false;
        }
        // 获取单条路径中节点的个数
        int blackCount = 0;
        RBTreeNode cur = root;
        while (cur != null) {
            if (cur.color == COLOR.BLACK) {
                blackCount ++;
            }
            cur = cur.left;
        }
        // 具体的检验方式
        return _isValidRBtree(root, 0, blackCount);
    }
    // 检验是否存在连续的红色节点
    // 检验是否每条路径黑色节点数相同
    private boolean _isValidRBtree(RBTreeNode root, int pathCount, int blackCount) {
        if (root == null) {
            return true;
        }
        // 遇到一个黑色节点,统计当前路径中黑色节点个数
        if(root.color == COLOR.BLACK) {
            pathCount ++;
        }
        // 验证性质4
        RBTreeNode parent = root.parent;
        if(parent != null && parent.color == COLOR.RED && root.color == COLOR.RED) {
            System.err.println("违反了性质4:有连在一起的红色节点");
            return true;
        }
        // 验证性质5
        // 如果是叶子节点,则一条路径已经走到底,检验该条路径中黑色节点总个数是否与先前统计的结果相同
        if (root.left == null && root.right == null) {
            if (pathCount != blackCount) {
                System.err.println("违反了性质5:每条路径中黑色节点个数不一致");
                return false;
            }
        }
        // 以递归的方式检测 root 的左右子树
        return _isValidRBtree(root.left, pathCount, blackCount) &&
                _isValidRBtree(root.right, pathCount, blackCount);
    }
}

回到目录…


总结:
提示:这里对文章进行总结:
以上就是今天的学习内容,本文是Java高阶数据结构的学习,剖析红黑树底层原理,红黑树的时间复杂度,红黑树的插入以及插入时的平衡调整。之后的学习内容将持续更新!!!

  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只咸鱼。。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值