目录
1、定义
通过前面算法-15-平衡查找树的学习,我们了解到要想实现平衡的二叉树,我们需要2-3结点的二叉查找树思想。如果你能完全理解2-3二叉查找树的话,你理解红黑树将简单很多。也知道红黑树是通过什么思想来保证平衡性的。
红黑二叉查找树背后的基本思想是用标准的二叉查找树(完 全由2-结点构成)和一些额外的信息(替换3-结点)来表示2-3 树。
我们通过红色链接,来将两个结点连接起来构造成一个3-结点,黑色链接是2-3树中的普通链接。
2、特点
红黑树要想和2-3树一一对应,那么它必须含有以下定义:
- 根节点总是黑色的
- 红链接均为左链接;
- 没有任何一个结点同时和两条红链接相连;
- 该树是完美黑色平衡的,即任意空链接到根结点的路径上的黑链接数量相同。
3、完成代码
public class RedBlackTree<Key extends Comparable<Key>,Value> {
private static final boolean RED=true;
private static final boolean BLACK=false;
private Node root;
class Node{
Key key;
Value val;
int N;//这棵子树的结点总数
Node left,right;
boolean color;
public Node(Key key,Value val,int N,boolean color){
this.key=key;
this.val=val;
this.N=N;
this.color=color;
}
}
public void put(Key key,Value value){
root=put(root,key,value);
root.color=BLACK;//根节点永远是黑色
}
/**
* 1、 如果右子结点是红色的而左子结点是黑色 的,进行左旋转;
* 2、 如果左子结点是红色的且它的左子结点也是红色的,进行右旋转;
* 3、 如果左右子结点均为红色,进行颜色转换。
*/
private Node put(Node h, Key key, Value value) {
if (h==null) return new Node(key,value,1,RED);
int cmp=key.compareTo(h.key);
if (cmp<0) h.left=put(h.left,key,value);
else if (cmp>0) h.right=put(h.right,key,value);
else h.val=value;
if (isRed(h.right)&&!isRed(h.left)) h=rotateLeft(h);
if (isRed(h.left)&&isRed(h.left.left))h=rotateRight(h);
if (isRed(h.left)&&isRed(h.right)) flipColors(h);
h.N=size(h.left)+size(h.right)+1;
return h;
}
public Value get(Key key) {
return get(root, key);
}
/**
* 使用递归的方式,从根节出发,不断向下查找
*/
private Value get(Node node, Key key) {
if (node == null) {
return null;
}
int cmp = key.compareTo(node.key);
if (cmp < 0) {
return get(node.left, key);
} else if (cmp > 0) {
return get(node.right, key);
} else {
return node.val;
}
}
private void flipColors(Node h){
h.color= RED;
h.left.color=BLACK;
h.right.color=BLACK;
}
private Node rotateLeft(Node h){
Node x=h.right;
h.right=x.left;
x.left=h;
x.color=h.color;
h.color=RED;
x.N=h.N;
h.N=size(h.left)+size(h.right)+1;
return x;
}
private Node rotateRight(Node h){
Node x=h.left;
h.left=x.right;
x.right=h;
x.color=h.color;
h.color=RED;
x.N=h.N;
h.N=size(h.left)+size(h.right)+1;
return x;
}
public int size(Node node) {
if (node == null) return 0;
return node.N;
}
private boolean isRed(Node x){
if (x==null) return false;//树最底部空结点都是黑色链接
return x.color==RED;
}
}
4、旋转---rotateLeft()
在我们实际操作过程红,比如删除一个结点或者插入一个结点时候,我们会出现红色右链接或者一个结点连接着两条红色链接的情况。
这个时候我们就需要进行旋转来恢复红黑树的有序性和完美平衡性。
5、向一棵2-结点树中插入新键---put()
我们新增结点会默认该节点为红链接,然后通过从底部不断的旋转到根节点来恢复树的平衡性,假设这棵红黑树只有一个根结点,如果我们插入的新键比根节点小,那么我们直接新增一个红色的左结点就可以了,如果我们新增的结点比根结点大,那么我们就新增了一个红色了右连接,这时候我们就需要旋转。
6、向树底部的2-结点插入新键---put()
我们插入一个新结点的时候,会默认这个结点为红色,如果他刚好在右连接,就得进行旋转,左连接就不需要。
7、向一个3-结点中插入新键---flipColors()
在一棵2-3树中,当只有一个3-结点时,我们插入一个新键的时候,我们需要先把它变成一个4-结点树(3个键,四条链接)然后再把它转换成一颗3个2-结点的树,这样我们就保证了树的平衡性。
变成一个4-结点,在红黑树中就是一个结点同时连接着两个红色链接,把它拆分成3个2-结点,就是将这两个红色链接都变黑。
8、向树底部3-结点插入新键
向树底部的3-结点插入新键的时候,首先我们会形成4-结点(一个结点连接着两条红色链接),然后把4-结点中3个键中间那个键移动给父结点,同时拆分这个4-结点为2个2-结点。如果父结点也是一个3-结点,那么继续重复前面的步骤直到遇到根节点或者一个2-结点的父结点。
9、put()---插入规则
在沿着 插入点到根结点的路径向上移动时在所经过的每 个结点中顺序完成以下操作,我们就能完成插入 操作:
- 如果右子结点是红色的而左子结点是黑色 的,进行左旋转;
- 如果左子结点是红色的且它的左子结点也是红色的,进行右旋转;
- 如果左右子结点均为红色,进行颜色转换
10、红黑树的构造轨迹