一)红黑树简介
规则1、 每个节点要么是黑色,要么是红色(也可以用其它颜色表示,红黑色比较常用)。
规则2、根节点一直是黑色的。
规则3、每个叶子节点都是黑色的,并且为null(每个叶子到根的所有路径上不能有两个连续的红色节点)。
规则4、从根节点到叶子节点或null子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)。
规则5、新插入的节点颜色总是红色的,因为黑色比红色多,插入红色违背上面4点可能性小。
优点:新增和删除相对于AVL平衡二叉树耗费性能小。
缺点:在极端的情况下,比AVL平衡二叉树查询慢。
备注:红黑树适用于新增和删除多的情况,AVL平衡二叉树适用于查询多的情况。
原图模型:(如果节点下面没有子节点,会默认为NULL黑色节点,也称为“黑色哨兵”)
二)红黑树
第一步:定义红黑树结构
/**
* 红黑树
* @author ouyangjun
*/
public class RedBlackTree<T extends Comparable<T>> {
private static final boolean RED = false; // 红色
private static final boolean BLACK = true; // 黑色
// 红黑树结构
static class Node<T> {
Node<T> parent; // 父节点
Node<T> left; // 左节点
Node<T> right; // 右节点
T value; // 节点的值
boolean color; // 节点的颜色, 红色=false、黑色=true
Node(Node<T> parent, Node<T> left, Node<T> right,
T value, boolean color) {
this.parent = parent;
this.left = left;
this.right = right;
this.value = value;
this.color = color;
}
}
private Node<T> root; // 根节点
// 获取红黑树节点信息
public Node<T> getRoot() {
return root;
}
}
第二步:左旋转
// 左旋转
private Node<T> left(Node<T> x) {
Node<T> y = x.right; // 记录节点X的右节点Y
x.right = y.left; // 将节点Y的左节点LY作为节点X的右节点,为空就直接为空了
if (y.left != null) { // 判断节点Y是否有左节点LY
y.left.parent = x; // 把节点X作为节点LY的父节点
}
y.parent = x.parent; // 将节点X的父节点作为节点Y的父节点
if (x.parent == null) { // 判断节点X是否有父节点
// 没有父节点,直接把节点Y作为根节点
root = y;
} else {
if (x == x.parent.left) { // 判断节点X属于节点P的左节点还是右节点
x.parent.left = y; // 将节点Y作为节点X父节点下的左节点
} else {
x.parent.right = y; // 将节点Y作为节点X父节点下的右节点
}
}
y.left = x; // 把节点X作为节点Y的左节点
x.parent = y; // 把节点Y作为节点X的父节点
// 返回
return y;
}
第三步:右旋转
// 右旋转
private Node<T> right(Node<T> y) {
Node<T> x = y.left; // 记录节点Y的左节点X
y.left = x.right; // 将节点X的右节点RX作为节点Y的右节点,为空就直接为空了
if (x.right != null) { // 判断节点X是否有右节点RX
x.right.parent = y; // 把节点Y作为节点RX的父节点
}
x.parent = y.parent; // 将节点Y的父节点作为节点X的父节点
if (x.parent == null) { // 判断节点Y是否有父节点
// 没有父节点,直接把节点X作为根节点
root = x;
} else {
if (y == y.parent.left) { // 判断节点Y属于节点P的左节点还是右节点
y.parent.left = x; // 将节点X作为节点Y父节点下的左节点
} else {
y.parent.right = x; // 将节点X作为节点Y父节点下的右节点
}
}
x.right = y; // 把节点Y作为节点X的左节点
y.parent = x; // 把节点X作为节点Y的父节点
// 返回
return x;
}
第四步:新增节点场景及源码
场景一:黑父,父节点为黑的情况,直接插入新节点,不用做任何变化。
场景二:红父红叔,把父节点和叔节点的颜色变黑,如果祖节点是根节点,那就保持黑色(可以先变红再变黑),否则就变红色。
场景三:红父黑叔情况一
场景四:红父黑叔情况二
场景五:红父黑叔情况三
场景六:红父黑叔情况四
// 新增节点
public Node<T> add(T value) {
// 新节点
Node<T> newNode = new Node<T>(null, null, null, value, RED);
// 返回
return add(newNode);
}
private Node<T> add(Node<T> node) {
if (node == null) {
return null;
}
Node<T> current = null; // 表示最后node的父节点
Node<T> next = root; // 根节点
// 先查找到需要插入的位置
while (next != null) {
current = next;
int num = node.value.compareTo(next.value);
if (num < 0) {
next = next.left; // 在左边新增节点
} else if(num > 0) {
next = next.right; // 在右边新增节点
} else {
current = null; // 新增的节点重复了,直接返回
return null;
}
}
node.parent = current; // 找到插入的位置,将current作为node的父节点
// 判断node是左节点还是右节点
if (current != null){
int cmp = node.value.compareTo(current.value);
if (cmp < 0) {
current.left = node;
} else {
current.right = node;
}
} else {
root = node;
}
// 新增参数之后, 二叉树可能违反规则, 需要旋转将其修正为一颗红黑树
return insertFixUp(node);
}
private Node<T> insertFixUp(Node<T> node) {
if (node == null) {
return root;
}
Node<T> parent, gparent; // 定义父节点和祖父节点
// 需要修正的条件: 父节点存在,且父节点的颜色是红色
parent = node.parent;
while (parent != null && !parent.color) {
gparent = parent.parent; // 获得祖父节点
// 若父节点是祖父节点的左子节点,下面的else相反
if (parent == gparent.left) {
Node<T> uncle = gparent.right; // 获得叔叔节点
// case1: 叔叔节点也是红色
if (uncle != null && !uncle.color) {
uncle.color = BLACK; // 把父节点和叔叔节点涂黑
parent.color = BLACK;
gparent.color = RED; // 把祖父节点涂红
node = gparent; // 把位置放到祖父节点处
continue; // 继续while循环,重新判断
}
// case2: 叔叔节点是黑色,且当前节点是右子节点
if (node == parent.right) {
left(parent); // 从父节点出左旋
Node<T> tmp = parent; // 然后将父节点和自己调换一下,为下面右旋做准备
parent = node;
node = tmp;
}
// case3: 叔叔节点是黑色,且当前节点是左子节点
parent.color = BLACK;
gparent.color = RED;
right(gparent);
} else { // 若父节点是祖父节点的右子节点,与上面的情况完全相反,本质是一样的
Node<T> uncle = gparent.left;
// case1: 叔叔节点也是红色的
if (uncle != null && !uncle.color) {
uncle.color = BLACK; // 把父节点和叔叔节点涂黑
parent.color = BLACK;
gparent.color = RED; // 把祖父节点涂红
node = gparent; // 把位置放到祖父节点处
continue; // 继续while循环,重新判断
}
// case2: 叔叔节点是黑色的,且当前节点是左子节点
if (node == parent.left) {
right(parent);
Node<T> tmp = parent;
parent = node;
node = tmp;
}
// case3: 叔叔节点是黑色的,且当前节点是右子节点
parent.color = BLACK;
gparent.color = RED;
left(gparent);
}
}
//将根节点设置为黑色,保持根节点一直是黑色
Node<T> rootNode = root;
rootNode.color = BLACK;
// 返回
return rootNode;
}
第五步:增加几个辅助方法
// 查询节点
private Node<T> search(T value, Node<T> node){
if (node != null) {
int num = value.compareTo(node.value);
if (num < 0) {
return search(value, node.left);
} else if(num > 0) {
return search(value, node.right);
} else {
return node;
}
}
return null;
}
// 寻找后继节点,即大于该节点的最小节点
private Node<T> min(Node<T> node) {
if (node == null) {
return null;
}
if (node.left == null) {
return node;
}
while (node.left != null) {
node = node.left;
}
return node;
}
第六步:删除节点
// 删除节点
public Node<T> remove(T value) {
// 删除节点
Node<T> removeNode = search(value, root);
// 判断是否找到值
if (removeNode != null) {
return remove(removeNode);
}
// 返回
return null;
}
private Node<T> remove(Node<T> node) {
Node<T> child, parent, replace;
boolean color = true;
if (node.left != null && node.right != null) {
replace = min(node.right);
if (replace != null) {
parent = replace.parent;
} else {
parent = null;
}
child = replace.right;
color = replace.color;
if (node == parent) {
parent = replace;
} else {
if (child != null) {
child.parent = parent;
}
replace.parent.left = child;
replace.right = node.right;
if (node.right != null) {
node.right.parent = replace;
}
}
replace.parent = node.parent;
replace.left = node.left;
if (node.left != null) {
node.left.parent = replace;
}
replace.color = node.color;
if (node.parent != null) {
if(node.parent.left==node) {
node.parent.left = replace;
} else {
node.parent.right = replace;
}
} else {
root = replace;
}
if (color) {
removeFixUp(child, parent);
}
} else {
if (node.left != null) {
replace = node.left;
} else {
replace = node.right;
}
parent = node.parent;
if (parent != null) {
if (parent.left == node) {
parent.left = replace;
} else {
parent.right = replace;
}
} else {
root = replace;
}
if (replace !=null) {
replace.parent = parent;
}
color = node.color;
child = replace;
if (color) {
removeFixUp(child, parent);
}
}
return root;
}
private void removeFixUp(Node<T> node, Node<T> parentNode) {
Node<T> other;
// node不为空且为黑色,并且不为根节点
while ((null == node || node.color) && (node != this.root)) {
// node是父节点的左孩子
if (node == parentNode.left) {
// 获取到其右孩子
other = parentNode.right;
// node节点的兄弟节点是红色
if (!other.color) {
other.color = BLACK;
parentNode.color = RED;
left(parentNode);
other = parentNode.right;
}
// node节点的兄弟节点是黑色,且兄弟节点的两个孩子节点也是黑色
if ((other.left == null || other.left.color) &&
(other.right == null || other.right.color)) {
other.color = RED;
node = parentNode;
parentNode = node.parent;
} else {
// node节点的兄弟节点是黑色,且兄弟节点的右孩子是红色
if (null == other.right || other.right.color) {
other.left.color = BLACK;
other.color = RED;
right(other);
other = parentNode.right;
}
// node节点的兄弟节点是黑色,且兄弟节点的右孩子是红色,左孩子是任意颜色
other.color = parentNode.color;
parentNode.color = BLACK;
other.right.color = BLACK;
left(parentNode);
node = this.root;
break;
}
} else {
other = parentNode.left;
if (!other.color) {
other.color = BLACK;
parentNode.color = RED;
right(parentNode);
other = parentNode.left;
}
if ((null == other.left || other.left.color) &&
(null == other.right || other.right.color)) {
other.color = RED;
node = parentNode;
parentNode = node.parent;
} else {
if (null == other.left || other.left.color) {
other.right.color = BLACK;
other.color = RED;
left(other);
other = parentNode.left;
}
other.color = parentNode.color;
parentNode.color = BLACK;
other.left.color = BLACK;
right(parentNode);
node = this.root;
break;
}
}
}
if (node != null) {
node.color = BLACK;
}
}
第七步:遍历方式
// 转换颜色
private String getColor(boolean color) {
return color ? "黑" : "红";
}
/**
* 前序遍历: 根节点 ==> 左节点 ==> 右节点
* @param node
*/
public void preNode(Node<T> node) {
if (node != null) {
System.out.print(node.value + "(" + getColor(node.color) + ")\t");
preNode(node.left);
preNode(node.right);
}
}
/**
* 中序遍历: 左节点 ==> 根节点 ==> 右节点
* @param node
*/
public void inNode(Node<T> node) {
if (node != null) {
inNode(node.left);
System.out.print(node.value + "(" + getColor(node.color) + ")\t");
inNode(node.right);
}
}
/**
* 后序遍历: 左节点 ==> 右节点 ==> 根节点
* @param node
*/
public void nextNode(Node<T> node) {
if (node != null) {
nextNode(node.left);
nextNode(node.right);
System.out.print(node.value + "(" + getColor(node.color) + ")\t");
}
}
第八步:main方法测试源码
public static void main(String[] args) {
// 初始化
RedBlackTree<Integer> redblack = new RedBlackTree<Integer>();
// --------------------新增节点演示begin--------------------
redblack.add(29); // 添加根节点29
redblack.add(21); // 在节点29下添加左节点21
redblack.add(28); // 在节点21下添加左节点28
redblack.add(46); // 在节点29下添加右节点46
redblack.add(10); // 在节点21下面添加左节点10
redblack.add(13); // 在节点10下面添加左节点13
redblack.add(59); // 在节点46下添加右节点59
redblack.add(51); // 在节点59下添加左节点51
redblack.add(38); // 在节点46下添加右节点38
redblack.add(33); // 在节点38下添加右节点33
// --------------------新增节点演示end--------------------
System.out.println("前序遍历结果为:");
redblack.preNode(redblack.getRoot());
System.out.println("\n中序遍历结果为:");
redblack.inNode(redblack.getRoot());
System.out.println("\n后序遍历结果为:");
redblack.nextNode(redblack.getRoot());
}
打印效果图:
识别二维码关注个人微信公众号
本章完结,待续,欢迎转载!
本文说明:该文章属于原创,如需转载,请标明文章转载来源!