红黑树定义
(1)所有的叶子结点为黑色
(2)红色结点的孩子只是黑色
(3)根结点一定是黑色
(4)从一个结点到该结点可以到达的叶子结点的路径上黑色结点数目相同
(5)结点的颜色是红色或者黑色
代码实现
这里我只是为了学习红黑树而只实现了整数作为value, 没有用自定义类型
主要是学习了TreeMap中的写法来写的这个红黑树
结点定义
// 定义颜色
private static final boolean BLACK = true;
private static final boolean RED = false;
// 保存这棵树的根结点
private transient Node root;
// 保存这棵树的结点个数
private transient int size = 0;
static final class Node{
Integer value;
Node parent, left, right;
boolean color = BLACK;
// 构造器, 新的结点左右孩子为空
Node(Integer value, Node parent) {
this.value = value;
this.parent = parent;
this.left = null;
this.right = null;
}
// node的equals方法
public boolean equals(Object o) {
if(!(o instanceof Node)) {
return false;
}
Node e = (Node) o;
return Objects.equals(e.value, value);
}
}
insert
添加新结点
这个方法与搜索树的插入无异
public Integer insert(int value) {
// 获取根结点
Node tmp = this.root;
// 根结点为空, 直接设置为根结点, 这里的结点是黑色
if(tmp == null) {
this.root = new Node(value, null);
size = 1;
return null;
}
// 一个比较标志位, 判断当前值应该插入的位置, 搜索树的插入
int cmp = 0;
Node parent = tmp;
while(tmp != null) {
parent = tmp;
cmp = tmp.value - value;
// 比当前结点大, 插入右边
if(cmp < 0) {
tmp = tmp.right;
} else if(cmp > 0) {
tmp = tmp.left;
// 与当前结点相同, 不插入
} else {
return value;
}
}
// 找到位置之后, 判断成为右孩子还是左孩子
Node e = new Node(value, parent);
if(cmp < 0) {
parent.right = e;
} else {
parent.left = e;
}
// 调整红黑树
fixBRTree(e);
size++;
return null;
}
fixBRTree
这一步是调整红黑树的, 方法比较多
这里全是功能代码, 用于美化调整方法的
private static Node parentOf(Node p) {
return p == null ? null : p.parent;
}
private static Node leftOf(Node p) {
return p == null ? null : p.left;
}
private static Node rightOf(Node p) {
return p == null ? null : p.right;
}
private static boolean colorOf(Node p) {
return p == null ? BLACK : p.color;
}
private static void setColor(Node p, boolean c) {
if(p != null) p.color = c;
}
结点右旋, 这里与平衡二叉树的左旋右旋一样
/**
* @Description: 对p结点进行左旋, 将p的右孩子放在p的位置
*/
private void rotateLeft(Node p) {
// 先对入参判空
if(p != null) {
Node r = p.right;
// 右旋是将p的右孩子r移动到p的位置, p变为r的左孩子的过程
// 所以这里r的原先的左孩子就会变为p的右孩子
// 因为r的左孩子一定比p大
p.right = r.left;
// 这里将r的左孩子的爹设置为p
if(r.left != null) {
r.left.parent = p;
}
// r的父亲设置为r原先的祖父, 也就是p的父亲
r.parent = p.parent;
// 这里如果p的父亲是空, 那么说明p是根结点, 所以将根结点设置为r
// 如果p的父亲不是空, 那么就让p的父亲的左孩子或者右孩子指向r
if(p.parent == null) {
this.root = r;
} else if(p.parent.left == p) {
p.parent.left = r;
} else {
p.parent.right = r;
}
// 完成最后的交换, 爹和孩子的身份互换
r.left = p;
p.parent = r;
}
}
这里是左旋, 所有操作与右旋相反
/**
* @Description: 对p结点进行右旋, 将p的左孩子放在p的位置
*/
private void rotateRight(Node p) {
if(p != null) {
Node l = p.left;
p.left = l.right;
if(l.right != null) {
l.right.parent = p;
}
l.parent = p.parent;
if(p.parent == null) {
this.root = l;
} else if(p.parent.left == p) {
p.parent.left = l;
} else {
p.parent.right = l;
}
l.right = p;
p.parent = l;
}
}
调整红黑树代码
private void fixBRTree(Node x) {
// 新插入的结点设置为红色
// 因为红色作为孩子对路径这个条件没有影响
x.color = RED;
// 因为红黑树需要循环调整, 所以这里一个while循环判断
// 如果x是空或者x是根节点, 那么退出循环, 因为根节点一定是黑色的
// 如果x是黑色的, 那么退出循环, 因为现在x的所有孩子都是有序的, 符合红黑树定义的
// 所以如果x是黑色, 那么x的祖先到x下面的叶子结点所经过的黑色结点一定相同
while(x != null && x != root && x.parent.color == RED) {
// 这里分两类, 一类是父亲是祖父的右孩子, 一类是左孩子
if(parentOf(x) == leftOf(parentOf(parentOf(x)))) {
// 红黑树如何调整需要根据叔叔结点判断
Node u = rightOf(parentOf(parentOf(x)));
// 如果叔叔结点是红色的, 那么只用交换结点颜色就可以维稳
// 每一步都只需要保证了祖父结点下面的所有结点处于稳态, 那么红黑树就处于稳态
if(colorOf(u) == RED) {
// 叔叔是红色, 所以将叔叔和父亲变成黑色, 祖父变成红色
// 因为红色结点的两个孩子一定是黑色, 而叔叔是红色, 所以祖父一定是黑色
// 根节点到祖父所经过的黑色结点假设为x
// 那么如果祖父变成红色, 他的两个孩子变成了黑色
// 这时候根节点到他的两个孩子所经过的黑色结点依旧是x个, 祖父树的稳态不变
setColor(parentOf(x), BLACK);
setColor(u, BLACK);
setColor(parentOf(parentOf(x)), RED);
// 如果祖父变成了红色, 那么要考虑祖父的父亲是否为红色, 这里重新进行循环
x = parentOf(parentOf(x));
} else {
// 如果叔叔结点是黑色的, 那单纯改变颜色无法维稳, 需要进行旋转
// 下面贴图
if(x == rightOf(parentOf(x))) {
x = parentOf(x);
rotateLeft(x);
}
// 左旋之后x, x爹, x爷应该是在一条线上了, 这时候x一定是红色, x爹红色, x爷黑色
// 所以如果经过下面的右旋, x爹成了爷, 那么x爹和x爷互换颜色就可以维稳
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateRight(parentOf(parentOf(x)));
}
} else {
// 这里的情况是右孩子, 跟左孩子的处理一样, 只是旋转的判断相反
Node u = leftOf(parentOf(parentOf(x)));
if(colorOf(u) == RED) {
setColor(parentOf(x), BLACK);
setColor(u, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if(x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
// 最后无论根结点是不是黑色, 都设置为黑色就ok
setColor(this.root, BLACK);
}
这种父红叔黑的情况, 除了叔叔结点是null这种情况之外, 我想不出来如何通过插入删除操作得到这种情况的红黑树(如果有人通过插入删除操作得到了这种情况的树, 麻烦评论告诉我下!!!)
测试
这里给个中序遍历的测试, 大家自己写main方法使用就行
public List<Node> getInOrder() {
Node root = this.root;
return inOrder(root, new ArrayList<>());
}
public List<Node> inOrder(Node p, List<Node> list) {
if(p == null) return null;
list.add(p);
if(p.left != null) {
inOrder(p.left, list);
}
if(p.right != null) {
inOrder(p.right, list);
}
return list;
}
后记
夜深了, 明天写一下删除和查找操作
这里给大家一个链接https://www.cs.usfca.edu/~galles/visualization/RedBlack.html这是一个模拟红黑树的网页, 挺有用的
红黑树学习 java实现简单的红黑树(2)