java实现红黑树(左旋,右旋,修复)

1. 红黑树的介绍

红黑树(Red-Black Tree 简称 R-B Tree),它是一种它一种特殊的二叉查找树。
红黑树是一种特殊的二叉搜索树,意味着它满足二叉查找树的特征(简书):

  • 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
  • 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
  • 任意节点的左、右子树也分别为二叉查找树;
  • 没有键值相等的节点

除了具备该特性之外,红黑树还包括许多额外的信息。
红黑树的每个节点上都有存储位表示节点的颜色,颜色是红(Red)或黑(Black)。
红黑树的特性:
(1) 每个节点或者是黑色,或者是红色。
(2) 根节点是黑色。
(3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!]
(4) 如果一个节点是红色的,则它的子节点必须是黑色的。
(5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
关于它的特性,需要注意的是:
第一,特性(3)中的叶子节点,是只为空(NIL或null)的节点。
第二,特性(5),确保没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。

这里大家要知道红黑树是平衡二叉树(AVL)的一种特殊实现,类似为平衡二叉树类似一个类,而红黑树是类的一个具体实例。他们的区别推荐几篇博客 : 红黑树和AVL树(平衡二叉树)区别红黑树和AVL树区别
红黑树示意图如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210126150937284.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzOTQ5Mjgw,size_16,color_FFFFFF,t_70

2. 红黑树的变换规则

在这里插入图片描述对于变换颜色来讲,无论是左子树还是右子树都是一样的,对于左旋和右旋,旋转的节点是有区别的
前提: 插入的节点的父亲是红色
情景1: 叔叔节点不存在, 或者叔叔节点为黑色,父节点为爷爷节点的左子树

  • 插入的节点为父亲节点的左子树 (LL情况)
    将父亲染为黑色,爷爷染为红色,以爷爷节点右旋
    在这里插入图片描述

  • 插入的节点为父亲节点的右子树(LR情况)
    以爸爸节点左旋回到LL情况,然后指定爸爸节点为当前节点进行下一轮处理
    在这里插入图片描述

情景2: 叔叔节点不存在, 或者叔叔节点为黑色,父节点为爷爷节点的右子树

  • 插入的节点为父亲节点的右子树(RR情况)
    在这里插入图片描述

  • 插入的节点为父亲节点的左子树 (RL情况)
    以爸爸节点右旋回到RR情况,然后指定爸爸节点为当前节点进行下一轮处理
    在这里插入图片描述

3. 红黑树的Java实现(代码说明)

红黑树的基本操作是添加、删除和旋转。在对红黑树进行添加或删除后,会用到旋转方法。为什么呢?道理很简单,添加或删除红黑树中的节点之后,红黑树就发生了变化,可能不满足红黑树的5条性质,也就不再是一颗红黑树了,而是一颗普通的树。而通过旋转,可以使这颗树重新成为红黑树。简单点说,旋转的目的是让树保持红黑树的特性。
旋转包括两种:左旋 和 右旋。下面分别对红黑树的基本操作进行介绍。

  • 基本节点定义
  1. 颜色 : boolean color
  2. 键值key : T key
  3. 左孩子 : RBNode left
  4. 右孩子 : RBNode right
  5. 父节点 : RBNode parent;
public class RBTree<T extends Comparable<T>> {

    private RBTNode<T> mRoot;   

    private static final boolean RED   = false;
    private static final boolean BLACK = true;

    public class RBTNode<T extends Comparable<T>> {
        boolean color;        
        T key;               
        RBTNode<T> left;    
        RBTNode<T> right;    
        RBTNode<T> parent;    

        public RBTNode(T key, boolean color, RBTNode<T> parent, RBTNode<T> left, RBTNode<T> right) {
            this.key = key;
            this.color = color;
            this.parent = parent;
            this.left = left;
            this.right = right;
        }

    }

    ...
}

  • 左旋
    注意: 对 x 进行左旋 就是将 x变换为一个左节点
    在这里插入图片描述左旋的实现代码(java)
/* 
 * 对红黑树的节点(x)进行左旋转
 *
 * 左旋示意图(对节点x进行左旋):
 *      px                              px
 *     /                               /
 *    x                               y                
 *   /  \      --(左旋)-.           / \                
 *  lx   y                          x  ry     
 *     /   \                       /  \
 *    ly   ry                     lx  ly  
 *
 *
 */
private void leftRotate(RBTNode<T> x) {
    // 设置x的右孩子为y
    RBTNode<T> y = x.right;

    // 将 “y的左孩子” 设为 “x的右孩子”;
    // 如果y的左孩子非空,将 “x” 设为 “y的左孩子的父亲”
    x.right = y.left;
    if (y.left != null)
        y.left.parent = x;

    // 将 “x的父亲” 设为 “y的父亲”
    y.parent = x.parent;

    if (x.parent == null) {
        this.mRoot = y;            // 如果 “x的父亲” 是空节点,则将y设为根节点
    } else {
        if (x.parent.left == x)
            x.parent.left = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
        else
            x.parent.right = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
    }
    
    // 将 “x” 设为 “y的左孩子”
    y.left = x;
    // 将 “x的父节点” 设为 “y”
    x.parent = y;
}

  • 右旋
    对y进行右旋,意味着将y变成一个右节点。
    在这里插入图片描述
    右旋的实现代码(java)
/* 
 * 对红黑树的节点(y)进行右旋转
 *
 * 右旋示意图(对节点y进行左旋):
 *            py                               py
 *           /                                /
 *          y                                x                  
 *         /  \      --(右旋)-.            /  \                     
 *        x   ry                           lx   y  
 *       / \                                   / \                   
 *      lx  rx                                rx  ry
 * 
 */
private void rightRotate(RBTNode<T> y) {
    // 设置x是当前节点的左孩子。
    RBTNode<T> x = y.left;

    // 将 “x的右孩子” 设为 “y的左孩子”;
    // 如果"x的右孩子"不为空的话,将 “y” 设为 “x的右孩子的父亲”
    y.left = x.right;
    if (x.right != null)
        x.right.parent = y;

    // 将 “y的父亲” 设为 “x的父亲”
    x.parent = y.parent;

    if (y.parent == null) {
        this.mRoot = x;            // 如果 “y的父亲” 是空节点,则将x设为根节点
    } else {
        if (y == y.parent.right)
            y.parent.right = x;    // 如果 y是它父节点的右孩子,则将x设为“y的父节点的右孩子”
        else
            y.parent.left = x;    // (y是它父节点的左孩子) 将x设为“x的父节点的左孩子”
    }

    // 将 “y” 设为 “x的右孩子”
    x.right = y;

    // 将 “y的父节点” 设为 “x”
    y.parent = x;
}
  • 添加节点
    首先红黑树是一颗特殊的二查搜索树,插入一个节点的时候我们将红黑树当作一棵二叉搜索树,插入节点,其次为了满足红黑树的特性,我们将插入的节点颜色着色为红色,最后为了保证插入节点之后,该树还是满足红黑树的特性,我们需要通过进行树的修正,例如变色或者是旋转。
    这里说一下为什么要将节点着色为红色,而不是黑色,回顾一下之前的红黑树的特性(5)

    从一个节点出发到该节点的任意子孙节点上的所有路径上包含数目相同的黑节点 
    

    将插入的节点着色为红色,不会违背"特性(5)"!少违背一条特性,就意味着我们需要处理的情况越少。接下来,就要努力的让这棵树满足其它性质即可;满足了的话,它就又是一颗红黑树了。

    添加操作的实现代码(Java语言)

/* 
* 将结点插入到红黑树中
*
* 参数说明:
*     node 插入的结点        // 对应《算法导论》中的node
*/
private void insert(RBTNode<T> node) {
   int cmp;
   RBTNode<T> y = null;
   RBTNode<T> x = this.mRoot;

   // 1. 将红黑树当作一颗二叉查找树,将节点添加到二叉查找树中。
   while (x != null) {
       y = x;
       cmp = node.key.compareTo(x.key);
       if (cmp < 0)
           x = x.left;
       else
           x = x.right;
   }

   node.parent = y;
   if (y!=null) {
       cmp = node.key.compareTo(y.key);
       if (cmp < 0)
           y.left = node;
       else
           y.right = node;
   } else {
       this.mRoot = node;
   }

   // 2. 设置节点的颜色为红色
   node.color = RED;

   // 3. 将它重新修正为一颗二叉查找树
   insertFixUp(node);
}

/* 
* 新建结点(key),并将其插入到红黑树中
*
* 参数说明:
*     key 插入结点的键值
*/
public void insert(T key) {
   RBTNode<T> node=new RBTNode<T>(key,BLACK,null,null,null);

   // 如果新建结点失败,则返回。
   if (node != null)
       insert(node);
}
    

添加修正操作的实现代码(Java语言)

/*
 * 红黑树插入修正函数
 *
 * 在向红黑树中插入节点之后(失去平衡),再调用该函数;
 * 目的是将它重新塑造成一颗红黑树。
 *
 * 参数说明:
 *     node 插入的结点        // 对应《算法导论》中的z
 */
private void insertFixUp(RBTNode<T> node) {
    RBTNode<T> parent, gparent;

    // 若“父节点存在,并且父节点的颜色是红色”
    while (((parent = parentOf(node))!=null) && isRed(parent)) {
        gparent = parentOf(parent);

        //若“父节点”是“祖父节点的左孩子”
        if (parent == gparent.left) {
            // Case 1条件:叔叔节点是红色 变色
            RBTNode<T> uncle = gparent.right;
            if ((uncle!=null) && isRed(uncle)) {
                setBlack(uncle);
                setBlack(parent);
                setRed(gparent);
                node = gparent;
                continue;
            }

            // Case 2条件:叔叔是黑色,且当前节点是右孩子 lr以父节点左旋
            if (parent.right == node) {
                RBTNode<T> tmp;
                leftRotate(parent);
                tmp = parent; //将子节点作为父亲 交换一下
                parent = node;
                node = tmp;
            }

            // Case 3条件:叔叔是黑色,且当前节点是左孩子。
            setBlack(parent);
            setRed(gparent);
            rightRotate(gparent);
        } else {    //若“z的父节点”是“z的祖父节点的右孩子”
            // Case 1条件:叔叔节点是红色
            RBTNode<T> uncle = gparent.left;
            if ((uncle!=null) && isRed(uncle)) {
                setBlack(uncle);
                setBlack(parent);
                setRed(gparent);
                node = gparent;
                continue;
            }

            // Case 2条件:叔叔是黑色,且当前节点是左孩子
            if (parent.left == node) {
                RBTNode<T> tmp;
                rightRotate(parent);
                tmp = parent;
                parent = node;
                node = tmp;
            }

            // Case 3条件:叔叔是黑色,且当前节点是右孩子。
            setBlack(parent);
            setRed(gparent);
            leftRotate(gparent);
        }
    }

    // 将根节点设为黑色
    setBlack(this.mRoot);
}
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
做一门精致,全面详细的 java数据结构与算法!!!让天下没有难学的数据结构,让天下没有难学的算法,不吹不黑,我们的讲师及其敬业,可以看到课程视频,课件,代码的录制撰写,都是在深夜,如此用心,其心可鉴,他不掉头发,谁掉头发???总之你知道的,不知道的,我们都讲,并且持续更新,走过路过,不要错过,不敢说是史上最全的课程,怕违反广告法,总而言之,言而总之,这门课你值得拥有,好吃不贵,对于你知识的渴求,我们管够管饱话不多说,牛不多吹,我们要讲的本门课程内容:稀疏数组、单向队列、环形队列、单向链表、双向链表、环形链表、约瑟夫问题、栈、前缀、中缀、后缀表达式、中缀表达式转换为后缀表达式、递归与回溯、迷宫问题、八皇后问题、算法的时间复杂度、冒泡排序、选择排序、插入排序、快速排序、归并排序、希尔排序、基数排序(桶排序)、堆排序、排序速度分析、二分查找、插值查找、斐波那契查找、散列、哈希表、二叉树、二叉树与数组转换、二叉排序树(BST)、AVL树、线索二叉树、赫夫曼树、赫夫曼编码、多路查找树(B树B+树和B*树)、图、图的DFS算法和BFS、程序员常用10大算法、二分查找算法(非递归)、分治算法、动态规划算法、KMP算法、贪心算法、普里姆算法、克鲁斯卡尔算法、迪杰斯特拉算法、弗洛伊德算法马踏棋盘算法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值