1. 红黑树概念
下图就是一棵红黑树:
为了后续操作中不出现空指针异常,可以加入一个额外的哨兵节点T,T作为所有叶子节点的子节点,作为根节点的父节点,节点T要求是黑色,其他属性不做要求,如下图所示:
2. 节点定义
class Node {
int key;
Node left, right;
Node parent;
boolean color;
public Node(boolean color) {
super();
this.color = color;
}
public Node(int key) {
super();
this.key = key;
}
@Override
public String toString() {
return "Node [key=" + key + ", color=" + color + " p=" + parent.key + "]";
}
}
3. 旋转操作
左旋操作代码:
public void rotateLeft(Node x) {
Node y = x.right;
x.right = y.left;// y的左子树作为x的右子树
if (y.left != nil) {// y存在左子树
y.left.parent = x;// 该左子树的父节点变为x
}
y.parent = x.parent;// x之前的父节点现在成为y的父节点
if (x.parent == nil) {// x是根节点
root = y;// y替代x成为根节点
} else if (x == x.parent.left) {// x是左子节点
x.parent.left = y;// y替代x作为左子节点
} else {// x是右子节点
x.parent.right = y;// y替代x作为右子节点
}
y.left = x;// x作为y的左子节点
x.parent = y;// y作为x的父节点
}
右旋操作代码:
public void rotateRight(Node y) {
Node x = y.left;
y.left = x.right;
if (x.right != null) {// x存在右子树
x.right.parent = y;// x的右子树的父节点变为y
}
x.parent = y.parent;// y之前的父节点现在变为x的父节点
if (y == nil) {// y是根节点
root = x;// x替代y成为根节点
} else if (y == y.parent.left) {// y是左子节点
y.parent.left = x;// x代替y作为其父节点的左子节点
} else {// y是右子节点
y.parent.right = x;// x代替y作为其父节点的右子节点
}
x.right = y;// y变成x的右子节点
y.parent = x;// x变成y的父节点
}
4. 插入操作
public void insert(Node z) {
Node y = nil;// y指向哨兵节点
Node x = root;// x指向根节点
while (x != nil) {
y = x;// y保存更新前的x
if (z.key < x.key) {// z的key较小 然后去x左子树中查找
x = x.left;
} else {// z的key较大 然后去x右子树中查找
x = x.right;
}
}
z.parent = y;// y作为z的父节点
if (y == nil) {// 说明目前树为空 插入的是第一个节点
root = z;// z成为根节点
} else if (z.key < y.key) {
y.left = z;// z的值比父节点值小 作为左子节点
} else {
y.right = z;// z的值比父节点值大 作为右子节点
}
z.left = nil;
z.right = nil;// z是叶子节点
z.color = RED;// 叶子节点红色
insetFix(z);// 插入之后需要调整树的结构
}
插入操作完成了还需要进行树的结构调整,先来看下面几种情况:
情况1: 新插入节点z的叔节点y是红色的
上图中的(a)或(b)违反了性质:一个红节点的两个子节点都是黑色的
解决方案:z的父节点和树节点颜色由红变黑,z的爷爷节点颜色由黑变红
Node y=z.parent.parent.right;
if(y.color==RED) {
z.parent.color=BLACK;
y.color=BLACK;
z.parent.parent.color=RED;
z=z.parent.parent;//z指向爷爷节点继续处理树的结构
}
情况2: 新插入节点z的叔节点y是黑色的并且z是一个右孩子节点
情况3: 新插入节点z的叔节点y是黑色的并且z是一个左孩子节点
情况2和情况3可以放在一张图中来讨论,因为情况2可以通过左旋操作进入情况3,入下图所示:
else if(z==z.parent.right) {//情况2
z=z.parent;
rotateLeft(z);
z.parent.color=BLACK;
z.parent.parent.color=RED;
rotateRight(z.parent.parent);
}else if(z==z.parent.right) {//情况3
z.parent.color=BLACK;
z.parent.parent.color=RED;
rotateRight(z.parent.parent);
}
上面的三种情况都是基于新插入节点z的父节点是一个左子节点的情形,对于z的父节点是一个右子节点的情况也有三种,与上面三种情况对称,关于调整的完整代码如下:
public void insetFix(Node z) {
while (z.parent.color == RED) {
if (z.parent == z.parent.parent.left) {// z的父节点是左子节点
Node y = z.parent.parent.right;
if (y != nil && y.color == RED) {// 情况1
z.parent.color = BLACK;
y.color = BLACK;
z.parent.parent.color = RED;
z = z.parent.parent;
continue;
} else if (z == z.parent.right) {// 情况2
z = z.parent;
rotateLeft(z);
}
// 情况3
z.parent.color = BLACK;
z.parent.parent.color = RED;
rotateRight(z.parent.parent);
} else if (z.parent == z.parent.parent.right) {// z的父节点是右子节点
Node y = z.parent.parent.left;
if (y != nil && y.color == RED) {
z.parent.color = BLACK;
y.color = BLACK;
z.parent.parent.color = RED;
z = z.parent.parent;
continue;
} else if (z == z.parent.left) {
z = z.parent;
rotateRight(z);
}
z.parent.color = BLACK;
z.parent.parent.color = RED;
rotateLeft(z.parent.parent);
}
root.color = BLACK;
}
}
5. 删除操作
红黑树的删除操作和BST的删除操作类似,如果待删除节点x没有左子节点,就用右子节点替代x;如果待删除节点x没有右子节点,就用左子节点替代x; 如果x既有左子节点也有右子节点,可以使用x右子树中的最小节点y来替代x, y之前的右子树来替换y, x的右子树作为y新的右子树,只不过在红黑树中需要根据节点的颜色进行树结构的调整
// 用节点v 替代 节点u
public void transplant(Node u, Node v) {
if (u.parent == T) {// u是根节点
T.left = v;// v替代u作为根节点
} else if (u == u.parent.left) {// u是左子节点
u.parent.left = v;
} else if (u == u.parent.right) {
u.parent.right = v;
}
v.parent = u.parent;// u的父节点成为v的父节点
}
public void delete(Node z) {//z是待删除节点
Node y = z;
Node x = null;
boolean originColor = y.color;
if (z.left == T) {// z没有左子节点
x = z.right;
transplant(z, z.right);//z的右子节点替代z
} else if (z.right == T) {//z没有右子节点
x = z.left;
transplant(z, z.left);//z的左子节点替代z
}else {//z的左右子节点都存在
y=minimum(z.right);//y保存z右子树中的最小节点 用y替换z
originColor=y.color;
x=y.right;
if(y.parent==z) {//y是z的直接右子节点 不需要else语句中的y的右子树替换y
x.parent=y;
}else {
transplant(y, y.right);//y的右子树替换y
y.right=z.right;//待删除节点z的右子树成为y的右子树
y.right.parent=y;//更新y的右子树的的父节点
}
transplant(z, y);//用y替换z
y.left=z.left;//z的左子树作为y的左子树
y.left.parent=y;
y.color=z.color;//更新颜色
}
if(originColor==BLACK) {//待删除节点的颜色是黑色 需要调整树的结构
deleteFix(x);
}
}
/*
* 寻找某个子树中的最小节点
*/
Node minimum(Node subTreeRoot) {
while (subTreeRoot.left != T) {
subTreeRoot = subTreeRoot.left;
}
return subTreeRoot;
}
删除-调整操作:
情形1:待调整节点x的兄弟节点w是红色
w.color=BLACK;
x.parent.color=RED;
rotateLeft(x.parent);
w=x.parent.right;
情形2:待调整节点x的兄弟节点w是黑色的,并且w的两个子节点也是黑色的
w.color=RED;
x=x.parent;
continue;
情形3:待调整节点x的兄弟节点w是黑色的,w的左子节点为红色,右子节点为黑色
w.left.color=BLACK;
w.color=RED;
rotateRight(w);
w=x.parent.right;
情形4:待调整节点x的兄弟节点w是黑色的,w的右子节点为红色,左子节点不考虑
w.color=x.parent.color;//case4
x.parent.color=BLACK;
w.right.color=BLACK;
rotateLeft(x.parent);
x=T.left;
可以发现情况1经过转化可以变成情况2,3,4中的一种,情况3经过转化后就变成了情况4
上面的4种情况是基于x是左子节点的情况,对于x是右子节点的情况,只一将上面代码中的left和right互换即可
刪除-调整代码如下:
public void deleteFix(Node x) {
Node w = null;
while (x != root && x.color == BLACK) {
if (x == x.parent.left) {
w = x.parent.right;
if (w.color == RED) {// case1
w.color = BLACK;
x.parent.color = RED;
rotateLeft(x.parent);
w = x.parent.right;
}
if (w.left.color == BLACK && w.right.color == BLACK) {// case2
w.color = RED;
x = x.parent;
continue;
} else if (w.right.color == BLACK) {// case3
w.left.color = BLACK;
w.color = RED;
rotateRight(w);
w = x.parent.right;
}
w.color = x.parent.color;// case4
x.parent.color = BLACK;
w.right.color = BLACK;
rotateLeft(x.parent);
x = root;
} else {
w = x.parent.left;
if (w.color == RED) {// case1
w.color = BLACK;
x.parent.color = RED;
rotateRight(w);
w = x.parent.left;
}
if (w.left.color == BLACK && w.right.color == BLACK) {// case2
w.color = RED;
x = x.parent;
continue;
} else if (w.right.color == BLACK) {// case3
w.right.color = BLACK;
w.color = RED;
rotateLeft(w);
w = x.parent.left;
}
w.color = x.parent.color;// case4
x.parent.color = BLACK;
w.left.color = BLACK;
rotateRight(x.parent);
x = root;
}
}
x.color = BLACK;
}
6. 完整代码
package datastructure.tree;
import java.util.LinkedList;
import java.util.Queue;
class Node {
int key;
Node left, right;
Node parent;
boolean color;
public Node(boolean color) {
super();
this.color = color;
}
public Node(int key) {
super();
this.key = key;
}
@Override
public String toString() {
return "Node [key=" + key + ", color=" + color + " p=" + parent.key + "]";
}
}
public class RedBlackTree {
private static final boolean RED = true;
private static final boolean BLACK = false;
private Node nil;
private Node root;
/*
* 初始化操作中创建一个哨兵节点nil root开始等于nil
*/
public RedBlackTree() {
nil = new Node(BLACK);
nil.key = -1;
root = nil;
}
public void rotateLeft(Node x) {
Node y = x.right;
x.right = y.left;// y的左子树作为x的右子树
if (y.left != nil) {// y存在左子树
y.left.parent = x;// 该左子树的父节点变为x
}
y.parent = x.parent;// x之前的父节点现在成为y的父节点
if (x.parent == nil) {// x是根节点
root = y;// y替代x成为根节点
} else if (x == x.parent.left) {// x是左子节点
x.parent.left = y;// y替代x作为左子节点
} else {// x是右子节点
x.parent.right = y;// y替代x作为右子节点
}
y.left = x;// x作为y的左子节点
x.parent = y;// y作为x的父节点
}
public void rotateRight(Node y) {
Node x = y.left;
y.left = x.right;
if (x.right != null) {// x存在右子树
x.right.parent = y;// x的右子树的父节点变为y
}
x.parent = y.parent;// y之前的父节点现在变为x的父节点
if (y == nil) {// y是根节点
root = x;// x替代y成为根节点
} else if (y == y.parent.left) {// y是左子节点
y.parent.left = x;// x代替y作为其父节点的左子节点
} else {// y是右子节点
y.parent.right = x;// x代替y作为其父节点的右子节点
}
x.right = y;// y变成x的右子节点
y.parent = x;// x变成y的父节点
}
public void insert(Node z) {
Node y = nil;// y指向哨兵节点
Node x = root;// x指向根节点
while (x != nil) {
y = x;// y保存更新前的x
if (z.key < x.key) {// z的key较小 然后去x左子树中查找
x = x.left;
} else {// z的key较大 然后去x右子树中查找
x = x.right;
}
}
z.parent = y;// y作为z的父节点
if (y == nil) {// 说明目前树为空 插入的是第一个节点
root = z;// z成为根节点
} else if (z.key < y.key) {
y.left = z;// z的值比父节点值小 作为左子节点
} else {
y.right = z;// z的值比父节点值大 作为右子节点
}
z.left = nil;
z.right = nil;// z是叶子节点
z.color = RED;// 叶子节点红色
insetFix(z);// 插入之后需要调整树的结构
}
public void insetFix(Node z) {
while (z.parent.color == RED) {
if (z.parent == z.parent.parent.left) {// z的父节点是左子节点
Node y = z.parent.parent.right;
if (y != nil && y.color == RED) {// 情况1
z.parent.color = BLACK;
y.color = BLACK;
z.parent.parent.color = RED;
z = z.parent.parent;
continue;
} else if (z == z.parent.right) {// 情况2
z = z.parent;
rotateLeft(z);
}
//情况2经过调整之后可以变成情况3
// 情况3
z.parent.color = BLACK;
z.parent.parent.color = RED;
rotateRight(z.parent.parent);
} else if (z.parent == z.parent.parent.right) {// z的父节点是右子节点
Node y = z.parent.parent.left;
if (y != nil && y.color == RED) {
z.parent.color = BLACK;
y.color = BLACK;
z.parent.parent.color = RED;
z = z.parent.parent;
continue;
} else if (z == z.parent.left) {
z = z.parent;
rotateRight(z);
}
z.parent.color = BLACK;
z.parent.parent.color = RED;
rotateLeft(z.parent.parent);
}
root.color = BLACK;
}
}
// 用节点v 替代 节点u
public void transplant(Node u, Node v) {
if (u.parent == nil) {// u是根节点
root = v;// v替代u作为根节点
} else if (u == u.parent.left) {// u是左子节点
u.parent.left = v;
} else if (u == u.parent.right) {
u.parent.right = v;
}
v.parent = u.parent;// u的父节点成为v的父节点
}
public void delete(Node z) {
Node y = z;
Node x = null;
boolean originColor = y.color;
if (z.left == nil) {// z没有左子节点
x = z.right;
transplant(z, z.right);// z的右子节点替代z
} else if (z.right == nil) {// z没有右子节点
x = z.left;
transplant(z, z.left);// z的左子节点替代z
} else {// z的左右子节点都存在
y = minimum(z.right);// y保存z右子树中的最小节点 用y替换z
originColor = y.color;
x = y.right;
if (y.parent == z) {// y是z的直接右子节点 不需要else语句中的y的右子树替换y
x.parent = y;
} else {
transplant(y, y.right);// y的右子树替换y
y.right = z.right;// 待删除节点z的右子树成为y的右子树
y.right.parent = y;// 更新y的右子树的的父节点
}
transplant(z, y);// 用y替换z
y.left = z.left;
y.left.parent = y;
y.color = z.color;
}
if (originColor == BLACK) {// 节点y的颜色是黑色 需要调整树的结构
deleteFix(x);
}
}
/*
* 寻找某个子树中的最小节点
*/
Node minimum(Node subTreeRoot) {
while (subTreeRoot.left != nil) {
subTreeRoot = subTreeRoot.left;
}
return subTreeRoot;
}
public void deleteFix(Node x) {
Node w = null;
while (x != root && x.color == BLACK) {
if (x == x.parent.left) {
w = x.parent.right;
if (w.color == RED) {// case1
w.color = BLACK;
x.parent.color = RED;
rotateLeft(x.parent);
w = x.parent.right;
}
if (w.left.color == BLACK && w.right.color == BLACK) {// case2
w.color = RED;
x = x.parent;
continue;
} else if (w.right.color == BLACK) {// case3
w.left.color = BLACK;
w.color = RED;
rotateRight(w);
w = x.parent.right;
}
//case3 经过调整之后可以变成case4
w.color = x.parent.color;// case4
x.parent.color = BLACK;
w.right.color = BLACK;
rotateLeft(x.parent);
x = root;
} else {
w = x.parent.left;
if (w.color == RED) {// case1
w.color = BLACK;
x.parent.color = RED;
rotateRight(w);
w = x.parent.left;
}
if (w.left.color == BLACK && w.right.color == BLACK) {// case2
w.color = RED;
x = x.parent;
continue;
} else if (w.right.color == BLACK) {// case3
w.right.color = BLACK;
w.color = RED;
rotateLeft(w);
w = x.parent.left;
}
w.color = x.parent.color;// case4
x.parent.color = BLACK;
w.left.color = BLACK;
rotateRight(x.parent);
x = root;
}
}
x.color = BLACK;
}
/*
* 层次遍历
*/
public void printTreeByLevel(Node root) {
Queue<Node> q = new LinkedList<>();
q.offer(root);
while (!q.isEmpty()) {
int sz = q.size();
Node node = null;
for (int i = 0; i < sz; i++) {
node = q.poll();
System.out.print(node + " ");
if (node.left != nil)
q.offer(node.left);
if (node.right != nil)
q.offer(node.right);
}
System.out.println();
}
}
/*
* 根据key查找某个节点
*/
public Node find(Node root, int key) {
if (root == nil) {
return null;
}
if (root.key < key) {
return find(root.right, key);
} else if (root.key > key) {
return find(root.left, key);
} else if (key == root.key) {
return root;
}
return null;
}
/*
* 中序遍历
*/
public void printInorder(Node root) {
if (root == nil)
return;
printInorder(root.left);
System.out.println(root);
printInorder(root.right);
}
public static void main(String[] args) {
RedBlackTree tree = new RedBlackTree();
System.out.println("插入数据:");
for (int i = 1; i <= 10; i++) {
tree.insert(new Node(i));
}
System.out.println("层次遍历:");
tree.printTreeByLevel(tree.root);
System.out.println("中序遍历:");
tree.printInorder(tree.root);
System.out.println("删除数据:");
int[] delete = { 1, 2,3, 4,5, 6,7,8, 9,10 };
for (int num : delete) {
Node delNode = tree.find(tree.root, num);
System.out.println("删除了元素" + delNode);
tree.delete(delNode);
System.out.println("中序遍历:");
tree.printInorder(tree.root);
}
}
}