红黑树的基本性质及操作

   JDK1.7的时候 HashMap是由数组+链表实现  -> JDK1.8时推出的红黑树,用于解决出现哈希冲突的情况。这是红黑树在面试时常被问到的点。下面就从二叉搜索树来一步一步介绍理解红黑树。

  红黑树(Red Black Tree) 是一种自平衡二叉查找树,在1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的“红黑树”。

要想理解红黑树,先介绍其最基本的结构二叉排序树,也叫二叉搜索树:二叉树排序是指树中节点的度不大于2的有序树,它有如下性质:

1.若任意结点的左子树不空,则左子树上所有结点的值均不大于它的根结点的值。

2. 若任意结点的右子树不空,则右子树上所有结点的值均不小于它的根结点的值。

3.任意结点的左、右子树也分别为二叉排序树。

 可以见到,二叉搜索树有其优良的性质,可以算是二分算法的数据结构版,若用它来存储数据,那么查找数据时的时间复杂度一般为logn。

但是,二叉搜索树可能会出现一种问题:退化成一个链表;

 可以见到,此时的二叉搜索树的性能就降低到了n。那么该如何构建二叉搜索树而防止这种问题呢?

1.用AVL树:完全平衡二叉树(追求极致的平衡,转成极致的情况,一般情况下很难用到)

2.用红黑树。红黑树就是一种特殊的二叉搜索树,也是一种自平衡二叉查找树。

在HashMap中为何舍弃AVL树而采取红黑树:

AVL树更加严格平衡,因此可以提供更快的查找效果,因此,对于密集型查找任务,使用AVL树,而对于插入密集型任务,应该使用红黑树。一方面,AVL树比红黑树保持更加严格的平衡。AVL树中从根到最深叶的路径最多为~1.44 lgn + 2),而在红黑树中最多为~2 lgn + 1),因此,在AVL树中查找通常更快。另一方面,在插入和删除时,AVL树速度较慢:因为需要更高的旋转次数才能在修改时正确地重新平衡数据结构。

对于通用实现(即先验并不清楚查找是否是操作的主要部分),RedBlack树是首选:它们更容易实现,并且在常见情况下更快 - 无论数据结构如何经常被搜索修改。

在CurrentHashMap中是加锁了的,实际上是读写锁,如果写冲突就会等待,如果插入时间过长必然等待时间更长,而红黑树相对AVL树他的插入更快!

下面介绍红黑树的性质:

  1. 每个结点不是红色就是黑色,根节点都是黑色root
  2. 不可能有连载一起的红色结点
  3. 根结点到所有的【空结点】的路径上都具有相同数量的黑色结点
  • 根据第三条性质可以推出,红黑树中每个节点到【所在子树中的空节点】的路径上都具有相同数量的黑色节点
  • 如果节点是红色节点,则这个节点要么没有子节点,要么有两个子节点,并且都是黑色节点,因为需要满足第二条和第三条性质
  • 如果节点是黑色节点,并且有一个黑色子节点,则另一个子节点一定存在,为红色节点或者黑色节点,因为需要满足第三条性质

 

红黑树有三种常用操作:

  1. 改变颜色
  2. 左旋 右子为轴,当前结点左旋  轴结点的左孩子变成待旋转结点的右孩子

  3. 右旋

 

什么时候要变色左旋右旋?

 - 破坏了红黑树规则的情况下要旋转变色。

旋转和颜色变换规则:所有插入的点默认为红色(因为如果结点都是黑色的话就直接满足了红黑树所有的性质,变换不了)

  1. 变颜色的情况:当前结点的父亲是红色,且它的祖父结点的另一个子节点也是红色(叔叔结点):
  1. 把父节点变为黑色
  2. 把叔叔结点也变为黑色
  3. 把祖父结点设为红色
  4. 把指针定义到祖父结点设为当前要操作的
  1. 左旋:当前父节点是红色,叔叔是黑色的时候,且当前的结点是右子树,左旋,以父节点作为左旋
  2. 右旋:当前父节点是红色,叔叔是黑色的时候,且当前的结点是左子树,右旋
  1. 把父结点变为黑色
  2. 把祖父结点变为红色
  3. 以祖父结点旋转

