数据结构--红黑树

红黑树

定义

  红黑树也是一种平衡搜索树,他能够保证在最坏的状况下基本动态集合操做的时间复杂度为O(lgn)。java

  红黑树是一棵二分搜索树,它在每一个位置上增长了一个存储位来表示节点的颜色,能够是RED或BLACK。经过任何一条从根到叶子的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其余路径常出两倍,于是近似于平衡的。node

红黑树与 2-3查找树

  红黑树是2-3查找树的一种表示方式。区别在于,2-3查找树中包含有2-结点3-结点。在红黑树中只有全部的结点都是2-结点,这样一来就会有这样的疑问,既然都是2-结点,那怎么表示2-3树呢? 答案就是,在红黑树中,对2-3树中的3-结点作了必定的处理,经过使用2-结点和一些其余的信息来表示3-结点git

红黑树中对2-3树中的3-结点的处理github

  将2-3树中的3-结点拆分红2个2-结点,并将这两个2-结点的左节点使用红色的连接,右链接使用黑色(原来2-3树中的连接)连接,连在一块儿。以下图:算法

这样一来,就获得了构造红黑树的基本思想:bash

  用标准的二分搜索树(彻底由2-结点构成)和一些额外的信息(替换3-节点)来表示2-3树微信

从上面的描述咱们能够获得红黑树的定义:学习

  红黑树是含有红黑连接并知足下列条件的二分搜索树:ui

   1. 红连接均为左连接;this

   2. 没有任何一个结点同时和两条红连接相连;

   3. 该树是完美平衡的,即任意空连接到根结点的路径上黑连接的数量相同。

红黑树中链接的分类:

  1. 红连接将两个2-结点链接起来构成一个3-结点;

  2. 黑连接则是2-3树中的普通连接。

以下图所示:

红黑树中颜色替换

  为了方便表示红黑树,间红黑树中连接的颜色表示在该连接所链接的结点中,咱们都知道,一条连接的两端,有两个结点(父子结点),将链接的颜色保存在子节点中。作颜色替换后,红黑树的示意图以下:

上面图片左侧的部分,在连接的旁边都有一个数字,这个数字称为:黑高

黑高(black-height)定义

   从某个结点出发(不含该结点)到达一个叶子结点的任意一条简单路径上的黑色连接(结点)的个数。

   经过上述的黑高定义,及红黑树的平衡性能够知道红黑树的黑高就是根结点的黑高

红黑树的操做

红黑树的构成:

  • 节点   树中的结点包含5个属性:color、key、left、right、和p。

java代码:

public class Node{
      /** 键*/
      public K key;
      /** 相关联的值*/
      public V value;
      /** 左右子树*/
      public Node left,right;
      /** 父结点指向该结点的颜色*/
      public boolean color;
      public Node(K key, V value){
          this.key = key;
          this.value = value;
          left = null;
          right = null;
          color = RED;
      }
  }
复制代码
红黑树图示:

红黑树的性质

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

  • 2.根结点是黑色的;

  • 3.每一个叶子结点(空连接)是黑色的;

  • 4.若是一个结点是红色的,则它的两个叶子结点都是黑色的;

  • 5.对每一结点,从该结点到其全部后代叶结点的简单路径上,均包含有相同数目的黑色结点。

旋转

  在对红黑树进行操做(插入或删除)的时候,可能会出现右连接为红色连接,或者有两条连续的红色连接。这样一来就破坏红黑树的性质。所以须要 在本次操做完成以前经过旋转使得本次操做后,该树仍然是红黑树。旋转操做会改变红连接的指向。旋转也分为左旋转右旋转

  左旋转:将红色链接为右连接转化为左连接。图示以下:

总结:

  上述图片中,结点值为8的结点颜色能够是红色,夜能够是黑色。该结点一样也但是左子树,也能够是右子树。上述左旋转的过程:将上述图片的部分当作一棵子树,该子树的根结点的值是8。通过旋转后将值为15的结点做为该树的根结点。(将两个键中值较小的左为跟结点变成值交大者做为根结点的过程)。

  • 左旋转的代码实现:
private Node leftRotate(Node node){
    Node x = node.right;
    //左旋转
    node.right = x.left;
    x.left = node;

    x.color = node.color;
    node.color = RED;
    return x;
}
复制代码

  右旋转:将红色链接为左连接转化为右连接。图示以下:

总结:

  红黑树右旋转的过程就是左旋转的逆向过程。

  • 右旋转的代码实现:
private Node rightRotate(Node node){
    Node x = node.left;
    node.left = x.right;
    x.right = node;

    x.color = node.color;
    node.color = RED;
    return x;
}
复制代码

插入

  为了更好的理解红黑树,在上一篇文章中首先学习了2-3树,这一篇文章中关于红黑树的学习,也是经过2-3树通过相应的变化而来。这里要对红黑树 进行插入结点的操做,一样类比2-3树的插入。来看看,在红黑树的插入过程当中,是如何维护红黑树的性质的。

  在红黑树的插入操做中,假设插入的节点都为红色。

这里假设结点为红色,插入红黑树以后,会破坏上述的性质4。经过上述的旋转过程能够进行相应的调整来维护红黑树。这里若是假设插入的是黑色的结点,就会破坏红黑树的平衡性(性质5)。

向2-结点中插入新键

  向2-结点中插入新键分为以下的两种状况。

红黑树根结点的左侧插入

  在红黑树的根结点的左侧插入,默认插入是红结点,而红黑树根结点为黑节点,插入该结点后,红黑树的性质不变。

