数据结构:树状结构之2-3树和红黑树

走得最慢的人,只要他不丧失目标,也比漫无目的地徘徊的人走得快。

什么是2-3树?

如下图所示:
在这里插入图片描述
这就是一个2-3树。一颗2-3树应该是这样的:

  • 满足二叉树的基本性质
  • 节点可以存放一个或两个元素

从上面图片可以看出,2-3已经不是一颗二叉树了,在树中有两种不同的节点,放一个元素的节点叫二节点,也就是说这个节点有两个孩子,而放两个元素的节点叫做三节点,他有三个孩子。

在这里插入图片描述
正是因为每个节点有2个或3个孩子,所以才叫2-3树。

所谓的2-3树满足二叉树的基本性质是指:在2-3树中,2节点的左孩子小于根节点,右孩子大于根节点;3节点的左孩子小于根节点的第一个元素,中间孩子的大小在两个根节点元素之间,右孩子元素大于根节点的第二个元素。

以刚才的图为例:

  • 2节点 :左孩子 < a < 右孩子
  • 3节点 :左孩子 < b < 中间孩子 < c < 右孩子

2-3树的绝对平衡

2-3树是一颗绝对平衡的树,所谓的绝对平衡是指在2-3树中从根节点到任意叶子结点所经过的节点数是一样的。

为了保持2-3树的绝对平衡,在元素插入时有以下特征:

  • 如果插入位置节点的父节点的左右孩子都为空,则就和父节点进行融合
  • 如果和一个三节点的节点融合,则可以先融合成四节点,四节点在拆分为以中间大小元素为根,较小元素为中间大小元素的左孩子,较大元素为中间大小元素的右孩子二叉树。若这颗二叉树的根不为2-3树的根,这需要将这个二叉树的根向上融合

这两个特征描述起来确实不好理解。

举个栗子:

下面演示2-3树的建立过程,将包含所有情况:

在这里插入图片描述
42 是第一个插入的元素,作为2-3树的根节点。

在这里插入图片描述
现在要将37插入到这颗2-3书中,根据第一个特征:如果插入位置节点的父节点的左右孩子都为空,则就和父节点进行融合,根据大小值,37应该插入到42的左孩子位置,但是42的左右孩子此时均为空,所以应该进行融合,此时 37 和 42 组成了一个3节点。如下图:
在这里插入图片描述
此时又需要将12 插入2-3树:
在这里插入图片描述
同理,12应该插入到37左孩子位置,但37节点左右孩子均为空,因此需要进行融合,此时就是和一个3节点进行融合,可以先融合成一个4节点
在这里插入图片描述
由于2-3树中只能是2节点或3节点,所以还需要将4节点拆分出来,拆分成以中间大小元素为根,较小元素为中间大小元素的左孩子,较大元素为中间大小元素的右孩子二叉树。即:
在这里插入图片描述
再插入一个18:
在这里插入图片描述
同样根据特征一,应该和12融合:
在这里插入图片描述
接着插入6:
在这里插入图片描述
那么6应该和12 18 所在的三节点融合,构成一个临时的四节点:
在这里插入图片描述
此时需要将4节点拆分:
在这里插入图片描述
需要注意的是到这一步依然没有结束,还需要将拆分后的二叉树的根节点,也就是12继续向上融合,和37融合:
在这里插入图片描述
接着插入11:
在这里插入图片描述
在这里插入图片描述
插入5:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
OK,2-3树正是用这种方式来保证自己的绝对平衡,大家可以检查一下,的确每次插入完毕之后,2-3树依然是绝对平衡的,也就是说:从根节点到任意叶子结点所经过的节点数是一样的。

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

