利用Java实现红黑树

网友投稿 612 2022-12-08

利用java实现红黑树

利用Java实现红黑树

目录1、红黑树的属性2、旋转3、插入4、删除5、所有代码6、演示

1、红黑树的属性

红黑树是一种二分查找树,与普通的二分查找树不同的一点是,红黑树的每个节点都有一个颜色(color)属性。该属性的值要么是红色,要么是黑色。

通过限制从根到叶子的任何简单路径上的节点颜色,红黑树确保没有比任何其他路径长两倍的路径,从而使树近似平衡。

假设红黑树节点的属性有键(key)、颜色(color)、左子节点(left)、右子节点(right),父节点(parent)。

一棵红黑树必须满足下面有下面这些特性( 红黑树特性 ):

树中的每个节点要么是红色,要么是黑色;

根节点是黑色;

每个叶子节点(null)是黑色;

如果某节点是红色的,它的两个子节点都是黑色;

对于每个节点到后面任一叶子节点(null)的所有路径,都有相同数量的黑色节点。

为了在红黑树代码中处理边界条件方便,我们用一个哨兵变量代替null。对于一个红黑树tree,哨兵变量RedBlackTree.NULL(下文代码中)是一个和其它节点有同样属性的节点,它的颜色(color)属性是黑色,其它属性可以任意取值。

我们使用哨兵变量是因为我们可以把一个节点node的子节点null当成一个普通节点。

在这里,我们使用哨兵变量RedBlackTree.NULL代替树中所有的null(所有的叶子节点及根节点的父节点)。

我们把从一个节点n(不包括)到任一叶子节点路径上的黑色节点的个数称为 黑色高度 ,用bh(n)表示。一棵红黑树的黑色高度是其根节点的黑色高度。

关于红黑树的搜索,求最小值,求最大值,求前驱,求后继这些操作的代码与二分查找树的这些操作的代码基本一致。可以在用java实现二分查找树查看。

结合上文给出下面的代码。

用一个枚举类Color表示颜色:

public enum Color {

Black("黑色"), Red("红色");

private String color;

private Color(String color) {

this.color = color;

}

@Override

public String toString() {

return color;

}

}

类Node表示节点:

