多路平衡树之红黑树

  • 红黑树主要是对2-3 树进行编码,红黑树背后的基本思想是用标准的二叉查找树(完全由 2- 结点构成) 和一些额外的信息(替换 3- 结点)来表示 2-3 树,树中的链接分为两种类型:

    • 红链接 :将两个 2- 结点连接起来构成一个 3- 结点
    • 黑链接 :则是2-3树中的普通链接
  • 确切的说,我们将 3- 结点表示为由一条左斜的红色链接(两个2-结点其中之一是另一个的左子结点)相连的两个2-结点。这中表示法的优点是:我们无需修改就可以直接使用标准的二叉查找树的get方法

红黑树定义

  • 红黑树是含有红黑链接并满足下列条件的 二叉查找树 :
    1. 红链接均为左链接
    2. 没有任何一个结点同时和两条红链接相连
    3. 该树是完美黑色平衡的,即任意空链接到根结点的路径上的黑色链接数量相同
  • 红黑树与2-3树对应关系
    红黑树与2-3树对应关系

API设计

类名 Node<Key,Value>
构造方法 Node(Key key,Value value,Node left,Node right, boolean color):创建 Node 对象
1. public Node left:记录左子结点
2. public Node right:记录右子结点
成员变量 3. public Key key:存储键
4. public Value value:存储值
5. public boolean color:由其父结点指向它的链接的颜色,红色为true

红黑树API_color

  • 注意 : 红线关系,父结点指向子结点的线为红色,代表子结点的color 是RED,属性为true。即父结点与子结点间线的颜色实际上是子节点的颜色

平衡化

  • 在对红黑树进行一些增删改查的操作后,很有可能会出现红色的右链接或者两条连续红色的链接 ,而这些都不满足红黑树的定义,所以我们需要对这些情况通过旋转进行修复,让红黑树保持平衡
左旋
  • 当某个结点的左子结点为黑色,右子结点为红色,此时需要左旋

  • 前提:结点x(值为S),结点h(值为E)

  1. h.right = x.left;x.left = h。x以轴心和为轴逆时针旋转后,轴心h变为了x的左结点
  2. x.color = h.color
  3. h.color=true,让h的color属性变为RED,因为旋转后,h的父结点指向h的链为红色
    红黑树_左旋
右旋
  • 当某个结点的左子结点为红色,且左子节点的左子节点也是红色,需要右旋
    • 前提:结点x(值为E),结点h(值为s)
  1. 让x的右子结点成为h的左子结点:h.left=x.right
  2. 让h成为x的右子结点:x.right=h
  3. 让x的color变为h的color属性值:x.color=h.color(注:h.color==false 黑色)
  4. 让h的color为RED

红黑树_右旋

  • 右旋转结束后发现,右旋后依然不满足红黑树定义。虽然 x(值为E) 与 h(值为S)节点不是左链为连续的两条红色的线,但是 x 节点(值为E)的右链又为红色了。后续会通过颜色反转解决当前问题,现在右旋已经结束

红黑树的插入

向单个2-结点中插入新键
  • 一棵只含有一个键的红黑树只含有一个2-结点。插入另一个键后,我们马上就需要将他们旋转
    • 如果新键小于当前结点的键,我们只需要增加一个红色结点即可,新的红黑树和单个3-结点完全等价
      红黑树_Insert01

    • 如果新键大于当前结点的键 ,那么新增的红色结点将会产生一条红色的右链接,此时我们需要通过左旋 ,把红色右链接变成左链接 ,插入操作才算完成。形成的新的红黑树依然和3-结点等价其中含有两个键,一条红色的链接
      红黑树_Insert02

向底部的2-结点插入新键
  • 用和二叉查找树相同的方式向一棵红黑树中插入一个新键,会在树的底部新增一个结点(可以保证有序性),唯一的区别地方是我们会用红链接将新结点和它的父结点想连。如果它的父结点是一个2- 结点,那么刚才讨论两种方式仍然适用
    红黑树_Insert03
    红黑树_Insert031
颜色反转
  • 当一个结点的左子结点和右子结点的color都为RED时,也是是出现了临时的4-结点 此时只需要把左子结点和右子结点的颜色变为BLACK,同时让当前的结点的颜色变为RED即可
    红黑树_color_fix
向一棵双键树(即一个3-结点)中插入新键
  • 可分为三种子情况
  1. 新键大于原树中的两个键
  • 新结点置于右子结点
  • 颜色反转(子结点color为black,父结点color为RED)
    红黑树_Insert0041
  1. 新键小于原树中的两个键
  • 新结点置于左子结点
  • 以原始父结点为轴右旋
  • 颜色反转(子结点color为black,父结点color为RED)
    红黑树_Insert0042
  1. 新键介于原数中两个键之间
  • 新结点置于左子结点的右子结点
  • 以左子结点为轴左旋
  • 以父结点为轴右旋
  • 颜色反转(子结点color为black,父结点color为RED)
    红黑树_Insert051
    红黑树_Insert052
向树底部的3-结点插入新键
  • 假设在树底部的一个3-结点 下加入一个新的结点。之前的3种情况都会出现 。颜色转换会使中间
    红黑树_Insert06

    • 指向新结点的链接可能是3-结点的右链接 ,此时只需转换颜色即可
    • 指向新结点的链接可能是3-结点的左链接 ,此时需要先进行右旋转(以父结点为轴),然后再颜色转换,然后左旋(根结点为轴)

红黑树_Insert061
红黑树_Insert0611

- 指向新结点的链接可能是`3-结的中链接` 此时`需要先左旋转,然后右旋转,最后转换颜色` 