这里,加了一个6之后,进行了颜色变换,将操作结点放到了12上,此时满足左结点旋转情况,然后进行左旋。 

到这,红黑树的基本性质和操作介绍完毕。下面是附带红黑树的java源码

 

package cs0416;


import com.sun.source.tree.Tree;

import javax.swing.tree.TreeNode;

//这是红黑树的第一种实现(等价于2-3-4查找树、4阶B树)
public class RBT_1 {

    private TreeNode root;
    private TreeNode sentry; //定义哨兵结点,其它结点的空指针都指向哨兵节点 应该定义final 但是为什么会出错
    private int size;
    private int height ; //定义高度

    private static class TreeNode{
        public Comparable comparable;
        public TreeNode parent ;
        public TreeNode left ;
        public TreeNode right;
        public boolean color;

        public TreeNode(Comparable comparable , boolean color){
            this.comparable = comparable;
            this.color = color;
            this.parent = null;
            this.left = null;
            this.right = null;
        }
    }

    public RBT_1(){
        this.sentry = new TreeNode(null , false);//哨兵结点为黑色
        this.root = sentry;
        this.size = 0;
        this.height = 0;
    }

    //基本操作  查找某棵子树的最左结点和最右结点
    private TreeNode getMin(TreeNode node){
        while(node.left != sentry)
            node = node.left;
        return node;
    }

    private TreeNode getMax(TreeNode node){
        while(node.right != sentry)
            node = node.right;
        return node;
    }

