红黑树,一个很牛x的数据结构,作为目前JDK的hashmap的底层,是一个兼顾了空间和时间的完美二叉查找树,在AVL的平衡性上做出了巨大改进。首先它的本质是一种特殊的AVL树,祖父辈是二叉排序树,就是那个左子节点必定小于等于它,右子节点必定大于它的树。
首先要了解红黑树就要从他的基本性质说起,
1.根节点必定为黑色
2.不能有两个连接的红色节点
3.节点颜色只能为红或者黑
4.任意节点到每个叶子节点途径的黑色节点数都是相同的
5.所有的子节点(NIL)必定为黑色
第一个性质,硬性规定,没有什么好说的
第二个性质,红色不能相连,如果相连被允许,那么该树将无法确定平衡方法
第三个性质,红黑树红黑树,难道还能有绿色吗?
第四个性质,这是红黑树设计时所考虑到的独特的平衡性,即去掉所有红色节点后,该树的黑色叶子结点会在同一层
第五个性质,为了更好的观摩性质4提出的(实际不考虑也可以)
那么性质讲完了,就该讲讲操作了,
首先插入操作,插入时需要考虑很多东西,先是按照二叉排序树方式插入,插入时规定每个节点颜色为红色以便于自我平衡,最后再进行自我平衡。首先要判断是否为根节点,然后再进行迭代操作进行插入。最后再自我平衡。
public class RBT<T>{
private Node root;
private int R=0;
private int B=1;
//内部类,节点
class Node{
Node left;
Node right;
Node parent;
private T key;
int color;
public Node(T key) {
this.color=R;
this.key = key;
}
}
//插入操作
public void insert(T key)
{
Node node=new Node(key);
if(root==null)
{
root=node;
root.color=B;
return;
}
Node parent=root;
Node son=null;
if((Integer)(node.key)<=(Integer)(parent.key))
{
son=parent.left;
}
else {
son=parent.right;
}
while (son!=null)
{
parent=son;
if((Integer)(node.key)<=(Integer)(parent.key))
{
son=parent.left;
}
else {
son=parent.right;
}
}
if(key <=parent.key){
parent.left = node;
}else {
parent.right = node;
}
node.parent=parent;
insertPolicy(node);
}
自我平衡,这是红黑树实现的关键部分,这边先要了解三个原子操作,变色,左旋,右旋。
1.变色操作,条件叔叔和父亲都为红色,当然它的自身也为红色。那么就能进行变色操作。
操作核心,将父亲和叔叔变黑,爷爷节点(祖父)变成红色,这个操作不一定就能变好,所以然后把当前节点设置为爷爷节点,进行下一步操作。
2.左旋操作,直接说可能说不清直接上图(图片来源网络,侵权删
伪代码(不考虑父节点的情况下)
right=node.right;
node.parent=right;
node.right=right.left;
right.left=node;
具体操作为(结点的右树变为父节点的子节点),然后将右子节点的左树挂给当前结点做右树,让右子结点成为当前节点的父亲结点,当前结点为右子结点的左树。
3.右旋操作,继续上图
伪代码(仍然是不考虑父节点的情况)
left=node.left;
node.left=left.right;
left.right=node;
node.parent=left;
接下来再讲讲具体的平衡策略,首先我们要分清楚左倾和右倾的情况,一般而言建立左倾是最方便的,但是操作复杂度会增加,最好的方式就是让程序自己去平衡是要左倾还是右倾。
来看一下具体操作吧,
1.变色的约束条件,上面写了。
2.父结点为左子树的左旋条件,当前结点在父节点右侧,当前父节点为红色,叔叔结点是黑色,左旋时以父节点为旋转结点,之后将父节点视作当前结点,进行下一步操作
3.父结点亲左子树的右旋条件,父节点为红,叔叔为黑,
然后先变色再旋,父节点为黑,爷爷节点为红,然后以爷爷节点为旋转结点。
以上是父亲是祖父节点左子节点时候的情况,那么父节点为右子结点呢,很简单我们进行对称操作就行。
具体操作:
1.操作不变,2操作的判定改为当前结点为父节点的左节点,左旋改为以父节点右旋,3操作右旋改左旋,其他全部不改变。我们发现进行操作的前提必然是父节点为红,所以我们进行代码优化,将这一步作为大条件。
然后我们直接上代码。
//平衡策略
public void insertPolicy(Node node)
{
Node father,grandpa;
while((father=node.parent)!=null&&father.color==R)
{
grandpa=father.parent;
if(grandpa.left==father)
{
Node uncle=grandpa.right;
//叔叔为红,父亲为红就进行变色操作
if(uncle!=null&&uncle.color==R)
{
father.color=B;
uncle.color=B;
grandpa.color=R;
node=grandpa;
continue;
}
//左旋
if(node==father.right)
{
leftRotate(father);
Node tmp=node;
node=father;
father=tmp;
}
father.color=B;
grandpa.color=R;
rightRotate(grandpa);
}
else{//父节点在右
Node uncle=grandpa.left;
if(uncle!=null&&uncle.color==R)
{
father.color=B;
uncle.color=B;
grandpa.color=R;
node=grandpa;
continue;
}
if(node==father.left)
{
rightRotate(father);
Node tmp=node;
node=father;
father=tmp;
}
father.color=B;
grandpa.color=R;
leftRotate(grandpa);
}
}
root.color=B;
}
//左旋
private void leftRotate(Node node)
{
Node right=node.right;
Node parent=node.parent;
if(parent==null)
{
root=right;
right.parent=null;
}
else {
if(parent.left!=null&&node==parent.left)
{
parent.left=right;
}else parent.right=right;
right.parent=parent;
}
node.parent=right;
node.right=right.left;
if(right.left!=null)
{
right.left.parent=node;
}
right.left=node;
}
//右旋
private void rightRotate(Node node)
{
Node left=node.left;
Node parent=node.parent;
if(parent==null)
{
root=left;
left.parent=null;
}
else {
if(parent.left!=null&&node==parent.left)
{
parent.left=left;
}
else {
parent.right=left;
}
left.parent=parent;
}
node.parent=left;
node.left=left.right;
if(left.right!=null)
{
left.right.parent=node;
}
left.right=node;
}
public void mid(Node node)
{
if(node!=null) {
mid(node.left);
System.out.println(node.key);
mid(node.right);
}
}
public void first(Node node)
{
if(node!=null)
{
System.out.println(node.key);
first(node.left);
first(node.right);
}
}