上一篇写了关于红黑树基本性质的东西,这篇来说一说如何创建一棵红黑树吧。

  如果对红黑树的基本性质还有疑问,请先查看一下我的前一篇:http://www.cnblogs.com/unpolishedgem/archive/2012/05/16/2504311.html。

  如果图片打不开的话,就去看我的csdn博客:http://blog.csdn.net/arge129。

  红黑树是一种二叉查找树,那么我们可以使用插入的方法来创建一棵红黑树,为此,我们先来介绍关于红黑树的一些基本操作。

  1. 旋转

  旋转是一种能保持二叉查找树性质的查找树局部操作,包括左旋和右旋两种操作。

  如下图所示,在x结点上做左旋时,我们假设它的右孩子不是nil[T];x可以是树内任意右孩子不是nil[T]的结点。算法导论里面讲到“左旋以x到y之间的链为支轴进行。”我没太理解这句话,但是我是这么想象的,如下图中的曲线箭头所示,左旋就是x下移,y上移,箭头所示方向为左,右旋就是x上移,y下移,箭头所示方向为右。

  值得注意的是,在旋转过程中,只会有指针结构的变化,不会有颜色的变化,因此在上面的图中,我没有画出结点的颜色。

旋转的伪代码,我就不写了,在算法导论里面都有,下面我把我写的旋转代码给贴过来吧,当然还是Java版的。

 1 /**2      * 左旋3      * @author Alfred4      * @param x 输入结点5      */6     private void leftRotated(RBTreeNode x){7         RBTreeNode y = x.getRight();8         //x的右孩子y不能是NIL_T,如果是的话,直接返回。9         if(y == NIL_T){
10             return;
11         }
12         //将y的左子树变为x的右子树
13         //设置x的右子树
14         x.setRight(y.getLeft());
15         //设置y的右子树的父结点为x
16         if(y.getLeft() != NIL_T){
17             y.getLeft().setParent(x);
18         }
19         //将x的父结点设置为y的父结点
20         y.setParent(x.getParent());
21         //如果x是根结点,则更换根结点
22         if(x.getParent() == NIL_T){
23             rootNode = y;
24         }else if(x == x.getParent().getLeft()){
25             //如果x是其父结点的左孩子,则将y设为其父结点的左孩子
26             x.getParent().setLeft(y);
27         }else{
28             //如果x是其父结点的右孩子,则将y设为其父结点的右孩子
29             x.getParent().setRight(y);
30         }
31         //y的左孩子为x
32         y.setLeft(x);
33         //x的父结点为y
34         x.setParent(y);
35     }
36     /**
37      * 右旋
38      * @author Alfred
39      * @param y 输入结点
40      */
41     private void rightRotated(RBTreeNode y){
42         RBTreeNode x = y.getLeft();
43         //y的左孩子x不能是NIL_T,如果是的话,直接返回。
44         if(x == NIL_T){
45             return;
46         }
47         //将x的右子树变为y的左子树
48         //设置y的左子树
49         y.setLeft(x.getRight());
50         //设置x的右子树的父结点为y
51         if(x.getRight() != NIL_T){
52             x.getRight().setParent(y);
53         }
54         //将y的父结点设置为x的父结点
55         x.setParent(y.getParent());
56         //如果y是根结点,则更换根结点
57         if(y.getParent() == NIL_T){
58             rootNode = x;
59         }else if(y == y.getParent().getLeft()){
60             //如果y是其父结点的左孩子,则将x设为其父结点的左孩子
61             y.getParent().setLeft(x);
62         }else{
63             //如果y是其父结点的右孩子,则将x设为其父结点的右孩子
64             y.getParent().setRight(x);
65         }
66         //x的右孩子为y
67         x.setRight(y);
68         //y的父结点为x
69         y.setParent(x);
70     }

  2. 插入

  既然红黑树是一棵二叉查找树,那么我们就可以像二叉查找树那样为红黑树插入一个元素。我们将二叉查找树的插入算法做一个略微的修改,我们将结点z插入到树中,就像树T是一棵普通的二叉查找树一样,然后将z着为红色,为保持红黑树的性质,我们需要对树中的结点进行重新着色并旋转。如果对二叉查找树的插入操作不熟悉,请阅读我之前写过的博客:http://www.cnblogs.com/unpolishedgem/archive/2012/05/10/2494403.html。

  我们来分析一下,在插入过程中可能违反的性质有哪几个。为此,我把红黑树的性质再抄写一次。

  一棵二叉查找树如果满足下面的红黑性质,则为一棵红黑树:

  1) 每个结点是或是红的,或是黑的。

  2) 根结点是黑的。

  3) 每个叶结点(nil[T])是黑的。

  4) 如果一个结点是红的,那么它的两个儿子是黑的。

  5) 对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。

  首先,我们插入的结点是红色的,因此不会违反性质1)和性质5),性质3)自然成立。唯一可能被破坏的是2)和4)。而且,2)和4)至多有一个性质被破坏。性质2)被破坏时的修复很简单,只需要将根结点重新着色为黑色即可。而性质4)被破坏的修复则要复杂一些,具体分为三种请况。

  情况1):z的叔叔y是红色的。

  如下图所示,如果z的叔叔y是红色的,将z的父结点和y着色为黑色,然后将z的祖父结点着色为红色,最后将z的祖父结点作为新的z结点进行迭代检查,因为z的祖父结点原来是红色的,被着色为黑色的时候,有可能会引起红黑树性质的破坏。

  情况2):z的叔叔y是黑色的,而且z是右孩子。

  情况3):z的叔叔y是黑色的,而且z是左孩子。

  如下图所示,如果是情况2),我们可以立即使用一个左旋变成情况3)。情况3)中,首先交换了B和C的颜色,然后通过一个右旋来使整个树达到了满足性质4)。

  从这三种情况来看,可以发现一个非常有趣的事情,那就是该过程所做的旋转从不超过两次,因为只有情况1)会继续将z上移进行红黑性质检查,而一旦进入了情况2)或者情况3),就不会再进行检查了。

  同样,伪代码就不写了,算法导论上都有,在此只写Java实现代码。

 1 /**2      * 插入操作3      * @author Alfred4      * @param k5      */6     public void treeInsert(int k){7         RBTreeNode z = new RBTreeNode(k, NodeColor.RED);8         RBTreeNode y = NIL_T;9         RBTreeNode x = rootNode;
10         //与二叉查找树的插入过程类似
11         while(x != NIL_T){
12             y = x;
13             if(z.getKey() < x.getKey()){
14                 x = x.getLeft();
15             }else{
16                 x = x.getRight();
17             }
18         }
19         z.setParent(y);
20         if(y == NIL_T){
21             rootNode = z;
22         }else if(z.getKey() < y.getKey()){
23             y.setLeft(z);
24         }else{
25             y.setRight(z);
26         }
27         z.setLeft(NIL_T);
28         z.setRight(NIL_T);
29         //进行修复
30         rbInsertFixUp(z);
31     }
32     /**
33      * 修复插入操作引起的不满足的红黑性质
34      * @author Alfred
35      * @param z 要修复的结点
36      */
37     private void rbInsertFixUp(RBTreeNode z){
38         RBTreeNode y = null;
39         while(z.getParent().getColor() == NodeColor.RED){
40             //如果z的父结点是z的祖父结点的左孩子
41             if(z.getParent() == z.getParent().getParent().getLeft()){
42                 y = z.getParent().getParent().getRight();
43                 //情况1),z的叔叔y的颜色是红色的。
44                 if(y.getColor() == NodeColor.RED){
45                     z.getParent().setColor(NodeColor.BLACK);
46                     y.setColor(NodeColor.BLACK);
47                     z.getParent().getParent().setColor(NodeColor.RED);
48                     z = z.getParent().getParent();
49                 }else if(z == z.getParent().getRight()){
50                     //情况2),z的叔叔y的颜色是黑色的,且z是其父结点的右孩子
51                     z = z.getParent();
52                     leftRotated(z);
53                     //情况2)经过左旋之后变为情况3),z的叔叔y的颜色是黑色的,且z是其父结点的左孩子
54                     z.getParent().setColor(NodeColor.BLACK);
55                     z.getParent().getParent().setColor(NodeColor.RED);
56                     rightRotated(z.getParent().getParent());
57                 }
58             }else{
59                 //与上面情况类似。
60                 y = z.getParent().getParent().getLeft();
61                 if(y.getColor() == NodeColor.RED){
62                     z.getParent().setColor(NodeColor.BLACK);
63                     y.setColor(NodeColor.BLACK);
64                     z.getParent().getParent().setColor(NodeColor.RED);
65                     z = z.getParent().getParent();
66                 }else if(z == z.getParent().getLeft()){
67                     z = z.getParent();
68                     rightRotated(z);
69                     z.getParent().setColor(NodeColor.BLACK);
70                     z.getParent().getParent().setColor(NodeColor.RED);
71                     leftRotated(z.getParent().getParent());
72                 }
73
74             }
75         }
76         //修复性质2)
77         rootNode.setColor(NodeColor.BLACK);
78     }

