• 多路平衡树之红黑树


    • 红黑树主要是对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;
            }
        }
    }
    
    
    
  • 相关阅读:
    mysql在CentOS6.3上安装
    hdfs高可用性(HDFS High Availability)
    如何做个好员工
    lock(3)——更新锁(U)、排它锁(X)、死锁及如何避免死锁
    锁(1)—— 锁粒度和层次结构
    lock(2)——创建及更新表过程中SQL SERVER锁资源分配情况
    HBase体系结构
    HDFS的shell操作
    Windows 使用 net use 命令
    Windows 使用 net 命令简介
  • 原文地址:https://www.cnblogs.com/luckyCoder/p/12733288.html
Copyright © 2020-2023  润新知