概念性质
通过对任何红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。(和AVL树不一样,红黑树只是接近平衡)
红黑树的性质
1.每个结点不是红色就是黑色
2.根节点是黑色的
3.如果一个节点是红色的,则它的两个孩子结点是黑色的(没有2个连续的红色节点)4.对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
5.每个叶子结点都是黑色的(此处的叶子结点指的是空结点)不能忽略的代表每一条路径
思考:一颗红黑树种,有X个黑色结点,这棵树总共有N个结点,那么N的取值范围是多少?
答:N∈[X,2X]
解析:这是根据红黑树的性质所的出来的,由于红色结点不能够连续有两个,所有最小的取值就是当N个结点全部为黑色结点的时候,没有一个红色结点;而最大的时候就是X个黑色结点排成一条最长路径,红色结点间隔插入(黑红结点间隔排列),这时黑色结点数=红色结点数
最短路径时间复杂度:logN
最长路径时间复杂度:logN+1
最短路径长度:logN
最长路径长度:2logN
代码
结点定义
//枚举
public enum COLOR {
RED,BLACK
}
static class RBTreeNode {
public RBTreeNode left;
public RBTreeNode right;
public RBTreeNode parent;
public int val;
public COLOR color;
public RBTreeNode(int val) {
this.val = val;
this.color = COLOR.RED;
}
}
//根节点
public RBTreeNode root;
这里存在着一个问题:我们新建的结点为什么默认要为红色?
答:新增的节点不能是黑色的因为 如果是黑色的 那么就需要保证每条路径上的黑色节点必须是相同的。那我们就需要无缘无故地需要新增节点,这完全是没有必要的操作,如图
插入
//插入 其思路还是和搜索二叉树的插入一样
public boolean insert(int val) {
RBTreeNode node = new RBTreeNode(val);
node.color = COLOR.RED;
if (root == null) {
root = node;
//如果是第一次插入的结点,要将其颜色设置为黑色
root.color = COLOR.BLACK;
return true;
}
RBTreeNode parent = null;
RBTreeNode cur = root;
while (cur != null) {
if (cur.val < val) {
parent = cur;
cur = cur.right;
} else if (cur.val > val) {
parent = cur;
cur = cur.left;
} else {
return false;
}
}
if (parent.val > val) {
parent.left = node;
} else {
parent.right = node;
}
cur = node;
node.parent = parent;
//---------------------------------------------------------------------------------------
//每插入一个结点,就需要调整颜色对于红黑树来说
while (parent != null && parent.color == COLOR.RED) {
RBTreeNode grandFather = parent.parent;
if (parent == grandFather.left) {
RBTreeNode uncle = grandFather.right;
//第一种情况:uncle结点不为null且为红色
if (uncle.color == COLOR.RED && uncle != null){
//将uncle结点和父亲结点变黑色,将祖父结点变红色
parent.color = COLOR.BLACK;
uncle.color = COLOR.BLACK;
grandFather.color = COLOR.RED;
//考虑祖父结点上还有其父结点,其复结点也可能是红色,所以仍需要向上查找
cur = grandFather;
parent = cur.parent;
} else {
//第三种情况:parent为红,uncle为黑或者null
if (parent.right == cur) {
//先左旋再交换cur和parent的结点变成情况2,执行一样的逻辑即可
RotateR(parent);
//交换cur和parent结点
RBTreeNode tmp = parent;
parent = cur;
cur = tmp;
}
//第二种情况:parent结点为红 uncle结点为黑色或者null
//grandparent.left=parent parent.left=cur
//直接右旋
RotateR(grandFather);
//改变颜色
parent.color = COLOR.BLACK;
grandFather.color = COLOR.RED;
}
} else {
//parent == grandfather.right
RBTreeNode uncle = grandFather.left;
//第一种情况:uncle为红色且不为null
if (uncle.color == COLOR.RED && uncle == null) {
//改变uncle和parent grandfather的颜色
uncle.color = COLOR.BLACK;
parent.color = COLOR.BLACK;
grandFather.color = COLOR.RED;
cur = grandFather;
parent = grandFather.parent;
} else {
//第三种情况:和上面逻辑一样,只是方向相反,右旋后交换结点,执行情况2的逻辑即可
if (cur == parent.left) {
//先右旋
RotateR(parent);
RBTreeNode tmp = parent;
parent = cur;
cur = tmp;
}
//第二种情况:uncle为null或者黑 parent为红
//grandfather.right = parent parent.right = cur
//左旋
RotateL(grandFather);
//改变grandfather和parent结点的颜色
grandFather.color = COLOR.RED;
parent.color = COLOR.BLACK;
}
}
}
root.color = COLOR.BLACK;
return true;
}
其插入的思想还是和搜索二叉树一样,定义一个结点cur指向当前root,定义一个parent结点指向cur.parent,当cur为null时,判断此时parent.val和插入结点val的值的大小来判断结点插入在parent结点的左边还是右边
后续的操作就是来修改红黑树的颜色,共有2类大情况,一类大情况有三种小情况
第一类大情况:grandFather.left=parent ----> RBTreeNode uncle=grandFather.right
1.第一种小情况:(颜色就不写枚举了,直接写中文了,方便看)
parent.color=红色 uncle.color=红色
当grandFather变成红色后,其上面一定还有其父结点,如果其父结点也是红色的话,就
违背了红黑树性质中的不能连续出现两个红色结点,所以我们仍然需要向上查找,这也
对应了代码:cur = grandFather; parent = cur.parent
//第一种情况:uncle结点和父结点颜色都为红色 if (parent.color == COLOR.RED && uncle.color == COLOR.RED) { //将uncle结点和父亲结点变黑色,将祖父结点变红色 parent.color = COLOR.BLACK; uncle.color = COLOR.BLACK; //考虑祖父结点上还有其父结点,其复结点也可能是红色,所以仍需要向上查找 cur = grandFather; parent = cur.parent;
2.第二种情况:
parent.color=红色 uncle.color=黑色 or uncle=null
此时我们需要判断cur与parent的相对位置
(1):当cur = parent.left时,我们需要右旋,再改变parent和grandFather结点的颜色
(2).当cur=parent.right时,我们需要先左旋,再交换parent和cur的结点,这时结构状态 就转变成情况2一样了,再执行情况2的逻辑即可
第二大类情况:grandFather.right=parent------>grandFather.left=uncle
其思想和前面的完全一样,只不过结构顺序相反,下面只画图分析
第一种情况:
第二种情况:
第三种情况: