数据结构之红黑树

简介: 红黑树

红黑树


什么是红黑树

红黑树是一种常见的自平衡二分搜索树,常用于关联数组、字典,在各种语言的底层实现中被广泛应用。


满足红黑树的五个性质


  1. 每个节点是红色或者是黑色
  2. 根节点是黑色
  3. 每一个叶子节点(最后的空节点)是黑色
  4. 如果一个节点是红色的,那么它的孩子节点都是黑色的
  5. 从任意一个节点到叶子节点,经过的黑色节点是一样的

2-3树的定义

红黑树和2-3树有相似之处,我们先了解一下什么是2-3树


满足二分搜索树的基本性质,但是2-3树并不是二叉树


2-3树有两个节点,一个可以存放1个元素,一个可以存放2个元素


满足2-3树的三个性质


  1. 满足二叉搜索树的性质
  2. 节点可以存放一个或两个元素
  3. 每个节点有两个或三个子节点


1654604947099.png

每个节点都有2个子节点或者3个子节点,所以就叫做2-3树


左边节点都比42小,而右边节点都比42大,所以是满足二分搜索树的性质。注意,13和33这个节点中间是18,是比17大同时比33小。


1654605001187.png

2-3树的绝对平衡性

对于2-3树来说任意一个节点,左右子树的高度一定是相等的,而类似像AVL这种平衡二叉树左右子树的高度相差不超过1。


而2-3树是如何维持绝对平衡性,就要从它的添加操作说起。


对于一颗空树我们添加一个节点42,他既是根节点也是叶子节点,这个时候如果我们想再添加一个节点37应该添加到那里呢?


1654605037478.png

按平衡二叉树结构的逻辑来讲37比42小,所以添加到42的左孩子的位置。


但是在2-3树的中并不是这样的,


在2-3中我们依然是从根节点出发,请注意在2-3树中新的节点永远不会添加进空的位置


发现42节点的左子树为空,2-3树就会找到最后一个叶子节点上也就是42的位置,这时候37就会和42进行融合,从一个2节点变为了3节点。这个时候还是一颗绝对平衡的二叉树。

1654605067928.png


如果这个时候我们再添加一个12节点的话,因为12节点比37小,所以去找37的左孩子,但是我们已经知道,37的左孩子为空,而2-3并不会将新的节点添加到空的位置中去,这个时候我们就会变成4节点


1654605106056.png

但是在2-3中最多支持3个节点所以这个时候就会进行分裂,同时保持绝对平衡。


1654605147481.png

如果这个时候我们再添加一个18节点,我们可以很清楚的知道18会找到12节点但是12节点的子节点都为空,所以18就会和12节点进行融合。


1654605172008.png

如果这个时候我们再添加一个6节点,大家很清楚的知道是会和12节点、18节点进行融合,这个时候变成了4节点,所以要进行分裂成为下图这样。


1654605212307.png

但是!这样我们就不是绝对平衡的二叉树了!


这个时候就需要我们12节点去找他的父节点,发现12的父节点37是一个2节点,这个时候就简单了,我们只需要让12和37节点进行融合为3节点,然后让6变成12和37的左孩子,让18节点变成中间孩子节点即可!


1654605250325.png

这个时候我们依次加入5和11节点,就会变成下图这样。


1654605299373.png

因为不满足2-3树的性质,所以我们还是将他们进行分裂。


1654605328357.png

但是这个时候又不是绝对平衡了,所以我们需要让6去找父亲节点,但是父亲节点已经是3节点了,不过没关系,我们照样把他融合进去成下图这样。


1654605348431.png

这个时候我们需要进行分裂,我们需要把5和11分别放在6的左孩子和右孩子的位置,37也同理,形成下图这样。依然保持着绝对平衡性。


1654605366687.png

其实我们可以分为两种情况去理解,一种就是当前节点为3节点而父节点为2节点。


1654605394410.png

另一种就是当前节点为3节点,父节点也为3节点的情况。


1654605412673.png

红黑树和2-3树的等价性

在2-3树种,我们可以表示2个节点和3个节点。


1654605429934.png

在红黑树中,我们可以这样表示,然后用红色的线来表示bc是融合的,同时b是比c小的。由于红色节点是由3节点拆分而来,因此所有的红色节点都只会出现在左子树上。


1654605474324.png

在下图中只有6和17和66是红色节点,我们很容易得知,因为他们都是3节点。


1654605498190.png

如果抽象的话可以红色节点上移一下,我们可以看到其实是和2-3树是一样的。


1654605521965.png

保持根节点为黑色和左旋

在下图上,我们知道最后分裂以后,6继续会向上进行融合,我们知道向上融合的节点一定是红色的,所以我们知道6是红色的,但发现已经没有父亲节点的时候,我们将6变为黑色。


1654605545370.png

最开始的时候我们想添加一个42的话,我们直接让42当根节点就好了,然后我们需要让42变为黑色节点。


1654605570848.png

这个时候我们添加一个37,可以直接放入到42左孩子的位置。


1654605593587.png

但是,如果我们要插入的节点为根节点的右孩子的节点的时候,我们需要进行左旋。

1654605637485.png

1654605659778.png

伪代码如下:


node.right = x.left;
x.left = node;
x.color = node.color;
node.color = red;

Java代码如下:


 

//   node                     x
    //  /   \     左旋转         /  \
    // T1   x   --------->   node   T3
    //     / \              /   \
    //    T2 T3            T1   T2
    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;
    }

颜色翻转和右旋

最开始的时候我们想添加一个42的话,我们直接让42当根节点就好了,然后我们需要让42变为黑色节点。


1654605732145.png

这个时候我们添加一个37,可以直接放入到42左孩子的位置。

如果这时候我们再向红黑树中加入66节点,会变成如下图这样。


1654605753177.png

相应的在2-3树中是这个样子。


1654605772105.png

在红黑树中红色节点的意思就是和父节点是融合在一起的。

而37和66都是红色节点,说明它们都和父亲节点是融合在一起的。

在2-3树中,我们处理4节点的时候会进行分裂,分裂为3个2节点。


1654605795476.png

但是在红黑树中其实黑色节点就是2个节点,我们不需要进行旋转操作,我们只需要让37和66进行变色为黑色,就和2-3树中节点的性质是一样的了。


1654605817731.png

在2-3树中,我们需要继续让42节点向上去找父亲节点进行融合,相应的在红黑树中我们只需要让42变为红色即可。


1654605836900.png

这个就是颜色翻转(flipColors)

 

//颜色翻转
    private void flipColors(Node node){
        node.color = RED;
        node.left.color = BLACK;
        node.right.color = BLACK;
    }

另外一种情况就是,我们树添加成这个样子。我们添加了12节点,其实可以理解为它们是融合在一起的。在2-3树种就是4节点,然后将它进行分裂。


1654605863604.png

但是在红黑树中我们怎么变成这个样子呢?其实很简单,我们只需要进行右旋转即可。

为了方便理解,我们引入T1和T2虚拟节点来演示。


1654605888905.png

我们将x的右节点t1放到node的左孩子上,然后将node放在x的右孩子的位置即可。


1654605907965.png

别忘了,在这个时候node(42)节点其实是融合在x(37)节点上的,所以它要改为红色,37要改为黑色。


对应的伪代码:

node.left = t1;
x.right = node;
x.color = node.color;
node.color = red;

最终我们就可以得到和2-3树结构一样的红黑树了。

1654605945416.png

//       node                            x
    //       / \                           /   \
    //      x   T2     向右旋转 (y)        y     node
    //     / \       - - - - - - - ->          / \
    //    y   T1                              T1  T2
    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;
    }

在红黑树中添加新元素

其实还有一种情况我们在前面没有讲到,就是在下图中添加一个元素40应该怎么做。


1654605972072.png

根据二分搜索树的性质,我们会将40添加进37的右孩子的位置。


1654605992675.png

但是这个时候已经破坏了红黑树的性质,所以我们需要进行左旋。


1654606015412.png

这个时候我们还需要进行右旋。


1654606038367.png

然后我们需要将40变黑色,42变为红色。


1654606058067.png

最后我们进行颜色翻转即可维持红黑树的性质。


1654606076431.png

总结图片如下:


1654606103357(1).png

如果我们添加的第三个元素是最小的话,我们可以直接从右旋开始。


1654606119333.png