红黑树是每个节点都带有颜色属性的二分搜索树,在满足二分搜索树的基本性质的同时,还具有以下性质:

  • 每个节点或者是红色的,或者是黑色的
  • 根节点是黑色的
  • 每一个叶子节点(最后的空节点)是黑色的
  • 如果一个节点是红色的,那么他的孩子节点都是黑色的
  • 从任意节点出发,到达叶子结点所经过的黑色节点个数是一样的
    在这里插入图片描述
    需要注意的是,在红黑树中,所谓的叶子结点指的是最后的空节点。

根据上面所讲的2-3树,其实红黑树与2-3树是等价的,他们具有这样的等价关系:
在这里插入图片描述
这张图片展示了2-3树中的2节点和3节点在红黑树中所对应的节点,其中:
红黑树中的红色节点表示和双亲结点是融合的关系,在图片中b节点是红色的,就表示和b的双亲结点c相融合。

有了这样的对应关系,我们可以轻易的将一颗2-3树转化为红黑树,或将一颗红黑树转化为2-3树。

举个栗子:

将下面的2-3树转化为红黑树:
在这里插入图片描述
转化后的结果应该是这样的:
在这里插入图片描述
怎么样?在了解了2-3树以后,其实红黑树也不难!

细心的读者可能已经发现了一个细节,也就是红黑树严格意义上讲,并不是一颗平衡的二叉树!请看以33为根节点的左右子树,其中左子树的高度为3,右子树的高度为1,高度之差为2,已经大于1了。

但红黑树是保持黑平衡的二叉树,这是由红黑树的最后一个性质得来的: 从任意节点出发,到达叶子结点所经过的黑色节点个数是一样的 ,由于2-3树和红黑树的等价关系,其实红黑树的某些性质就是由2-3树的性质得来的,比如刚才这条红黑树的性质正是由2-3树的绝对平衡得来的。

红黑树(左倾红黑树)中插入新元素

在红黑树中插入新元素有下面两种情况:

  • 向2节点中插入元素,形成3节点
  • 向3节点中插入元素,暂时形成4节点

由于插入一个元素一定是要与2节点或3节点进行融合的,所以在红黑树中插入元素的颜色永远是红色的。

其次,红黑树中的根节点一定是黑色的。

下面就分别对插入的几种情况进行分析:

向2节点中插入元素

  1. 插入元素小于根节点元素

如向以42为根的红黑树中插入37
在这里插入图片描述
这种情况最为简单,之间插入在42的左孩子即可:
在这里插入图片描述
2. 插入元素大于根节点元素

如向以37为根的红黑树中插入42
在这里插入图片描述
同样我们直接插入到37的右孩子位置:
在这里插入图片描述
由于我们实现的是左倾红黑树,因此我们还需将这颗树以37为根进行一次左旋转:
在这里插入图片描述
用T1,T2,T3分别表示37的左子树,42的左子树和右子树,左旋转:
在这里插入图片描述
左旋转后还应维护结点的颜色,左旋转前,37为根,旋转后42为根,因此旋转后42的颜色应该为旋转前根节点37的颜色(之所以42不直接赋为黑色是应为37不一定是整个红黑树的根,37也可能是红色,表示37与37的双亲是融合的),而左旋后37的元素一定是红色的表示与42融合。
在这里插入图片描述

向3节点插入新元素

  1. 插入的元素大于3结点中的其他元素

如:将66插入到下面的3节点中
在这里插入图片描述
显然66应该作为42的右孩子,在2-3树中对应了临时的四节点:
在这里插入图片描述
在2-3树中,临时的四节点要被拆分成一个二叉树的形状,并且根节点要向上融合,因此 37 和 66 应该对应为黑色节点,而42为红色节点,向上融合。
在这里插入图片描述
此时这个红黑树的颜色发生了翻转,因此这个操作也叫颜色翻转。

  1. 插入的元素小于3结点中的其他元素