public class Node {

public int key;

public Color color;

public Node left;

public Node right;

public Node parent;

public Node() {

}

public Node(Color color) {

this.color = color;

}

public Node(int key) {

this.key = key;

this.color = Color.Red;

}

public int height() {

return Math.max(left != RedBlackTree.NULL ? left.height() : 0, right != RedBlackTree.NULL ? right.height() : 0) + 1;

}

public Node minimum() {

Node pointer = this;

while (pointer.lefthttp:// != RedBlackTree.NULL)

pointer = pointer.left;

return pointer;

}

@Override

public String toString() {

String position = "null";

if (this.parent != RedBlackTree.NULL)

position = this.parent.left == this ? "left" : "right";

return "[key: " + key + ", color: " + color + ", parent: " + parent.key + ", position: " + position + "]";

}

}

类RedTreeNode表示红黑树:

public class RedBlackTree {

// 表示哨兵变量

public final static Node NULL = new Node(Color.Black);

public Node root;

public RedBlackTree() {

this.root = NULL;

}

}

2、旋转

红黑树的插入和删除操作,能改变红黑树的结构,可能会使它不再有前面所说的某些特性性。为了维持这些特性,我们需要改变树中某些节点的颜色和位置。

我们可以通过旋转改变节点的结构。主要有 左旋转 和 右旋转 两种方式。具体如下图所示。

左旋转:把一个节点n的右子节点right变为它的父节点,n变为right的左子节点,所以right不能为null。这时n的右指针空了出来,right的左子树被n挤掉,所以right原来的左子树称为n的右子树。

右旋转:把一个节点n的左子节点left变为它的父节点,n变为left的右子节点,所以left不能为null。这时n的左指针被空了出来,left的右子树被n挤掉,所以left原来的右子树被称为n的左子树。

可在RedTreeNode类中,加上如下实现代码:

public void leftRotate(Node node) {

Node rightNode = node.right;

node.right = rightNode.left;

if (rightNode.left != RedBlackTree.NULL)

rightNode.left.parent = node;

rightNode.parent = node.parent;

if (node.parent == RedBlackTree.NULL)

this.root = rightNode;

else if (node.parent.left == node)

node.parent.left = rightNode;

else

node.parent.right = rightNode;

rightNode.left = node;

node.parent = rightNode;

}

public void rightRotate(Node node) {

Node leftNode = node.left;

node.left = leftNode.right;

if (leftNode.right != RedBlackTree.NULL)

leftNode.right.parent = node;

leftNode.parent = node.parent;

if (node.parent == RedBlackTree.NULL) {

this.root = leftNode;

} else if (node.parent.left == node) {

node.parent.left = leftNode;

} else {

node.parent.right = leftNode;

}

leftNode.right = node;

node.parent = leftNode;

}

3、插入

红黑树的插入代码与二分查找树的插入代码非常相似。只不过红黑树的插入操作会改变红黑树的结构,使其不在有该有的特性。

在这里,新插入的节点默认是红色。

所以在插入节点之后,要有维护红黑树特性的代码。

public void insert(Node node) {

Node parentPointer = RedBlackTree.NULL;

Node pointer = this.root;

while (this.root != RedBlackTree.NULL) {

parentPointer = pointer;

pointer = node.key < pointer.key ? pointer.left : pointer.right;

}

node.parent = parentPointer;

if(parentPointer == RedBlackTree.NULL) {

this.root = node;

}else if(node.key < parentPointer.key) {

parentPointer.left = node;

}else {

parentPointer.right = node;

}

node.left = RedBlackTree.NULL;

node.right = RedBlackTree.NULL;

node.color = Color.Red;

// 维护红黑树属性的方法

this.insertFixUp(node);

}

用上述方法插入一个新节点的时候,有两类情况会违反红黑树的特性。

当树中没有节点时,此时插入的节点称为根节点,而此节点的颜色为红色。

当新插入的节点成为一个红色节点的子节点时,此时存在一个红色结点有红色子节点的情况。

对于第一类情况,可以直接把根结点设置为黑色;而针对第二类情况,需要根据具体条件,做出相应的解决方案。

具体代码如下:

public void insertFixUp(Node node) {

// 当node不是根结点,且node的父节点颜色为红色

while (node.parent.color == Color.Red) {

// 先判断node的父节点是左子节点,还是右子节点,这不同的情况,解决方案也会不同

if (node.parent == node.parent.parent.left) {

Node uncleNode = node.parent.parent.right;

if (uncleNode.color == Color.Red) { // 如果叔叔节点是红色,则父父一定是黑色

// 通过把父父节点变成红色,父节点和兄弟节点变成黑色,然后在判断父父节点的颜色是否合适

uncleNode.color = Color.Black;

node.parent.color = Color.Black;

node.parent.parent.color = Color.Red;

node = node.parent.parent;

} else if (node == node.parent.right) {

node = node.parent;

this.leftRotate(node);

} else {

node.parent.color = Color.Black;

node.parent.parent.color = Color.Red;

this.rightRotate(node.parent.parent);

}

} else {

Node uncleNode = node.parent.parent.left;

if (uncleNode.color == Color.Red) {

uncleNode.color = Color.Black;

node.parent.color = Color.Black;

node.parent.parent.color = Color.Red;

node = node.parent.parent;

} else if (node == node.parent.left) {

node = node.parent;

this.rightRotate(node);

} else {

node.parent.color = Color.Black;

node.parent.parent.color = Color.Red;

this.leftRotate(node.parent.parent);

}

}

}

// 如果之前树中没有节点,那么新加入的点就成了新结点,而新插入的结点都是红色的,所以需要修改。

this.root.color = Color.Black;

}

下面的图分别对应第二类情况中的六种及相应处理结果。

情况1:

情况2:

情况3:

情况4:

情况5:

情况6:

4、删除

红黑树中节点的删除会使一个结点代替另外一个节点。所以先要实现这样的代码:

public void transplant(Node n1, Node n2) {

if(n1.parent == RedBlackTree.NULL){

this.root = n2;

}else if(n1.parent.left == n1) {

n1.parent.left = n2;

}else {

n1.parent.right = n2;

}

n2.parent = n1.parent;

}

红黑树的删除节点代码是基于二分查找树的删除节点代码而写的。

删除结点代码:

public void delete(Node node) {

Node pointer1 = node;

// 用于记录被删除的颜色,如果是红色,可以不用管,但如果是黑色,可能会破坏红黑树的属性

Color pointerOriginColor = pointer1.color;

// 用于记录问题的出现点

Node pointer2;

if (node.left == RedBlackTree.NULL) {

pointer2 = node.right;

this.transplant(node, node.right);

} else if (node.right == RedBlackTree.NULL) {

pointer2 = node.left;

this.transplant(node, node.left);

} else {

// 如要删除的字节有两个子节点,则找到其直接后继(右子树最小值),直接后继节点没有非空左子节点。

pointer1 = node.right.minimum();

// 记录直接后继的颜色和其右子节点

pointerOriginColor = pointer1.color;

pointer2 = pointer1.right;

// 如果其直接后继是node的右子节点,不用进行处理

if (pointer1.parent == node) {

pointer2.parent = pointer1;

} else {

// 否则,先把直接后继从树中提取出来,用来替换node

this.transplant(pointer1, pointer1.right);

pointer1.right = node.right;

pointer1.right.parent = pointer1;

}

// 用node的直接后继替换node,继承node的颜色

this.transplant(node, pointer1);

pointer1.left = node.left;

pointer1.left.parent = pointer1;

pointer1.color = node.color;

}

if (pointerOriginColor == Color.Black) {

this.deleteFixUp(pointer2);

}

}

当被删除节点的颜色是黑色时需要调用方法维护红黑树的特性。

主要有两类情况:

当node是红色时,直接变成黑色即可。

当node是黑色时,需要调整红黑树结构。,

private void deleteFixUp(Node node) {

// 如果node不是根节点,且是黑色

while (node != this.root && node.color == Color.Black) {

// 如果node是其父节点的左子节点

if (node == node.parent.left) {

// 记录node的兄弟节点

Node pointer1 = node.parent.right;

// 如果他兄弟节点是红色

if (pointer1.color == Color.Red) {

pointer1.color = Color.Black;

node.parent.color = Color.Red;

leftRotate(node.parent);

pointer1 = node.parent.right;

}

if (pointer1.left.color == Color.Black && pointer1.right.color == Color.Black) {

pointer1.color = Color.Red;

node = node.parent;

} else if (pointer1.right.color == Color.Black) {

pointer1.left.color = Color.Black;

pointer1.color = Color.Red;

rightRotate(pointer1);

pointer1 = node.parent.right;

} else {

pointer1.color = node.parent.color;

node.parent.color = Color.Black;

pointer1.right.color = Color.Black;

leftRotate(node.parent);

node = this.root;

}

} else {

// 记录node的兄弟节点

Node pointer1 = node.parent.left;

// 如果他兄弟节点是红色

if (pointer1.color == Color.Red) {

pointer1.color = Color.Black;

node.parent.color = Color.Red;

rightRotate(node.parent);

pointer1 = node.parent.left;

}

if (pointer1.right.color == Color.Black && pointer1.left.color == Color.Black) {

pointer1.color = Color.Red;

node = node.parent;

} else if (pointer1.left.color == Color.Black) {

pointer1.right.color = Color.Black;

pointer1.color = Color.Red;

leftRotate(pointer1);

pointer1 = node.parent.left;

} else {

pointer1.color = node.parent.color;

node.parent.color = Color.Black;

pointer1.left.color = Color.Black;

rightRotate(node.parent);

node = this.root;

}

}

}

node.color = Color.Black;

}

对第二类情况,有下面8种:

情况1:

情况2:

情况3:

情况4:

情况5:

情况6:

情况7:

情况8:

5、所有代码

public enum Color {

Black("黑色"), Red("红色");

private String color;

private Color(String color) {

this.color = color;

}

@Override

public String toString() {

return color;

}

}

public class Node {

public int key;

public Color color;

public Node left;

public Node right;

public Node parent;

public Node() {

}

public Node(Color color) {

this.color = color;

}

public Node(int key) {

this.key = key;

this.color = Color.Red;

}

/**

* 求在树中节点的高度

*

* @return

*/

public int height() {

return Math.max(left != RedBlackTree.NULL ? left.height() : 0, right != RedBlackTree.NULL ? right.height() : 0) + 1;

}

/**

* 在以该节点为根节点的树中,求最小节点

*

* @return

*/

public Node minimum() {

Node pointer = this;

while (pointer.left != RedBlackTree.NULL)

pointer = pointer.left;

return pointer;

}

@Override

public String toString() {

String position = "null";

if (this.parent != RedBlackTree.NULL)

position = this.parent.left == this ? "left" : "right";

return "[key: " + key + ", color: " + color + ", parent: " + parent.key + ", position: " + position + "]";

}

}

import java.util.LinkedList;

import java.util.Queue;

public class RedBlackTree {

public final static Node NULL = new Node(Color.Black);

public Node root;

public RedBlackTree() {

this.root = NULL;

}

/**

* 左旋转

*

* @param node

*/

public void leftRotate(Node node) {

Node rightNode = node.right;

node.right = rightNode.left;

if (rightNode.left != RedBlackTree.NULL)

rightNode.left.parent = node;

rightNode.parent = node.parent;

if (node.parent == RedBlackTree.NULL)

this.root = rightNode;

else if (node.parent.left == node)

node.parent.left = rightNode;

else

node.parent.right = rightNode;

rightNode.left = node;

node.parent = rightNode;

}

/**

* 右旋转

*

* @param node

*/

public void rightRotate(Node node) {

Node leftNode = node.left;

node.left = leftNode.right;

if (leftNode.right != RedBlackTree.NULL)

leftNode.right.parent = node;

leftNode.parent = node.parent;

if (node.parent == RedBlackTree.NULL) {

this.root = leftNode;

} else if (node.parent.left == node) {

node.parent.left = leftNode;

} else {

node.parent.right = leftNode;

}

leftNode.right = node;

node.parent = leftNode;

}

public void insert(Node node) {

Node parentPointer = RedBlackTree.NULL;

Node pointer = this.root;

while (pointer != RedBlackTree.NULL) {

parentPointer = pointer;

pointer = node.key < pointer.key ? pointer.left : pointer.right;

}

node.parent = parentPointer;

if (parentPointer == RedBlackTree.NULL) {

this.root = node;

} else if (node.key < parentPointer.key) {

parentPointer.left = node;

} else {

parentPointer.right = node;

}

node.left = RedBlackTree.NULL;

node.right = RedBlackTree.NULL;

node.color = Color.Red;

this.insertFixUp(node);

}

private void insertFixUp(Node node) {

// 当node不是根结点,且node的父节点颜色为红色

while (node.parent.color == Color.Red) {

// 先判断node的父节点是左子节点,还是右子节点,这不同的情况,解决方案也会不同

if (node.parent == node.parent.parent.left) {

Node uncleNode = node.parent.parent.right;

if (uncleNode.color == Color.Red) { // 如果叔叔节点是红色,则父父一定是黑色

// 通过把父父节点变成红色,父节点和兄弟节点变成黑色,然后在判断父父节点的颜色是否合适

uncleNode.color = Color.Black;

node.parent.color = Color.Black;

node.parent.parent.color = Color.Red;

node = node.parent.parent;

} else if (node == node.parent.right) { // node是其父节点的右子节点,且叔叔节点是黑色

// 对node的父节点进行左旋转

node = node.parent;

this.leftRotate(node);

} else { // node如果叔叔节点是黑色,node是其父节点的左子节点,父父节点是黑色

// 把父节点变成黑色,父父节点变成红色,对父父节点进行右旋转

node.parent.color = Color.Black;

node.parent.parent.color = Color.Red;

this.rightRotate(node.parent.parent);

}

} else {

Node uncleNode = node.parent.parent.left;

if (uncleNode.color == Color.Red) {

uncleNode.color = Color.Black;

node.parent.color = Color.Black;

node.parent.parent.color = Color.Red;

node = node.parent.parent;

} else if (node == node.parent.left) {

node = node.parent;

this.rightRotate(node);

} else {

node.parent.color = Color.Black;

node.parent.parent.color = Color.Red;

this.leftRotate(node.parent.parent);

}

}

}

// 如果之前树中没有节点,那么新加入的点就成了新结点,而新插入的结点都是红色的,所以需要修改。

this.root.color = Color.Black;

}

/**

* n2替代n1

*

* @param n1

* @param n2

*/

private void transplant(Node n1, Node n2) {

if (n1.parent == RedBlackTree.NULL) { // 如果n1是根节点

this.root = n2;

} else if (n1.parent.left == n1) { // 如果n1是其父节点的左子节点

n1.parent.left = n2;

} else { // 如果n1是其父节点的右子节点

n1.parent.right = n2;

}

n2.parent = n1.parent;

}

/**

* 删除节点node

*

* @param node

*/

public void delete(Node node) {

Node pointer1 = node;

// 用于记录被删除的颜色,如果是红色,可以不用管,但如果是黑色,可能会破坏红黑树的属性

Color pointerOriginColor = pointer1.color;

// 用于记录问题的出现点

Node pointer2;

if (node.left == RedBlackTree.NULL) {

pointer2 = node.right;

this.transplant(node, node.right);

} else if (node.right == RedBlackTree.NULL) {

pointer2 = node.left;

this.transplant(node, node.left);

} else {

// 如要删除的字节有两个子节点,则找到其直接后继(右子树最小值),直接后继节点没有非空左子节点。

pointer1 = node.right.minimum();

// 记录直接后继的颜色和其右子节点

pointerOriginColor = pointer1.color;

pointer2 = pointer1.right;

// 如果其直接后继是node的右子节点,不用进行处理

if (pointer1.parent == node) {

pointer2.parent = pointer1;

} else {

// 否则,先把直接后继从树中提取出来,用来替换node

this.transplant(pointer1, pointer1.right);

pointer1.right = node.right;

pointer1.right.parent = pointer1;

}

// 用node的直接后继替换node,继承node的颜色

this.transplant(node, pointer1);

pointer1.left = node.left;

pointer1.left.parent = pointer1;

pointer1.color = node.color;

}

if (pointerOriginColor == Color.Black) {

this.deleteFixUp(pointer2);

}

}

/**

* The procedure RB-DELETE-FIXUP restores properties 1, 2, and 4

*

* @param node

*/

private void deleteFixUp(Node node) {

// 如果node不是根节点,且是黑色

while (node != this.root && node.color == Color.Black) {

// 如果node是其父节点的左子节点

if (node == node.parent.left) {

// 记录node的兄弟节点

Node pointer1 = node.parent.right;

// 如果node兄弟节点是红色

if (pointer1.color == Color.Red) {

pointer1.color = Color.Black;

node.parent.color = Color.Red;

leftRotate(node.parent);

pointer1 = node.parent.right;

}

if (pointer1.left.color == Color.Black && pointer1.right.color == Color.Black) {

pointer1.color = Color.Red;

node = node.parent;

} else if (pointer1.right.color == Color.Black) {

pointer1.left.color = Color.Black;

pointer1.color = Color.Red;

rightRotate(pointer1);

pointer1 = node.parent.right;

} else {

pointer1.color = node.parent.color;

node.parent.color = Color.Black;

pointer1.right.color = Color.Black;

leftRotate(node.parent);

node = this.root;

}

} else {

// 记录node的兄弟节点

Node pointer1 = node.parent.left;

// 如果他兄弟节点是红色

if (pointer1.color == Color.Red) {

pointer1.color = Color.Black;

node.parent.color = Color.Red;

rightRotate(node.parent);

pointer1 = node.parent.left;

}

if (pointer1.right.color == Color.Black && pointer1.left.color == Color.Black) {

pointer1.color = Color.Red;

node = node.parent;

} else if (pointer1.left.color == Color.Black) {

pointer1.right.color = Color.Black;

pointer1.color = Color.Red;

leftRotate(pointer1);

pointer1 = node.parent.left;

} else {

pointer1.color = node.parent.color;

node.parent.color = Color.Black;

pointer1.left.color = Color.Black;

rightRotate(node.parent);

node = this.root;

}

}

}

node.color = Color.Black;

}

private void innerWalk(Node node) {

if (node != NULL) {

innerWalk(node.left);

System.out.println(node);

innerWalk(node.right);

}

}

/**

* 中序遍历

*/

public void innerWalk() {

this.innerWalk(this.root);

}

/**

* 层次遍历

*/

public void print() {

Queue queue = new LinkedList<>();

queue.add(this.root);

while (!queue.isEmpty()) {

Node temp = queue.poll();

System.out.println(temp);

if (temp.left != NULL)

queue.add(temp.left);

if (temp.right != NULL)

queue.add(temp.right);

}

}

// 查找

public Node search(int key) {

Node pointer = this.root;

while (pointer != NULL && pointer.key != key) {

pointer = pointer.key < key ? pointer.right : pointer.left;

}

return pointer;

}

}

6、演示

演示代码:

public class Test01 {

public static void main(String[] args) {

int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8 };

RedBlackTree redBlackTree = new RedBlackTree();

for (int i = 0; i < arr.length; i++) {

redBlackTree.insert(new Node(arr[i]));

}

System.out.println("树的高度: " + redBlackTree.root.height());

System.out.println("左子树的高度: " + redBlackTree.root.left.height());

System.out.println("右子树的高度: " + redBlackTree.root.right.height());

System.out.println("层次遍历");

redBlackTree.print();

// 要删除节点

Node node = redBlackTree.search(4);

redBlackTree.delete(node);

System.out.println("树的高度: " + redBlackTree.root.height());

System.out.println("左子树的高度: " + redBlackTree.root.left.height());

System.out.println("右子树的高度: " + redBlackTree.root.right.height());

System.out.println("层次遍历");

redBlackTree.print();

}

}

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Java设计模式之java解释器模式详解
下一篇:详解MyBatis resultType与resultMap中的几种返回类型
相关文章

 发表评论

暂时没有评论,来抢沙发吧~