数据结构之从2-3 树理解红黑树

2-3 树

在介绍红黑树之前有必要先介绍一下2-3树,因为直接理解红黑树是有一定难度的,而红黑树其实是2-3 树的等价二叉树形式,清楚了2-3 树的逻辑,红黑树自然就迎刃而解了。
2-3 树是最简单的B树,是搜索树的一种,其结构中有两种节点,有两个孩子的被称为2-节点,三个孩子的被称为3-节点:

它满足BST的基本性质,所谓满足性质是:对于一个2-节点来说其左子树的值比该节点小,右子树值比该节点大。对于一个3-节点来说其中的两个值从小到大排列,中间孩子的值是介于该节点两个值之间的。
2-3树是一棵绝对平衡的树结构,在插入过程中会始终保持绝对平衡,绝对平衡是指:在该树中,对于任意节点,其子树的高度相等;或者从根出发到该树任意一个叶子节点经过的节点数相等
下面通过2-3树的建树过程来看一下为什么2-3树是一棵绝对平衡树,假设添加的顺序是:

42,37,12,18,6,11,5

  • 2-3树的添加过程除根节点外不会往空位置添加元素,它会依次添加出2-节点,3-节点;当新来一个元素要往3-节点继续添加时,则先形成4-节点,当然此时已不符合2-3树的定义了,再将4-节点分裂即可:
  • 若叶子节点形成4-节点,则分裂后的根节点要与其父亲节点融合:
  • 若分裂后与父亲节点融合导致父亲节点成为4-节点,则父亲节点再进行一次分裂:

自此,这个建树过程已完毕,其中要处理的一共就这三种情况,可以看到,这种建树方式可以保证所得的树是一棵绝对平衡树。而且可以发现 ,我们的添加过程是从大到小的,若是往BST中添加这些元素,树早已偏斜。可见2-3树的平衡性。

红黑树与2-3树的等价

好了,现在可以开始介绍红黑树了,红黑树是一种二叉树。红黑树与2-3树之所以等价是因为红黑树相当于在2-3树的节点上做了对应,2-节点对应到红黑树变化不大,3-节点对应到红黑树则要做相应变化:

其中红色节点表示在红黑树对应的原2-3树中,该节点与其父亲节点是融合在一块的。
将红色节点放在左边的红黑树叫做左倾红黑树,相应地有有右倾红黑树,原理是一致的。本篇所有讨论均基于左倾红黑树。
在算法导论中,作者Thomas H. Cormen直接给出了红黑树的性质(定义):

翻译:
1.每个节点都是红或黑
2.根节点是黑色
3.每个叶子(这里的叶子是指最后的空节点)是黑色
4.若一个节点是红色,则它的孩子节点都是黑色
5.从任一节点到叶子节点,经过的黑色节点个数相等

如果直接看这个定义一定会懵逼的,但是现在再来看这个定义就清晰多了,其中1、2显然,3、4、5都可以由2-3树往红黑树的转换中推出。值得注意的是,红黑树并不是AVL那样的平衡二叉树,可以计算得到红黑树的最大高度可能是2logn级别(当经过的每个黑节点之前都有红节点时),但从任一节点到叶子节点,经过的黑色节点个数相等,故也称红黑树为“黑平衡”。

红黑树的添加(插入)操作

接下来我们讨论红黑树的添加元素操作,由红黑树的性质不难知道,插入的每个元素初始必然都是红色的,不过根比较特殊,根是黑色的这是定义。对于本篇实现的红黑树的添加操作,不失一般性,共有5种情况:

  • 1.前两种对应于2-3树向2节点插入元素。插入节点在黑色节点左侧(就是比父亲节点小),则直接插入。
  • 2.插入节点在黑色节点右侧,此时执行左旋转操作:
  • 3.接下来三种是对应于2-3树向3-节点添加元素。case1:添加x2在黑色节点右边,形成3-节点,则进行颜色翻转:
  • 4.case2:添加在x的左侧形成3-节点,先执行右旋转,再进行颜色翻转即可:
  • 5.case3:添加在左侧的右侧形成3-节点,先执行左旋转变为case4,再按case4处理:

通过上面的分析,不难实现出一个红黑树,下面给出红黑树类的具体实现:

'''RBTree.java'''
import java.util.ArrayList;

public class RBTree<K extends Comparable<K>, V> {
    private static final boolean RED = true;
    private static final boolean BLACK = false;//私有且不可更改
    private class Node{
        public K key;
        public V value;
        public Node left, right;
        public boolean color;//true代表红色

        public Node(K key, V value){
            this.key = key;
            this.value = value;
            left = null;
            right = null;
            color = RED;//初始化为红色,因为新节点总要和某个节点融合
        }
    }

    private Node root;
    private int size;

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

    public int getSize(){
        return size;
    }

    public boolean isEmpty(){
        return size == 0;
    }
    //辅助函数判断节点颜色
    private boolean isRed(Node node){
        if(node == null)
            return BLACK;
        return node.color;
    }
    private void flipColors(Node node){
        //颜色翻转
        node.color = RED;
        node.left.color = BLACK;
        node.right.color = BLACK;
    }
    // 向RB树中添加新的元素(key, value)
    public void add(K key, V value){
        root = add(root, key, value);
        root.color = BLACK;//保持根节点为黑色
    }
    //红黑树左旋转,返回旋转后的根
    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;
    }
    // 向以node为根的RB树中插入元素(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;
    }
    // 返回以node为根节点索树中,key所在的节点
    private Node getNode(Node node, K key){

        if(node == null)
            return null;

        if(key.equals(node.key))
            return node;
        else if(key.compareTo(node.key) < 0)
            return getNode(node.left, key);
        else // if(key.compareTo(node.key) > 0)
            return getNode(node.right, key);
    }

    public boolean contains(K key){
        return getNode(root, key) != null;
    }

    public V get(K key){

        Node node = getNode(root, key);
        return node == null ? null : node.value;
    }

    public void set(K key, V newValue){
        Node node = getNode(root, key);
        if(node == null)
            throw new IllegalArgumentException(key + " doesn't exist!");

        node.value = newValue;
    }

    // 返回以node为根的树的最小值所在的节点
    private Node minimum(Node node){
        if(node.left == null)
            return node;
        return minimum(node.left);
    }

    // 删除掉以node为根的树中的最小节点
    // 返回删除节点后新的树的根
    private Node removeMin(Node node){

        if(node.left == null){
            Node rightNode = node.right;
            node.right = null;
            size --;
            return rightNode;
        }

        node.left = removeMin(node.left);
        return node;
    }

可以看到,上述代码是在BST代码的基础上修改的,相比于BST的代码,在节点中加入了表示颜色的布尔变量;设置了辅助函数左旋转、右旋转以及颜色翻转;添加操作中对红黑树的性质进行维护。
(由于删除操作有点复杂,暂时没有给出,过两天补上。。。)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章