如:将12 插入这个3节点中
在这里插入图片描述
显然12应该作为37的左孩子:
在这里插入图片描述
此时也对应2-3树中的4节点,同样需要将4节点拆分:
在这里插入图片描述
此时4节点的拆分,对应在红黑树中的操作为右旋转,同样我们用T1,T2分别表示x的右子树和node的右子树。
在这里插入图片描述
有旋转后是这样的:
在这里插入图片描述
和第一种情况一样,x的颜色是原来根节点node的颜色,node的颜色是红色表示和x融合:
在这里插入图片描述
最后在进行一次颜色翻转就好了:
在这里插入图片描述
3. 插入元素大小位于3节点元素之间

如:将40插入到下面的3节点中
在这里插入图片描述
显然40应该插入到37的右孩子上:
在这里插入图片描述
然后以 37 为根,做一次左旋转得到:
在这里插入图片描述
此时已经转化为第二种情况了。

因此向3节点中插入元素可以总结为以下过程:
在这里插入图片描述

Java实现红黑树

package cn.boom.tree;

public class RBTree<T extends Comparable<T>> {

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

    private Node root;
    private int size;


    private class Node {

        private T data;
        private Node left;
        private Node right;
        private boolean colour;

        public Node() {
            this.data = null;
            this.left = null;
            this.right = null;
            this.colour = RED; //默认为红色节点
        }

        public Node(T data) {
            this.data = data;
            this.left = null;
            this.right = null;
            this.colour = RED;
        }
    }

    public RBTree() {
        root = null;
        size = 0;
    }

    /**
     * 获取树中元素个数
     *
     * @return
     */
    public int getSize() {
        return size;
    }

    /**
     * 树是否为空
     *
     * @return
     */
    public boolean isEmpty() {
        return size == 0;
    }

    // 判断节点node的颜色
    private boolean getColour(Node node){
        if(node == null)//空节点为黑色
            return BLACK;
        return node.colour;
    }

    //   node                     x
    //  /   \     左旋转         /  \
    // T1   x   --------->   node   T3
    //     / \              /   \
    //    T2 T3            T1   T2
    private Node leftRotate(Node node) {

        Node x = node.right;
        Node T2 = x.left;

        x.left = node;
        node.right = T2;

        //维护颜色
        x.colour = node.colour;
        node.colour = RED;

        return x;
    }


    //     node                   x
    //    /   \     右旋转       /  \
    //   x    T2   ------->   y   node
    //  / \                       /  \
    // y  T1                     T1  T2
    private Node rightRotate(Node node) {

        Node x = node.left;
        Node T1 = x.right;

        x.right = node;
        node.left = T1;

        //维护颜色
        x.colour = node.colour;
        node.colour = RED;

        return x;
    }

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


    /**
     * 向红黑树中插入元素
     *
     * @param elem
     */
    public void add(T elem) {

        root = add(root, elem);
        size++;
        //维护根节点的颜色
        root.colour = BLACK;
    }

    private Node add(Node parent, T e) {

        if (parent == null) {
            return new Node(e);
        }

        if (e.compareTo(parent.data) > 0) { //忽略重复元素
            parent.right = add(parent.right, e);
        } else if (e.compareTo(parent.data) < 0) {
            parent.left = add(parent.left, e);
        }

        if (getColour(parent.right) == RED && getColour(parent.left) != RED) {
            parent = leftRotate(parent);
        }

        if (getColour(parent.left) == RED && getColour(parent.left.left) == RED) {
            parent = rightRotate(parent);
        }

        if (getColour(parent.left) == RED && getColour(parent.right) == RED) {
            flipColors(parent);
        }

        return parent;
    }

    /**
     * 元素e是否存在
     *
     * @param e
     * @return
     */
    public boolean contains(T e) {
        return contains(root, e);
    }

    private boolean contains(Node parent, T e) {

        if (parent != null) {

            if (e.compareTo(parent.data) == 0) {
                return true;
            } else if (e.compareTo(parent.data) > 0) {
                return contains(parent.right, e);
            } else if (e.compareTo(parent.data) < 0) {
                return contains(parent.left, e);
            }
        }
        return false;
    }


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值