红黑树的API设计

- -
类名 RedBlackTree<Key extends Comparable,Value>
构造方法 RedBlackTree():创建RedBlackTree对象
1.private boolean isRed(Node x):判断当前结点的父指向链接是否为红色
2.private Node rotateLeft(Node h):左旋调整
3.private Node rotateRight(Node h):右旋调整
4.private void flipColors(Node h):颜色反转,相当于完成拆分4-结点
成员方法 5.public void put(Key key,Value val):在树上完成插入操作
6.private Node put(Node h,Key key,Value val):在指定树中,完成插入操作,并返回添加元素后新的树
7.public Value get(Key key):根据key,从树中找出对应的值
8.private Value get(Node x,Key key):从指定的树x中,找出key对应的值
9.public int size():获取树中元素的个数
1.private Node root:记录根结点
成员变量 2.private int N:记录树中元素的个数
3.private static final boolean RED:红色标识链接
4.private static final boolean BLACK:黑色标识链接

红黑树实现

向树插入结点
  1. 判断当前树是否为空
  2. 比较新结点和树上的结点的键和新节点的键的大小
    • 新节点key小于树上的结点的key,向左放置(递归)
    • 新节点key大于树上的结点的key,向右放置(递归)
    • 新结点key等于树上的结点的key,值替换
  3. 左旋
  4. 右旋
  5. 颜色反转

规律

  1. 红黑树中所有的结点都是2-结点 ,每插入一个新结点,我们都期望把它和它的父结点组成一个3-结点 ,因此,新插入的结点都是红色结点 ,当不满足红黑树定义时才进行旋转或颜色反转

红黑树API实现

public class RedBlackTree<Key extends Comparable<Key>,Value> {
    //根节点
    private Node root;
    //元素个数
    private int N;
    //红色链接
    private static final boolean RED=true;
    //黑色链接
    private static final boolean BLACK=false;

    //结点类
    private class Node{
        public Key key;
        private Value value;
        public Node left;
        public Node right;
        public boolean color;

        public Node(Key key, Value value, Node left, Node right, boolean color) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
            this.color = color;
        }
    }
    //获取树中元素的个数
    public int size(){
        return N;
    }
    //当前结点的color是否为RED
    public boolean isRed(Node x){
        if (x == null) {
            return false;
        }
        return x.color==RED;
    }
    //左旋转
    public Node rotateLeft(Node h){
        //获取h结点的右子结点,标记为x
        Node x = h.right;
        //将 x结点的左子结点 赋值给 h节点的右子节点
        h.right = x.left;
        //让 h结点成为 x结点的 右子节点
        x.left = h;
        //将h结点的颜色(黑色) 赋值给 x结点
        x.color = h.color;
        //将h结点的颜色转换为 红色
        h.color = RED;
        //左旋后,父链接指向x结点,所以返回x
        return x;
    }

    //右旋转
    public Node rotateRight(Node h) {
        //获取h结点的左子结点,标记为x
        Node x = h.left;
        //将 x结点的右子节点 赋值给 h结点的左子节点
        h.left = x.right;
        //将 h结点 赋值给 x结点的右子节点
        x.right = h;
        //将 h结点的颜色 赋值 给 x结点
        x.color = h.color;
        //将 h结点的颜色 设置为 红色
        h.color = RED;
        //右旋后,父链接志向x结点,所以返回x
        return x;
    }
    //颜色反转,完成拆分 4- 结点
    public void flipColors(Node h) {
        h.color = RED;
        h.left.color = BLACK;
        h.right.color = BLACK;

    }
    //在整个树上完成插入操作
    public void put(Key key,Value val){
        root = put(root, key, val);
        root.color = RED;
    }
    //指定结点插入 结点
    public Node put(Node h,Key key,Value val){
        //判断树是否为空,如果为空则直接返回一个红色的结点就可以了
        if (h == null) {
            N++;
            return new Node(key, val, null, null, RED);
        }
        //比较新结点key 和 插入位置的结点的key 大小
        int cmp = key.compareTo(h.key);
        if (cmp < 0) {
            //新结点往左放
            h.left=put(h.left,key,val);
        }
        if (cmp > 0) {
            //新结点往右放
            h.right=put(h.right,key,val);
        }
        if (cmp == 0) {
            h.value=val;
        }

        //左旋,当 当前左子结点为黑色,当前右子结点为红色,需要反转
        if (isRed(h.right) && !isRed(h.left)) {
            //旋转后,返回的结果应该 指向 当前树
            h=rotateLeft(h);
        }
        //右旋:当当前结点的左子结点 和 当前结点的左子结点的左子结点 都为红色
        if (isRed(h.left) && isRed(h.left.left)) {
            //旋转后,返回的结果应该 指向 当前树
            h=rotateRight(h);
        }

        //颜色转换:当 当前结点的左子结点 和 右子结点都为红色,需要进行颜色反转
        if (isRed(h.left) && isRed(h.right)) {
            flipColors(h);
        }

        //当前 子树h 添加了 新结点 成功后,返回 h 子树
        return h;
    }

    public Value get(Key key) {
        return get(root, key);
    }

    public Value get(Node x, Key key) {
        if (x == null) return null;
        int cmp = key.compareTo(x.key);
        if (cmp < 0) {
            return get(x.left, key);
        } else if (cmp > 0) {
            return get(x.right, key);
        } else {
            return x.value;
        }
    }
}


posted @ 2020-04-19 20:25  雪梨加冰  阅读(154)  评论(0编辑  收藏  举报