反之我们添加的如果是最大的元素,我们可以直接进行颜色翻转。


1654606133461.png

// 判断节点node的颜色
    private boolean isRed(Node node){
        if(node == null) {
            return BLACK;
        }
        return node.color;
    }
    //   node                     x
    //  /   \     左旋转         /  \
    // T1   x   --------->   node   T3
    //     / \              /   \
    //    T2 T3            T1   T2
    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 void flipColors(Node node){
        node.color = RED;
        node.left.color = BLACK;
        node.right.color = BLACK;
    }
    //       node                            x
    //       / \                           /   \
    //      x   T2     向右旋转 (y)        y     node
    //     / \       - - - - - - - ->          / \
    //    y   T1                              T1  T2
    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;
    }
    // 向红黑树中添加新的元素(key, value)
    public void add(K key, V value){
        root = add(root, key, value);
        //根节点为黑色
        root.color = BLACK;
    }
    // 向以node为根的红黑树中插入元素(key, value),递归算法
    // 返回插入新节点后红黑树的根
    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 // key.compareTo(node.key) == 0
        {
            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;
    }

时间复杂度分析

红黑树相比于AVL树,其实是牺牲了平衡性的,红黑树并不完全满足平衡二叉树的定义,红黑树的最大高度达到了2logn的高度,红色节点影响了红黑树的的平衡性。红黑树虽然牺牲了一定的查询性能,但是在增删改操作的性能得到了弥补,红黑树的综合性能还是要优于AVL树的。

相关文章
|
算法 C++
【数据结构与算法】—— 手撕红黑树
【数据结构与算法】—— 手撕红黑树
|
1月前
|
存储 Java 数据库
手撕红黑树 - 聊聊这个基本却又重要的数据结构
手撕红黑树 - 聊聊这个基本却又重要的数据结构
21 0
|
3月前
|
存储 关系型数据库 数据库
【数据结构】—红黑树(C++实现)
【数据结构】—红黑树(C++实现)
|
6月前
|
存储 Java 数据库
【红黑树数据结构及其应用】
【红黑树数据结构及其应用】
|
6月前
|
存储
数据结构之二叉查找树(Binary Search Tree)和红黑树(Red Black Tree)
二叉查找树又可以称之为 : 二叉搜索树 , 二叉排序树 , 它或者是一棵空树,或者是具有下列性质的二叉树:若它的左子树不空,则左子树上所有结点的值均小于它的根节点的值;若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;它的左、右子树也分别为二叉排序树。二叉搜索树作为一种经典的数据结构,它既有链表的快速插入与删除操作的特点,又有数组快速查找的优势 , 下图中这棵树,就是一棵典型的二叉查找树
110 1
|
6月前
|
关系型数据库
|
1月前
从0开始回顾数据结构---红黑树
红黑树 1、什么是红黑树 红黑树是一种不严格的平衡二叉树,插入、删除、查找的最坏时间复杂度都为 O(logn),避免了二叉树最坏情况下的O(n)时间复杂度。 红黑树特性如下: 1. 根节点是黑色 2. 每个节点要么是黑色要么是红色 3. 每个红色节点的两个子节点一定都是黑色 4. 每个叶子节点(NIL)都是黑色 5. 任意一个节点的路径到叶子节点所包含的黑色节点的数量是相同的---这个也称之为黑色完美平衡 6. 新插入的节点必须是红色->为什么?如果新插入的节点是黑色,那不管是在插入到那里,一定会破坏黑色完美平衡的,因为任意一个节点的路径到叶子节点的黑色节点的数量肯定不一样了。 2、为什么需
|
6月前
|
C++
【数据结构】红黑树封装map和set(上)
【数据结构】红黑树封装map和set(上)
|
3月前
数据结构——红黑树
数据结构——红黑树
27 0
|
8月前
|
存储 算法 Java
深入解析 Java 数据结构:红黑树的特点与应用
红黑树(Red-Black Tree)是一种自平衡的二叉搜索树,它在 Java 编程中扮演着重要的角色,用于高效地存储和管理数据。红黑树的特点使其在查找、插入和删除操作中保持相对平衡,从而提供了稳定且高效的性能。本文将深入探讨红黑树的特点、用法、实现方式以及在实际应用中的优势。