- 红黑树是二叉搜索树的一种
- 红黑树的性质:
- 每个节点的颜色要么为红色,要么为黑色;
- 根节点是黑色的;
- 叶子节点是黑色的(根据《算法导论》一书,我们把最底层的结点下面两个空节点视为叶子节点,成为Nil);
- 任意两个红色节点不能为父子关系;
- 每个节点到其后代叶子节点的黑高相同。(由于该特性导致红黑树是一种近似平衡的二叉树)
- 黑高(hb):从某个节点x出发(不含该节点)到达一个叶子节点的任意一条简单路径上黑色节点的个数
- 为实现红黑树功能,先定义了以下树节点的信息:
public class RBTNode<T extends Comparable> {
private boolean color;
private T value;
private RBTNode left;
private RBTNode right;
private RBTNode parent;
public RBTNode(T value) {
this.color = Color.BLACK;
this.value = value;
this.left = null;
this.right = null;
this.parent = null;
}
public boolean getColor() {
return color;
}
public void setColor(boolean color) {
this.color = color;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
public RBTNode getLeft() {
return left;
}
public void setLeft(RBTNode left) {
this.left = left;
}
public RBTNode getRight() {
return right;
}
public void setRight(RBTNode right) {
this.right = right;
}
public RBTNode getParent() {
return parent;
}
public void setParent(RBTNode parent) {
this.parent = parent;
}
@Override
public String toString() {
return "RBTNode{" +
"color=" + (color==Color.RED?"RED":"BLACK" )+
", value=" + value +
'}';
}
}
- 红黑树类的定义如下:
public class RBTree<T> {
private RBTNode root;
public RBTNode getRoot() {
return root;
}
public void setRoot(RBTNode root) {
this.root = root;
}
public void insert(RBTNode node);
public void insert(T value);
public void leftRotation(RBTNode node);
public void rightRotation(RBTNode node);
public void fixUp(RBTNode node);
}
- insert操作
向红黑树中插入新节点。新加的节点默认为红色,由于新加的节点可能会产生“两个红色节点为父子关系”的现象,需要经过左旋、右旋以及修改颜色的手段来维持红黑树的性质。为了方便插入新的节点,提供两种新增节点的方法:
public void insert(RBTNode node) {
RBTNode parent = null;//记录当前遍历节点的父节点
RBTNode current = this.getRoot();
int cmp; //记录value比较的结果
//按照二叉搜索树的插入方式插入节点node,新插入的结点必然是叶子节点或是根节点
while(current!=null) {
cmp = node.getValue().compareTo(current.getValue());
parent = current;
if(cmp<0) {
current = current.getLeft();
} else {
current = current.getRight();
}
}
//找到新节点该插入的位置,将其父节点锁定
node.setParent(parent);
if(parent==null) { //是空树
this.setRoot(node);
} else {
if(node.getValue().compareTo(parent.getValue())<0) {
parent.setLeft(node);
} else {
parent.setRight(node);
}
}
//新加的节点颜色默认是红色
node.setColor(Color.RED);
//进行颜色的修改
fixUp(node);
}
public void insert(T value) {
RBTNode node = new RBTNode((Comparable)value);
if(node!=null) {
insert(node);
}
}
- 左旋操作
public void leftRotation(RBTNode node) {
//获取node的右孩子
RBTNode right = node.getRight();
//调整right的左孩子:将right的左孩子改到node的右孩子,此时需要修改right的左孩子节点的父节点的信息
node.setRight(right.getLeft());
if(right.getLeft()!=null) {
right.getLeft().setParent(node);
}
//调整node与right的位置关系
right.setParent(node.getParent());
if(node.getParent()==null) {
this.setRoot(right);
} else {
if(node.getParent().getLeft()==node) {
node.getParent().setLeft(right);
} else {
node.getParent().setRight(right);
}
}
right.setLeft(node);
node.setParent(right);
}
- 右旋操作
public void rightRotation(RBTNode node) {
//获取node的左节点
RBTNode left = node.getLeft();
//调整left的右节点为node的左节点
node.setLeft(left.getRight());
if(left.getRight()!=null) {
left.getRight().setParent(node);
}
//调整node与left的关系
left.setParent(node.getParent());
if(node.getParent().getLeft()==node) {
node.getParent().setLeft(left);
} else {
node.getParent().setRight(left);
}
left.setRight(node);
node.setParent(left);
}
-
修改颜色信息
也就是出现了“两红色节点是父子关系”这种现象,需要分成以下几种情况来讨论:
- 父节点与叔叔节点同为红色,那么直接将叔叔节点和父亲节点修改为黑色,祖父节点改为红色即可;
- 父亲节点为红色,叔叔节点为黑色
(1)父亲节点为左孩子,当前节点为左孩子:父节点变黑,祖父节点变红后右旋;
(2)父亲节点为左孩子,当前节点为右孩子:父节点左旋,转(1);
(3)父亲节点为右孩子,当前节点为左孩子:父节点右旋,转(4);
(4)父亲节点为右孩子,当前节点为右孩子:父节点变黑,祖父节点变红后左旋;
注意:由于需要保证根节点是黑色的,而在修改颜色的过程中可能会将根节点变红,所以需要特别设置一下根节点的颜色。
public void fixUp(RBTNode node) {
//如果是根节点,则直接改为黑色
if(this.getRoot()==node) {
node.setColor(Color.BLACK);
return;
}
//修改颜色信息与父节点以及叔叔节点有关
RBTNode parent,uncle;
//由于新加入的节点是红色的,不会影响黑高,需要调整。按照算法导论中的意思,所有叶子节点都在最后一层,全是黑色,但是没有value
//所以新加入的结点都不会是叶子节点,需要调整颜色信息只是因为其与父节点颜色冲突(同时为红色)
//此处需要用循环,因为在修改颜色的过程中,可能会产生新的冲突
parent = node.getParent();
while(parent.getColor()==Color.RED) { //如果是红色,则该节点的肯定不会为根节点,此时需要判别该节点的叔叔节点的颜色信息
//parent是祖父结点的左孩子
if(parent==parent.getParent().getLeft()) {
//获取叔叔节点信息
uncle = parent.getParent().getRight();
//case1 :叔叔节点的颜色也是红色,那么只需要将父节点和叔叔节点改为黑色。
// 由于红色节点的父节点必为黑色节点,为了保持黑高,所以将祖父结点的颜色改为红色
if(uncle!=null&&uncle.getColor()==Color.RED) {
parent.setColor(Color.BLACK);
uncle.setColor(Color.BLACK);
parent.setColor(Color.RED);
//看看祖父节点是否产生颜色冲突
parent = parent.getParent();
continue;
}
//case2 : 叔叔节点的颜色是黑色,node是右孩子
//parent节点进行一次左旋(拉直),然后进行case3的右旋,但是需要调整节点信息
if(parent.getRight()==node) {
leftRotation(parent);
//左旋之后父子关系发生变化,需要修改才能正确执行case3的情形
RBTNode tmp = parent;
parent = node;
node = tmp;
}
//case3 : 叔叔节点的颜色是黑色,node是左孩子
//gparent节点进行一次右旋
parent.setColor(Color.BLACK);
parent.getParent().setColor(Color.RED);
rightRotation(parent.getParent());
} else { //parent节点是祖父节点的右孩子,需要进行对称操作
//获取叔叔节点信息
uncle = parent.getParent().getLeft();
//case1 :叔叔节点的颜色也是红色,那么只需要将父节点和叔叔节点改为黑色。
// 由于红色节点的父节点必为黑色节点,为了保持黑高,所以将祖父结点的颜色改为红色
if(uncle!=null&&uncle.getColor()==Color.RED) {
parent.setColor(Color.BLACK);
uncle.setColor(Color.BLACK);
parent.setColor(Color.RED);
//看看祖父节点是否产生颜色冲突
parent = parent.getParent();
continue;
}
//case2 : 叔叔节点的颜色是黑色,node是右孩子
//parent节点进行一次左旋(拉直),然后进行case3的右旋,但是需要调整节点信息
if(parent.getLeft()==node) {
rightRotation(parent);
//左旋之后父子关系发生变化,需要修改才能正确执行case3的情形
RBTNode tmp = parent;
parent = node;
node = tmp;
}
//case3 : 叔叔节点的颜色是黑色,node是左孩子
//gparent节点进行一次右旋
parent.setColor(Color.BLACK);
parent.getParent().setColor(Color.RED);
leftRotation(parent.getParent());
}
}
//循环结束后需要保证根节点为黑色
this.getRoot().setColor(Color.BLACK);
}
- 删除操作(未实现,待续…)