ps:写博客很累,转载的朋友请注明出处,谢谢。

白话红黑树系列之二——红黑树的构建相关推荐

  1. 红黑树详解(二)红黑树的插入(附动图和案例)

    红黑树详解(二)红黑树的插入(附动图和案例) 摘要: 在很多源码涉及到大量数据处理的时候,通常都是用红黑树这一数据结构.红黑树是一种自平衡的二叉查找树,它能在进行插入和删除操作时通过特定操作保持二叉查 ...

  2. 红黑树系列之一:红黑树的概述

    一.红黑树(RBT)的定义 1.红黑树的引入目的 BST查找效率较低: 查找最好时间复杂度O(lgn); 查找最坏时间复杂度O(n). AVL查找效率较高 查找最好.最坏时间复杂度都是O(lgn) 要 ...

  3. WCF系列(二) -- 使用配置文件构建和使用WCF服务

    当然,配置一个ServiceHost除了上面说的完全使用代码的方式,更好的方式是使用配置文件,把一些可能需要修改的属性跟代码分离,放到配置文件中,这样可以提供服务配置的灵活性,也更容易维护. 看看前面 ...

  4. 红黑树详解(一)红黑树的介绍和操作

    红黑树详解(一)红黑树的介绍和操作 摘要: 在很多源码涉及到大量数据处理的时候,通常都是用红黑树这一数据结构.红黑树是一种自平衡的二叉查找树,它能在进行插入和删除操作时通过特定操作保持二叉查找树的平衡 ...

  5. 白话经典算法系列之七 堆与堆排序

     堆排序与高速排序,归并排序一样都是时间复杂度为O(N*logN)的几种常见排序方法.学习堆排序前,先解说下什么是数据结构中的二叉堆. 二叉堆的定义 二叉堆是全然二叉树或者是近似全然二叉树. 二叉堆满 ...

  6. 三白话经典算法系列 Shell排序实现

    山是包插入的精髓排序排序,这种方法,也被称为窄增量排序.因为DL.Shell至1959提出命名. 该方法的基本思想是:先将整个待排元素序列切割成若干个子序列(由相隔某个"增量"的元 ...

  7. 白话经典算法系列之——希尔排序的实现

    希尔排序的实质就是分组插入排序,该方法又称缩小增量排序,因DL.Shell于1959年提出而得名. 该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个"增量"的 ...

  8. 红黑树系列1——红黑树的建立

    原创码字不易,转载请注明出处,谢谢~ 红黑树系列2--红黑树的删除(码字中,待发表) 红黑树系列3--红黑树的应用(码字中,待发表) 红黑树系列4--红黑树的代码实现(码代码中,待发表) 红黑树动态建 ...

  9. 红黑树模拟软件_红黑树

    红黑树 发布时间:2018-08-10 15:12, 浏览次数:415 一.在理解红黑树之前,先看一些二叉查找树 二叉查找树特性:左字数上所有的节点的值都小于或等于他的根节点上的值 右子树上所有节点的 ...

最新文章

  1. sql 纵向求和_sql列统计求和
  2. mac 配置c语言环境,C语言学习笔记————–MAC下配置GTK+环境
  3. LeetCode UTF-8 Validation
  4. 最短路径(Floyd算法)(c/c++)
  5. JAVA实现拼图游戏
  6. Reactive(3)5分钟理解 SpringBoot 响应式的核心-Reactor
  7. javascript Event监听
  8. [Python] Ubuntu 16.04 上安装 python3.7 和 pip 并配置虚拟环境
  9. C++ 著名程序库 概览
  10. 禁止跨域_新的跨域策略:使用COOP、COEP为浏览器创建更安全的环境
  11. 深入浅出H桥驱动电路
  12. com.mysql.jdbc.driver jar下载_com.mysql.jdbc.Driver
  13. ORAN C平面 Section Extension 10
  14. php如何去重,php如何去除重复数据
  15. C语言高级应用---操作linux下V4L2摄像头应用程序
  16. 英雄联盟出现game_error_directx的解决办法
  17. Android SearchView
  18. HTML5 代码实例
  19. 【520表白日】程序员如何表白吗?程序员表白教程送给你!
  20. 两种实现模糊匹配的方法--python

热门文章

  1. vue文件快速生成模板代码
  2. 【keras】Input 0 of layer conv2d is incompatible with the layer. expected ndim=4, found ndim=3
  3. CLion报错解决:allocating an object of abstract class type--unimplemented pure virtual method
  4. mysql建索引 字段截取_提高MySQL索引策略一:隔离查询列
  5. linux关闭防火墙stop,linux如何关闭防火墙
  6. 用c语言 编写桌面应用程序,谁能帮我用C语言编写“动态桌面啊”!!!急呀!!!...
  7. 计算机cmd入门,ODS(cmd)入门命令大全,果断收藏!!!
  8. form左上角有个锁的符号_第三章 表单笔记
  9. 禁用java rmi_java-如何安全关闭rmi客户端?
  10. linux常用操作命令详解