本篇文章先实现插入的相关内容,后续在更新删除的内容。
一、红黑树的介绍
红黑树全称是Red-Black Tree,它一种特殊的二叉查找树。红黑树的每个节点上都有颜色,可以是红(Red)或黑(Black)。
红黑树的特性:
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NULL)是黑色。 [注意:这里叶子节点,是指为空(NULL)的叶子节点]
(4)如果一个节点是红色的,则它的子节点必须是黑色的,也就是说不能有两个连续的红色节点。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
注意:
特性(5),确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。
二、红黑树的操作
1.旋转
1.左旋
/**
* 左旋
* 首先获取右孩子,将右孩子的左孩子放到旋转节点的右边
* 在判断右孩子的左孩子为空吗,不为空设置母亲,在判断旋转节点有没有母亲没有母亲更新根节点,有母亲要设置母亲和儿子
* 最后在更新旋转节点的母亲,将旋转节点放在新的节点的左边
* @param rbNode
* @return
*/
public void leftRotation(RBNode<AnyType> rbNode){
//首先获取右孩子,将右孩子的左孩子放到旋转节点的右边
RBNode<AnyType> rChild = rbNode.rightChild;
rbNode.rightChild = rChild.leftChild;
//在判断右孩子的左孩子为空吗,不为空设置母亲,在判断旋转节点有没有母亲没有母亲更新根节点,有母亲要设置母亲和儿子
if(rChild.leftChild != null) {
rChild.leftChild.parent = rbNode;
}
//更新孩子和母亲
setChildAndParent(rbNode,rChild);
//最后在更新旋转节点的母亲,将旋转节点放在新的节点的左边
rbNode.parent = rChild;
rChild.leftChild = rbNode;
}
/**
* 当母亲不为空时,更新母亲和孩子,更新根结点颜色
* @param rbNode
* @param rp
*/
public void setChildAndParent(RBNode<AnyType> rbNode,RBNode<AnyType> rp){
if(rbNode.parent != null) {
rp.parent = rbNode.parent;
if(rbNode == rbNode.parent.leftChild) {
rbNode.parent.leftChild = rp;
} else {
rbNode.parent.rightChild = rp;
}
}else {
//母亲为空,则将旋转之后的设为根节点
this.root = rp;
rp.parent = null;
rp.setColor(BLACK);
}
}
如下图:
2.右旋
public void rightRotation(RBNode<AnyType> rbNode){
RBNode<AnyType> rp = rbNode.leftChild;
rbNode.leftChild = rp.rightChild;
if(rp.rightChild != null) {
rp.rightChild.parent = rbNode;
}
setChildAndParent(rbNode,rp);
rbNode.parent = rp;
rp.rightChild = rbNode;
}
/**
* 当母亲不为空时,更新母亲和孩子,更新根结点颜色
* @param rbNode
* @param rp
*/
public void setChildAndParent(RBNode<AnyType> rbNode,RBNode<AnyType> rp){
if(rbNode.parent != null) {
rp.parent = rbNode.parent;
if(rbNode == rbNode.parent.leftChild) {
rbNode.parent.leftChild = rp;
} else {
rbNode.parent.rightChild = rp;
}
}else {
//母亲为空,则将旋转之后的设为根节点,同时更新颜色
this.root = rp;
rp.parent = null;
rp.setColor(BLACK);
}
}
解释和上述左旋大致相同,只是换了一个方向。
如下图:
变色在插入的应用中根据需求会改变颜色就不具体写了。
2 插入
在讨论红黑树的插入操作之前必须要明白,任何一个即将插入的新结点的初始颜色都为红色。这一点很容易理解,因为插入黑点会增加某条路径上黑结点的数目,从而导致整棵树黑高度的不平衡。但如果新结点父结点为红色时(如下图),将会违返红黑树性质:一条路径上不能出现相邻的两个红色结点。这时就需要通过一系列操作来使红黑树保持平衡。
首先说一下插入的四种情景
- 若新插入节点的母亲是黑色节点,红黑树不需要调整。
- 若新插入节点的母亲和它叔叔都是红色节点,红黑树只需要变色,不需要旋转
- 若新插入节点的母亲是红色,但是它叔叔是黑色或者为空,为空和是黑色操作是一样的就只说一个方面,第三点会在分两种情况去讨论的在下面会讲到。
结点的位置查询
public void insert(AnyType key){
RBNode<AnyType> parent = null;
//新增的节点都为红色
RBNode<AnyType> node = new RBNode<AnyType>(RED,key);
RBNode<AnyType> root = this.root;
//找到待插入的位置的母亲
while(root != null){
parent = root;
int cmp = root.key.compareTo(key);
if(cmp > 0){
root = root.leftChild;
}else if(cmp < 0){
root = root.rightChild;
}else{
//若key相同,更新key值
root.key = key;
}
}
//设置母亲
node.parent = parent;
//如果母亲不为空,则设置在母亲的位置
if(parent != null){
if(key.compareTo(parent.key) > 0){
parent.rightChild = node;
} else{
parent.leftChild = node;
}
} else{
//母亲为空,第一个节点,更新根节点,更新颜色
this.root = node;
node.setColor(BLACK);
}
//判断满足红黑树的性质
insertFix(node);
}
平衡修复
以下插入操作根据母亲在左边讨论,另一方解决思路是一样的就不在赘述了
1 插入的母亲节点为黑色,这个很好理解,什么都不用做,因为满足所有要求不需要调整。
以下就是在插入节点母亲为红色的情况下,就不在叙述了
2 插入的节点I为红色,现在出现了两个相连的红色出现,既不满足特性4那莫就要进行调整了如下图:
上面就是进行了变色操作,这样的操作就保证该子树中每条路径中黑色节点相同并且没有连续的红节点,因为我们改变了爷爷的颜色,但是我们不知道他的母亲是什么颜色,所以然再把爷爷pp当成新插入的节点继续往上继续调整,这是自底向顶的调整,代码实现如下
//叔叔存在,同时为黑色,设置爷爷为红色,母亲和叔叔为黑色,将爷爷看成新插入的节点继续判断
grandfather.setColor(RED);
grandfather.rightChild.setColor(BLACK);
parent.setColor(BLACK);
insertFix(grandfather);
2 叔叔为NULL或者为黑色如下图:
当叔叔为空或者黑色时,,以下是叔叔为空的操作,首先先判断你插入的节点是母亲的右孩子还是左孩子。
当为母亲的左孩子时也就是上述的情况母亲和孩子在一条直线上,先进行变色,在旋转,代码实现如下:
if(grandfather.rightChild == null || grandfather.rightChild.getColor().equals(BLACK)){
//传入节点为母亲的左孩子,都为坐,右旋传入爷爷
if(rbNode == parent.leftChild){
grandfather.setColor(RED);
parent.setColor(BLACK);
rightRotation(grandfather);
} else {
//为母亲的右孩子,左旋 传入母亲
leftRotation(parent);
//将两个红色放在一条线上继续向上在判断,将母亲看成是新插入的节点,继续判断
insertFix(parent);
}
当为母亲的右孩子时先进行旋转,有没有发现,这样就转变为上述的情况了,所以在把p也就是之前的母亲当成新插入的节点就完相当于进行上面的操作,进行平衡修复操作的判断就可以,如下图:
代码就是上面的else后面的部分
叔叔存在且为黑色的情况是一样的,对叔叔什么都没有改变,所以就不在举例啦。
所以说红黑树的插入就是从一种状态转为另外一种最终达到平衡,平衡的过程最多只需要进行2次旋转操作。
下面是插入的全部代码:
/**
*找到插入结点
* @param key
*/
public void insert(AnyType key){
RBNode<AnyType> parent = null;
//新增的节点都为红色
RBNode<AnyType> node = new RBNode<AnyType>(RED,key);
RBNode<AnyType> root = this.root;
//找到待插入的位置的母亲
while(root != null){
parent = root;
int cmp = root.key.compareTo(key);
if(cmp > 0){
root = root.leftChild;
}else if(cmp < 0){
root = root.rightChild;
}else{
//若key相同,更新key值
root.key = key;
}
}
//设置母亲
node.parent = parent;
//如果母亲不为空,则设置在母亲的位置
if(parent != null){
if(key.compareTo(parent.key) > 0){
parent.rightChild = node;
} else{
parent.leftChild = node;
}
} else{
//母亲为空,第一个节点,更新根节点,更新颜色
this.root = node;
node.setColor(BLACK);
}
//判断满足红黑树的性质
insertFix(node);
}
/**
* 传入新插入的节点,判断母亲的颜色在进行相关操作
* @param rbNode
*/
private void insertFix(RBNode<AnyType> rbNode) {
this.root.setColor(BLACK);
RBNode<AnyType> parent = rbNode.parent;
//母亲不为空,同时母亲为红色
if(parent != null && parent.getColor().equals(RED)){
//获取爷爷
RBNode<AnyType> grandfather = rbNode.parent.parent;
//母亲是左孩子
if(parent == grandfather.leftChild){
//叔叔为空,或者黑色
if(grandfather.rightChild == null || grandfather.rightChild.getColor().equals(BLACK)){
//传入节点为母亲的左孩子,母亲孩子左左 先变色 在传入爷爷进行右旋可以看上图
if(rbNode == parent.leftChild){
grandfather.setColor(RED);
parent.setColor(BLACK);
rightRotation(grandfather);
} else {
//为母亲的右孩子,传入母亲进行左旋
leftRotation(parent);
//上面的左旋结果就是将两个红色放在一条线上,将原来的母亲看成是新插入的节点,继续向上继续判断
insertFix(parent);
}
} else {
//叔叔存在,同时为黑色,设置爷爷为红色,母亲和叔叔为黑色,将爷爷看成新插入的节点继续向上判断
grandfather.setColor(RED);
grandfather.rightChild.setColor(BLACK);
parent.setColor(BLACK);
insertFix(grandfather);
}
} else{
//母亲是右孩子 叔叔为黑色或为空,母亲孩子在同一条线上都为右 先变色 在左旋转,母亲孩子为右左 先旋转,在传入原来的母亲继续判断,
if(grandfather.leftChild == null || grandfather.leftChild.getColor().equals(BLACK)){
if(rbNode == parent.rightChild){
grandfather.setColor(RED);
parent.setColor(BLACK);
leftRotation(grandfather);
} else{
rightRotation(parent);
insertFix(parent);
}
} else{
//叔叔为红色和上面一样
grandfather.setColor(RED);
grandfather.leftChild.setColor(BLACK);
parent.setColor(BLACK);
insertFix(grandfather);
}
}
}
}
红黑树的节点删除敬请期待。