红黑树根结点的右侧插入

  在红黑树的根结点的右侧插入,因为默认插入的是红色结点,插入后不知足红黑树的性质,此时右节点为红色结点。经过左旋转,将其旋转为左结点为红色结点,修正根结点的链接。

总结:

  通过上述操做后,该红黑树等价为一棵只有一个3-结点的2-3树,该红黑树有两个结点,其中一个为红色结点,树的黑高为1。

向树底部的2-结点中插入新键

  用和二分搜索树相同的方式向一棵红黑树中插入一个新键会在树的底部新增一个结点(保证树的有序性),一样也是红结点和其父结点相连。若是父结点是一个2-结点,那么上述的两种处理方式仍然适用。若是指向新结点的是父结点的左连接,那么父结点就直接成为一个3-结点;若是指向新结点的是父结点的右连接,这就是一个错误的3-结点,经过一次左旋转来修正。

向一棵双键树(3-结点)中插入新键

  双键树中插入新的键分为以下的三种状况,下面分类讨论:

新插入的键大于原树中的两个键

  要插入的键在大于原树中的两个键,此时根结点连接两个红结点,不符合红黑树的性质,只需将两个红色节点变为黑色节点便可。

新插入的键介于原树中的两个键之间

  新插入的键介于原树两个键之间,这会产生两个连续的红色结点,一个结点是左结点,一个结点是右结点。须要将红色的右结点先左旋转变,使其变成两个连续的红色结点;而后在进行右旋转使其变成一个结点连接两个红色的结点(一左一右);最后在对两个红色结点的颜色进行变换。

新插入的键小于原树中的两个键

  新插入的键小于原树中的两个键,新插入的结点会被连接到最左边的空连接,这样也产生了两个连续的红色结点,将从根结点开始的第一个红结点右旋转,使根结点连接两个红色的结点;而后,将两个红色结点的颜色变为黑色。

颜色转换

  当一个结点连接两个红色的子结点时,须要将子结点的颜色右红变黑,同时也须要将父结点的颜色由黑变红。

代码实现

private void flipColors(Node node){
    node.color = RED;
    node.left.color = BLACK;
    node.right.color = BLACK;
}
复制代码
根结点老是黑色的

  在发生颜色转换的时候,会遇到根结点连接连个红色结点的状况。此时进行颜色转换后根结点为红色,当红色及结点出如今根结点的时候,红黑树的黑高就会增长1。上篇文章中有说到,2-3树的节点与红黑树结点的对应关系。而红色结点的来源就是3-结点拆成两个2结点,而后将左2-结点标记为红色。在每次添加新的结点后,都将红黑树的根结点设置为黑色。

向树底部的3-结点中插入新键

  假设在树的底部的一个**3-**结点下加入一个新的结点。上面讨论的三种状况都会出现。指向新结点的连接多是3-结点的右连接(此时须要转换颜色),或是左连接(须要右旋转而后再转换颜色),或是中连接(须要先左旋转下层连接而后右旋转上层连接,最后再转换颜色)。颜色转换会使到中结点的连接变红,至关于将它送入了父结点。这意味着在父结点中继续插入一个新键,使用相同的办法解决这个问题。

将红连接在树中向上传递

  2-3树中插入算法须要咱们分解3-结点,将中间键插入父结点,如此这般直遇到一个2-结点或根结点。以前考虑过的全部状况都是为了达成这个目标:每次必要的旋转以后咱们都会进行颜色转换 使得根结点变红。站在父结点的角度来看,处理这样的一个红色结点的方式和处理一个新插入的红色结点彻底相同,继续把红连接转移到中间结点上去。下图中总结的三种状况显示了在红黑树实现2-3树的插入算法的关键操做所需步骤:要在一个3-结点下插入新键,先建立一个临时的4-结点,将其分解并将红连接由中间键传递给它的父结点。重复这个过程,咱们就能将红连接在树中向上传递,直至遇到一个2-结点或根结点。

总结:

  只要谨慎的使用左旋转,右旋转和颜色转换这三个简单的操做,就能够保证插入操做后红黑树和2-3树的一一对应关系。在沿着插入点到根结点的路径向上移动时在所通过的每一个结点中顺序完成如下操做,便可完成插入操做

  • 若是右子结点是红色的而左子结点是黑色的,进行左旋转;

  • 若是左子结点是红色的且它的左子结点也是红色的,进行有旋转;

  • 若是左右子结点均为红色,进行颜色转换。

红黑树的插入代码实现

public void add(K key, V value){
     root = add(root, key, value);
     root.color = BLACK;
 }
复制代码
private Node add(Node node,K key, V value){
    if(node == null){
        size++;
        //默认插入红色节点
        return new Node(key,value);
    }
    if(key.compareTo(node.key) < 0){
        node.left = add(node.left,key,value);
    }else if(key.compareTo(node.key) > 0){
        node.right = add(node.right,key,value);
    }else{
        node.value = value;
    }
    if(isRed(node.right) && !isRed(node.left)){
        node = leftRotate(node);
    }
    if(isRed(node.left) && isRed(node.left.left)){
        node = rightRotate(node);
    }
    if(isRed(node.left) && isRed(node.right)){
        flipColors(node);
    }
    return node;
}
复制代码

  上述代码中的isRed()方法用来判断结点的颜色。

private boolean isRed(Node node){
    if(node == null){
        return BLACK;
    }
    return node.color;
}
复制代码

我的微信公众号:

我的github:

github.com/FunCheney

相关文章
相关标签/搜索