浅析红黑树!建议收藏
Posted 飞人01_01
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅析红黑树!建议收藏相关的知识,希望对你有一定的参考价值。
前些天,我们讲解了搜索二叉树和AVL树,也知道了AVL树的自平衡机制,是如何进行旋转的,也知道了对于AVL树来说,查找数值的时间复杂度在O(logN)内,也就是说整棵树的深度,就是最大的查找次数。但是在AVL树在进行自平衡旋转时,还是耗费了大量的时间,所以就有了后来的红黑树。具体红黑树是什么?我们往下看。
前期文章:二叉树的概念以及搜索二叉树。
本期文章源码:GitHub
一、2-3查找树
首先要深刻理解红黑树的性质,我们还得来看一下2-3树是个什么情况。红黑树的来源,可以说就是来自于2-3树。
定义:一颗2-3查找树或者一颗空树,由以下两种节点组合而成:
2-节点: 节点内有一个数值域,还有两个存放左右孩子节点的内存地址的区域。左孩子节点的数值比当前节点小,右孩子节点的数值比当前节点大。
3-节点: 节点内有两个数值域,还有三个存放孩子节点的内存地址的区域。左孩子节点的数值都是小于该节点的,右孩子的数值都是大于该节点的,中间孩子的数值在该节点两个数值的中间。
查找:对于2-3树的查找操作,就类似于BST树是一样的。小于的往左子树,大于的就往右子树,在中间的,往中间的子树就行,比较简单。
插入:对于2-3树来说,插入节点就很奇怪。假设是空树,直接新建2-节点,返回去作为根结点即可。那如果本身不是空树的情况下,我们不能像BST树那样,插入到null节点处,所以我们分为两种情况来讨论:
-
待插入节点当前是2-节点,这种情况,新插入的数值比本身节点的数值小,就插到左边,反之插到右边。
-
待插入节点当前是3-节点,因为本身这种树,只有2-节点和3-节点,如果强行插入到3-节点内,就会变为4-节点**(临时)**,所以此时就需要将这个临时的4-节点进行拆分,如下图:
可能有同学会疑惑,直接插入到左孩子或者右孩子不就行了吗?答案肯定是当然不行!
因为2-3树是一个绝对平衡的树,也就是说,无论什么时候,无论哪一个节点,它的左右子树的高度必须是一样的。而AVL树是左右子树的高度差不超过1,二者有一定的区别。所以在插入新的节点的时候,除了root为null时,其他的情况,都是与另一个节点进行组合,产生3-节点或者临时的4-节点。
就是因为2-3树这样的绝对平衡的性质,从而在此基础之上,就衍生了左倾红黑树(红黑树中的一种)。
二、从2-3树到红黑树
上文中,我们介绍了2-3树的情况,又也讲了插入操作。现在我们就在2-3树的插入操作的基础之上,来说一说红黑树。
红黑树的节点:
//红黑树节点
private static final boolean RED = true;
private static final boolean BLACK = false; //为了好区分颜色,我们定义两个静态常量值
private static class TreeNode {
public int val;
public TreeNode left, right;
public boolean color; //true表示红色,false表示黑色
public TreeNode(int val) {
this.val = val;
color = RED; //默认新的节点是红色
}
}
红黑树的节点,跟BST的节点差不多,只是在此基础上新添了一个成员变量:color,来表示当前节点的颜色。
我们先来从红黑树的性质讲起:
-
每个节点不是红色就是黑色。
-
每个叶节点(null节点)是黑色。
此处的叶节点不是左右子树为空的节点;而是null节点。
-
如果一个节点是红色,那么它的两个孩子节点是黑色。
也就是说,没有连续的红色节点。对比红黑树插入的节点形状就可以知道,总共也就两种插入节点的类型,并且根节点都是黑色的。
-
从某一个节点,到每个叶节点所途径的节点中,黑色节点的数量一样多。
仔细回想,红色节点的意义就是:说明该节点的和它的父节点组合在一起,形成3-节点。这也就回到了2-3树上,绝对平衡树,肯定是某一个节点,到每个节点途径的节点数是一样的。
简单的说了一下红黑树的性质,我们来具体的看一下红黑树该怎么进行插入,与2-3树的插入又有什么区别?
插入:
上图就是,罗列出了所有的插入节点的情况,因为我们所写的是左倾红黑树,所以红色节点应该是在它父节点的左边。也就是上图用浅绿色框起来的几种情况,是需要进行调整的。
情况一:左倾红黑树,红色节点在父节点的左边,所以情况一是需要进行左旋转的。如图:
//左旋转代码
public TreeNode leftRotate(TreeNode node) {
TreeNode x = node.right;
//左旋转过程
node.right = x.left;
x.left = node;
//重新改颜色
x.color = node.color; //新根节点继承原节点的颜色
node.color = RED; //原根节点改为红色
return x; //返回新的根结点
}
情况二:类似于AVL树中的LL型,相信大家很自然的就想到是右旋转,如图:
//右旋转代码
public TreeNode rightRoate(TreeNode node) {
TreeNode x = node.left;
//右旋转过程
node.left = x.right;
x.right = node;
//重新改颜色
x.color = node.color; //新根结点 继承 原根结点的颜色
node.color = RED; //原根结点改为红色
return x; //返回新的根结点
}
情况三: 这种情况,相信大家并不陌生,类似于AVL树的LR型,先左旋转,再右旋转,如图:
情况四: 终于来到了这最后一种情况,那就是颜色的反转。当一个节点的左右两个孩子节点都是红色的时候,我们专门有一个方法,用于反转这种情况,需要将父节点改为红色,将两个子节点改为黑色。
//颜色反转
public void flipColor(TreeNode node) {
node.color = RED;
node.left.color = BLACK;
node.right.color = BLACK;
}
上面就是红黑树插入节点的所有情况分析,总结起来,就那么一点点,跟AVL树的旋转操作差不多,我们只需要修改颜色,以及颜色反转的方法。现在我们就将上面的几种情况,进行综合:
//红黑树插入节点
public void add(int val) {
root = add(root, val);
root.color = BLACK; //红黑树性质,根结点始终保持黑色
}
private TreeNode add(TreeNode node, int val) {
if (node == null) {
return new TreeNode(val);
}
if(val < node.val) {
node.left = add(node.left, val);
} else {
node.right = add(node.right, val);
}
//旋转操作以及颜色调整
//左孩子是黑色,右孩子是红色---情况一
if (!isRed(node.left) && isRed(node.right)) {
node = leftRotate(node); //左旋转
}
//左孩子是红色,左孩子的左孩子也是红色---情况二
if (isRed(node.left) && isRed(node.left.left)) {
node = rightRotate(node); //右旋转
}
//左右两个孩子都是红色---情况四
if (isRed(node.left) && isRed(node.right)) {
node = flipColor(node); //颜色反转
}
return node; //返回当前节点
}
private boolean isRed(TreeNode node) {
if (node == null) {
return BLACK; //null节点,是黑色
}
return node.color == RED; //判断是不是红色
}
可能有同学会疑惑,为什么没有将情况三的代码写进去??
试想一下,当我出现情况三的时候,是在递归函数里面实现的左旋转,然后当前函数结束后,返回到上一个递归函数,实现右旋转。所以这里我们就并不需要进行额外的情况三的代码。
各种查询时间复杂度分析:
好啦,红黑树的插入操作,我们就讲到这里,删除操作,这里我们就不细讲了。删除操作更麻烦一点,有兴趣的同学可以看看《算法4》或者《算法导论》等等书籍。
好啦,各位同学,下期见!!!
以上是关于浅析红黑树!建议收藏的主要内容,如果未能解决你的问题,请参考以下文章
红黑树的删除真的很难吗?其实是你没找到好的解题思路,不信你点击进来看看,建议收藏哦!!!
掌握了2-3-4树也就掌握了红黑树,不信进来看看,建议收藏!
掌握了2-3-4树也就掌握了红黑树,不信进来看看,建议收藏!
住上铺的师兄面试去了TX,就因为他和面试官光红黑树就聊了半个小时,建议收藏