    //旋转操作
    //左旋 将结点与右结点交换角色与颜色
    private void rotateLeft(TreeNode node){
        TreeNode temp = node.right;
        //调整结点与其右子结点的左子结点的关系
        node.right = temp.left;
        //调整结点的父节点与其右子结点的关系
        temp.parent = node.parent;
        if(node == root)
            root = temp;
        else if(node == node.parent.left)
            node.parent.left = temp;
        else
            node.parent.right = temp;
        //调整结点与其右子结点关系,交换颜色
        temp.left = node;
        node.parent = temp;
        boolean flag = node.color;
        node.color = temp.color;
        temp.color = flag;
        return;
    }
    //右旋
    private void rotateRight(TreeNode node){
        TreeNode temp = node.left;
        node.left = temp.right;
        temp.parent = node.parent;
        if(node == root)
            root =temp;
        else if(node == node.parent.left)
            node.parent.left = temp;
        else
            node.parent.right = temp;
        temp.right = node;
        node.parent = temp;
        boolean flag = node.color;
        node.color = temp.color;
        temp.color = flag;
        return;

    }
    //变色操作:子节点都变为黑色 , 结点变为红色
    private static void convert(TreeNode node){
        node.color = true;
        node.left.color = false;
        node.right.color = false;
        return;
    }
    //查找操作
    private TreeNode search(Comparable comparable){
        TreeNode node = root;
        while(node != sentry){
            if(comparable.compareTo(node.comparable) == 0)
                break;
            else if(comparable.compareTo(node.comparable) < 0)
                node = node.left;
            else
                node = node.right;
        }
        return node;
    }
    //交换操作 交换结点保存的元素
    private void swap(TreeNode one , TreeNode other){
        Comparable comparable = one.comparable;
        one.comparable = other.comparable;
        other.comparable = comparable;
        return;
    }
    //替换结点
    private void replace(TreeNode source, TreeNode target) {
        if (target == root)
            root = source;
        else if (target == target.parent.left)
            target.parent.left = source;
        else
            target.parent.right = source;
        source.parent = target.parent;
        return;
    }
    //添加元素
    public void insert(Comparable comparable) {
        TreeNode node = root;
        TreeNode temp = sentry;//新节点的父节点
        //查找添加的位置
        while (node != sentry) {
            temp = node;
            if (comparable.compareTo(node.comparable) < 0)
                node = node.left;
            else
                node = node.right;
        }
        node = new TreeNode(comparable, true);//新节点都为红色
        node.parent = temp;
        if (temp == sentry)//添加第一个元素
            root = node;
        else if (comparable.compareTo(temp.comparable) < 0)
            temp.left = node;
        else
            temp.right = node;
        node.left = sentry;
        node.right = sentry;
        //自底向上调整红黑树
        correct_insert(node);
        size++;
        return;
    }
    //添加元素后修正节点,保持红黑树的性质
    private void correct_insert(TreeNode node) {
        TreeNode temp;
        while (node.parent.color) {//节点和父节点都为红色,并且父节点不是根节点
            if (node.parent == node.parent.parent.left) {//父节点是左子节点
                temp = node.parent.parent.right;
                if (!temp.color) {//父节点的兄弟节点为黑色,进行旋转,旋转之后,修正结束
                    if (node == node.parent.right) {
                        node = node.parent;
                        rotateLeft(node);
                    }
                    rotateRight(node.parent.parent);
                }else {//父节点的兄弟节点为红色,进行变色,变色之后,向上继续修正
                    node = node.parent.parent;
                    convert(node);
                }
            }else {//父节点是右子节点
                temp = node.parent.parent.left;
                if (!temp.color) {//父节点的兄弟节点为黑色,进行旋转,旋转之后,修正结束
                    if (node == node.parent.left) {
                        node = node.parent;
                        rotateRight(node);
                    }
                    rotateLeft(node.parent.parent);
                }else {//父节点的兄弟节点为红色,进行变色,变色之后,向上继续修正
                    node = node.parent.parent;
                    convert(node);
                }
            }
        }
        if (node == root) {//当前节点为根节点,则根节点变为红色,红黑树的黑色高度将加一
            root.color = false;
            height++;
        }
        return;
    }
    //传入被删节点,查找替代节点,使用替代节点的元素替代被删节点的元素,保留被删节点的颜色,删除替代节点
    private Comparable delete(TreeNode node) {
        TreeNode substitute;//替代节点
        TreeNode start;//调整红黑树的起始节点
        //优先使用前驱节点作为替代节点
        if (node.left != sentry) {//存在左子节点,以前驱节点替代被删节点
            substitute = getMax(node.left);
            start = substitute.left;
        }else if (node.right != sentry) {//存在右子节点,以后继节点替代被删节点
            substitute = getMin(node.right);
            start = substitute.right;
        }else {//不存在子节点,被删节点作为替代节点
            substitute = node;
            start = sentry;
        }
        swap(node, substitute);//替代元素
        replace(start, substitute);//起始节点取代替代节点的位置
        if (!substitute.color)//替代节点的颜色,即删除的颜色是黑色
            correct_delete(start);
        size--;
        return substitute.comparable;
    }
    //删除元素后修正节点,保持红黑树的性质
    private void correct_delete(TreeNode node) {
        TreeNode temp;
        while (node != root && !node.color) {//当前节点为黑色节点,并且不为根节点
            if (node == node.parent.left) {//当前节点是左子节点
                temp = node.parent.right;
                if (temp.color) {//兄弟节点为红色
                    rotateLeft(node.parent);
                }else if (!temp.left.color && !temp.right.color) {//兄弟节点为黑色,左右子节点都为黑色
                    temp.color = true;
                    node = node.parent;
                }else {//兄弟节点为黑色,左右子节点中存在红色
                    if (!temp.right.color) {//兄弟节点为黑色,其右子节点为黑色,则左子节点为红色
                        rotateRight(temp);
                    }else {//兄弟节点为黑色,其右子节点为红色
                        temp.right.color = false;
                        rotateLeft(node.parent);
                        break;
                    }
                }
            }else {//当前节点为右子节点
                temp = node.parent.left;
                if (temp.color) {//兄弟节点为红色
                    rotateRight(node.parent);
                }else if (!temp.left.color && !temp.right.color) {//兄弟节点为黑色,左右子节点都为黑色
                    temp.color = true;
                    node = node.parent;
                }else {//兄弟节点为黑色,左右子节点中存在红色
                    if (!temp.left.color) {//兄弟节点为黑色,其左子节点为黑色,则右子节点为红色
                        rotateLeft(temp);
                    }else {//兄弟节点为黑色,其左子节点为红色
                        temp.left.color = false;
                        rotateRight(node.parent);
                        break;
                    }
                }
            }
        }
        if (node == root)//当前节点为根节点,则红黑树的黑色高度将减一
            height--;
        else
            node.color = false;
        return;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值