zoukankan      html  css  js  c++  java
  • 二叉查找树之红黑树

     关于红黑树,先上一张图,这里推荐一个可视化数据结构的网站:可视化带动画的数据结构基本的在线操作,我在这上面生成了一张:

    网上有很多教程,写的也非常棒,这里简单的总结一下其基本性质:

    1.每一个节点或者是红色,或者是黑色;

    2. 根节点是黑色的;

    3.如果一个节点是红色的,那么它的子节点必须是黑色的;

    4.从一个节点到一个null引用(如上图 的 60节点的左子节点其实是没有的)的每一条路径一定包含相同数目的黑色节点

    5.规定 null引用指向的节点(即空节点)是黑色的。

    二 .为啥有了AVL树还要有红黑树

    根据二者的结构图很容易看出红黑树的查找速度明显不如AVL树,为啥,因为它的平衡度没有AVL树好,即某些节点的深度会比AVL树大。那是为啥呢,稍后做个数据抽样测试:

    三 .红黑树在插入和删除的时候是如何维护平衡的

    根据上面描述的基本性质,可以推断维护平衡的其中一个条件是 给节点涂上颜色,但是涂上颜色后不一定平衡, 这就用到了前面AVL树的维持平衡的方式:旋转

    综合即两种方式:

    1. 改变节点的颜色

    2.旋转节点

    四 .红黑树的插入实现,有图有代码实现:

    假设我们新插入的节点都是红色, 一会儿说明原因,下面先分析插入位置的几种情况,就像AVL树那样:

    1.写入根节点

             即当前的树是空树,第一次插入 ,直接new 一个节点,根据红黑树的定义 根节点是黑色的,指定这个节点是黑色,如插入节点8:

    2. 插入的节点的父节点是黑色节点

            为了满足上面定义的性质 后3条,插入的节点必须是红色的,而且插入后的树一定是平衡的,无需任何操作,这得益于上面的第4条性质,如下面的树插入值是2 的节点

                       

    插入2以后:    

     

    从这里能明显说明:如果插入的节点是红色,对于维护树的性质 不需要过多的操作,要简单的多, 所以规定先插入的节点的颜色默认是红色 。

    3. 插入的节点的父节点是红色,叔叔节点也是红色

              这里强调一下,叔叔节点 是插入节点的祖父节点的另一个子节点,如图下面红黑树,插入节点3,其父节点 4 和叔叔节点 7 都是红色

    插入3后: 

              这时,违反了性质3: 红色节点的子节点只能是黑色节点

    解决办法:

    父亲和叔叔都变成黑色,祖父变成红色:

    4. 插入的节点的父节点是红色,叔叔节点是黑色

              a. 插入到左子节点,接着上图,如下图,这里插入节点 65:

    这里说明一下:父节点是红色,叔叔节点因为是祖父节点的null引用,所以是一个黑色节点,即空节点是黑色

    这里也违反了 性质3:红色节点的子节点必须是黑色节点

    解决办法:

    先以插入节点的父节点 为支点右旋,然后就变成另外一种情况b 了,即 新节点插入到右子节点。

    如图先右旋:

    此时转向下面的情况:

               b. 插入到右子节点,接着上图,此时把70当插入节点,65当父节点:

               解决办法:

                     父节点 65 变黑,祖父节点 60 变红,并以祖父节点60 为支点左旋:

    为了验证这种操作的通用性,这里我再做一组实验

     接着上图,这里我插入值是 2 的节点,再次验证上面的  a情况 :

     这里为了对比直接将上面的图拷下来:

    插入2 后: 

    此时,按上面 a 的解决方案,直接右旋插入节点的父节点,就变成了:

    右旋 3 后: 

    这又变成了上面的情况 b,直接把插入节点指向原来的父节点,再对 新插入的节点(即现在的 3)进行情况b的操作,即: 3 的父节点 2 变成黑色,祖父节点 4 变成红色,左旋祖父节点 4 :

    嘿 !,问题出来,左旋 4 后,因为4没有右节点,原来 4 的位置不是空吗, 这不对啊, 仔细观察发现 :这时 应该左旋 2 ,即新节点的父节点,旋转后就变成了上面那个图了,又回去了,这样就成死循环了。

    忽略这个图,回到上面的图,这里再次把那个图拷过来:

    仔细观察,发现正常的操作应该是 :

    父节点 3 变黑,祖父节点 4 变红,右旋祖父节点 4:

    这样维护了整个树的平衡,但是同样 父节点是红色,叔叔节点是黑色,插入的值在父节点的左儿子上,为啥跟上面的  a 情况不一样呢!

    回顾一下前面的AVL树的插入  其中  左右 和 右左两种情况分别需要旋转2次才能解决, 同样这里也是一样:在 a 情况中 插入 65 就是 右 左 情况, 同理 b 情况也会出现类似的现象

    所以在4 的情况下 插入后整个树的平衡(即满足红黑树的4条性质)调整不仅依赖于 插入节点是否是左右节点,还依赖于其父节点是否是左节点还是右节点

    下面根据上面几幅图的操作,总结一下 插入的节点的父节点是红色,叔叔节点是黑色 的整个操作

    • 插在左子树上,且父节点是祖父的左子节点:

                    父节点变黑,祖父节点变红, 右旋祖父节点

    • 插在左子树上,且父节点是祖父的右子节点:

                   右旋父节点,并把插入节点指向父节点,即原来的父节点变成了插入节点(新节点),

                新节点的父节点变黑 ,新节点的祖父节点变红, 左旋新节点的祖父节点 【这里参考对象变了,所以描述的比较啰嗦】

    • 插在右子树上,且父节点是祖父的右子节点:

                父节点变黑,祖父节点变红,左旋祖父节点  (跟 2 的第二步一模一样)

    • 插在右子树上,且父节点是祖父的左子节点:

                  左旋父节点,并把插入节点指向父节点,即原来的父节点变成的插入节点(新节点),

                  新节点的父节点变黑 ,新节点的祖父节点变红, 右旋新节点的祖父节点 【这里参考对象变了,所以描述的比较啰嗦】

    红黑树的插入完整编码实现:

      1 public class RBTree {
      2 
      3     /**
      4      * 红黑树的根节点
      5      */
      6     private TreeNode root;
      7 
      8     /**
      9      * 根据红黑树的性质,定义树节点
     10      * 树节点对象
     11      */
     12     static class TreeNode {
     13         /**
     14          * 节点的值
     15          */
     16         private int val;
     17 
     18         /**
     19          * 左子节点
     20          */
     21         private TreeNode left;
     22 
     23         /**
     24          * 右子节点
     25          */
     26         private TreeNode right;
     27 
     28         /**
     29          * 父亲节点
     30          */
     31         private TreeNode parent;
     32 
     33         /**
     34          * 是否红色节点
     35          */
     36         private boolean red;
     37 
     38         /**
     39          * 默认插入的节点是红色的
     40          *
     41          * @param val
     42          */
     43         public TreeNode(int val) {
     44             this.val = val;
     45             this.red = true;
     46         }
     47 
     48     }
     49 
     50     /**
     51      * @param value 待插入的值
     52      */
     53     public void add(int value) {
     54         TreeNode t = root;
     55         if (root == null) {
     56             root = new TreeNode(value);
     57             //根节点是黑色的
     58             root.red = false;
     59             return;
     60         }
     61         TreeNode parent = null;
     62         while (t != null) {
     63             //第一次parent指向root, t 也是root
     64             parent = t;
     65             if (value < t.val)
     66                 t = t.left;
     67             else if (value > t.val)
     68                 t = t.right;
     69             else
     70                 t.val = value;
     71         }
     72         t = new TreeNode(value);
     73         //parent是上一次循环后的t
     74         t.parent = parent;
     75         if (parent != null) {
     76             if (t.val < parent.val)
     77                 parent.left = t;
     78             else
     79                 parent.right = t;
     80         }
     81         //添加完后 t是新的插入节点,对新插入的节点进行平衡调整
     82         fixAfterInsertion(t);
     83         //修复完后,最终的根节点的颜色是黑色
     84         root.red = false;
     85     }
     86 
     87     /**
     88      * 插入后新插入的节点进行从下向上变色,和旋转处理
     89      *
     90      * @param t
     91      * @solutions :
     92      * 1. 插在左子树上,且父节点是祖父的左子节点:
     93      * 父节点变黑,祖父节点变红, 右旋祖父节点
     94      * 2. 插在左子树上,且父节点是祖父的右子节点:
     95      * 右旋父节点,并把插入节点指向父节点,即原来的父节点变成了插入节点(新节点),
     96      * 新节点的父节点变黑 ,新节点的祖父节点变红, 左旋新节点的祖父节点 【这里参考对象变了,所以描述的比较啰嗦】
     97      * 3. 插在右子树上,且父节点是祖父的右子节点:
     98      * 父节点变黑,祖父节点变红,左旋祖父节点  (跟 2 的第二步一模一样)
     99      * 4. 插在右子树上,且父节点是祖父的左子节点:
    100      * 左旋父节点,并把插入节点指向父节点,即原来的父节点变成的插入节点(新节点),
    101      * 新节点的父节点变黑 ,新节点的祖父节点变红, 右旋新节点的祖父节点 【这里参考对象变了,所以描述的比较啰嗦】
    102      */
    103     private void fixAfterInsertion(TreeNode t) {
    104         while (t != null && t != root && t.red && t.parent.red) {
    105             TreeNode parent = t.parent;
    106             TreeNode grand = parentOf(parent);
    107             TreeNode uncle = uncleOf(t);
    108             //叔叔是null或者是黑色, 即叔叔都是黑色
    109             if (uncle == null || (!uncle.red)) {
    110                 //1.插在左子树上
    111                 if (parent.left == t) {
    112                     //1.1. 父节点是祖父的左子节点:  父节点变黑,祖父节点变红, 右旋祖父节点
    113                     if (parent == grand.left) {
    114                         setColor(parent, false);
    115                         setColor(grand, true);
    116                         rotateRight(grand);
    117                     } else {
    118                         //1.2. 父节点是祖父的右子节点:
    119                         // 右旋父节点,并把插入节点指向父节点,即原来的父节点变成了插入节点(新节点),下面这一步就递归了。
    120                         // 新节点的父节点变黑 ,新节点的祖父节点变红, 左旋新节点的祖父节点
    121                         t = parent;
    122                         rotateRight(parent);
    123                     }
    124                 } else {
    125                     //2. 插在右子树上
    126                     //2.1. 父节点是祖父的右子节点:  父节点变黑,祖父节点变红,左旋祖父节点
    127                     if (parent == grand.right) {
    128                         setColor(parent, false);
    129                         setColor(grand, true);
    130                         rotateLeft(grand);
    131                     } else {
    132                         //2.2. 父节点是祖父的左子节点:
    133                         //左旋父节点,并把插入节点指向父节点,即原来的父节点变成的插入节点(新节点),
    134                         //新节点的父节点变黑 ,新节点的祖父节点变红, 右旋新节点的祖父节点
    135                         t = parent;
    136                         rotateLeft(parent);
    137 
    138                     }
    139                 }
    140             } else {
    141                 //叔叔是红色,直接把叔叔和父亲变成黑色,爷爷变成红色
    142                 //父亲,叔叔变黑
    143                 setColor(parentOf(t), false);
    144                 setColor(uncleOf(t), false);
    145                 //爷爷变红
    146                 setColor(grandfatherOf(t), true);
    147             }
    148         }
    149     }
    150 
    151     /**
    152      * 给指定的节点上色
    153      *
    154      * @param treeNode
    155      * @param b        false : 黑色,true : 红色
    156      */
    157     private void setColor(TreeNode treeNode, boolean b) {
    158         if (treeNode != null)
    159             treeNode.red = b;
    160     }
    161 
    162     /**
    163      * 左旋
    164      * 基本的左旋,左旋与avl树一样
    165      *
    166      * @param node
    167      */
    168     private void rotateLeft(TreeNode node) {
    169         TreeNode right = node.right;
    170         node.right = right.left;
    171         if (right.left != null)
    172             right.left.parent = node;
    173         right.parent = node.parent;
    174         //重新给父节点的孩子赋值
    175         if (node.parent == null)
    176             root = right;
    177         else if (node.parent.left == node)
    178             node.parent.left = right;
    179         else
    180             node.parent.right = right;
    181         //重新给孩子的父节点赋值
    182         right.left = node;
    183         node.parent = right;
    184     }
    185 
    186     /**
    187      * 右旋
    188      * 基本的右旋, 右旋与avl树一样
    189      *
    190      * @param node
    191      */
    192     private void rotateRight(TreeNode node) {
    193         TreeNode left = node.left;
    194         node.left = left.right;
    195         if (left.right != null)
    196             left.right.parent = node;
    197         left.parent = node.parent;
    198         //重新给父节点的孩子赋值
    199         if (node.parent == null)
    200             root = left;
    201         else if (node.parent.left == node)
    202             node.parent.left = left;
    203         else
    204             node.parent.right = left;
    205         //重新给孩子的父节点赋值
    206         left.right = node;
    207         node.parent = left;
    208     }
    209 
    210     /**
    211      * 获取指定节点node 的父节点
    212      *
    213      * @param node
    214      * @return
    215      */
    216     private TreeNode parentOf(TreeNode node) {
    217         return node == null ? null : node.parent;
    218     }
    219 
    220     /**
    221      * 获取指定节点node的叔叔节点(父亲的兄弟)
    222      *
    223      * @param node
    224      * @return
    225      */
    226     private TreeNode uncleOf(TreeNode node) {
    227         TreeNode parent = parentOf(node);
    228         TreeNode left = leftOf(grandfatherOf(node));
    229         if (parent == left)
    230             return rightOf(grandfatherOf(node));
    231         else
    232             return left;
    233     }
    234 
    235     /**
    236      * 获取指定节点的爷爷节点
    237      *
    238      * @param node
    239      * @return
    240      */
    241     private TreeNode grandfatherOf(TreeNode node) {
    242         return parentOf(parentOf(node));
    243     }
    244 
    245     /**
    246      * 获取指定节点的左子节点
    247      *
    248      * @param node
    249      * @return
    250      */
    251     private TreeNode leftOf(TreeNode node) {
    252         return node == null ? null : node.left;
    253     }
    254 
    255     /**
    256      * 获取指定节点node的右子节点
    257      *
    258      * @param node
    259      * @return
    260      */
    261     private TreeNode rightOf(TreeNode node) {
    262         return node == null ? null : node.right;
    263     }
    264 
    265 
    266     /**
    267      * 断点测试一下
    268      * @param args
    269      */
    270     public static void main(String[] args) {
    271         RBTree obj = new RBTree();
    272         obj.add(4);
    273         obj.add(2);
    274         obj.add(1);
    275         obj.add(5);
    276         obj.add(6);
    277         obj.add(10);
    278         obj.add(8);
    279         System.err.println(obj.root);
    280     }
    281     //嗯,画出图来,好使
    282 }

    不错,要的就是这儿效果

  • 相关阅读:
    Flesch Reading Ease(模拟)
    实验一:词法分析设计
    java—容器学习笔记
    [转载]马士兵Java视频教程 —— 学习顺序
    Java的安装过程
    编程之美初赛第一场
    RCC 2014 Warmup (Div. 2)
    ural 1017. Staircases(dp)
    ural 1012. K-based Numbers. Version 2(大数dp)
    ural 1009. K-based Numbers(简单dp)
  • 原文地址:https://www.cnblogs.com/blentle/p/8386256.html
Copyright © 2011-2022 走看看