手写红黑树,实现增、删、查功能

红黑树

1、概念

红黑树(RBT)的定义:它或者是一颗空树,或者是具有一下性质的二叉查找树。

  1. 节点非红即黑
  2. 根节点是黑色
  3. 所有NULL节点称为叶子节点,且认为颜色为黑色
  4. 所有红节点的子节点都为黑色
  5. 从任一节点到其叶子节点的所有路径上都包含相同数目的黑节点

对平衡树的改进:任意一个节点,它的左右子树的层次最多不超过一倍。

1.png

2、插入节点

1. 插入节点:先按照二叉排序树的方式插入一个节点(红色)
2. 插入节点是根节点: 解决:直接将节点涂黑
3. 插入节点的父节点是黑色: 不违背任何性质,不用调整
4. 插入节点的父节点是红色,那么分以下几种情况:

第一种情况:父节点是祖父节点的左孩子

情况1:祖父节点的另一个子节点(叔叔节点)是红色
对策: 将当前节点的父节点和叔叔节点涂黑,祖父节点涂红,把当前节点指向祖父节点,从新的当前节点开始算法
2.png
情况2:叔叔节点是黑色,右分两种情况:
情况2.1:当前节点是其父节点的右孩子
对策:当前节点的父节点做为新的当前节点,以新当前节点为支点左旋。
3.png
情况2.2:当前节点是其父节点的左孩子
对策:父节点变为黑色,祖父节点变红色,再祖父节点为支点进行右旋
4.png

第二种情况:父节点是祖父节点的右孩子(和上面情况一样,将左全部变成右即可)

3、删除节点

删除节点:先进行二叉排序树的删除操作,然后已替换节点作为当前节点进行后面的平衡操作

1. 当前节点是红色。对策:直接把当前节点染成黑色,结束。

2. 当前节点x是黑色,分以下几种情况:

第一种情况:被删除节点是父节点的左孩子

2.1 当前节点是根节点
对策:什么都不做
2.2 当前节点x的兄弟节点是红色(此时父节点和兄弟节点的子节点分为黑)
对策:把父节点染成红色,兄弟节点染成黑色,对父节点进行左旋,重新设置x的兄弟节点
5.png
2.3 当前节点x 的兄弟节点是黑色
2.3.1 兄弟节点的两个孩子都是黑色
对策:将x的兄弟节点设为红色,设置x的父节点为新的x节点
6.png
2.3.2 兄弟的右孩子是黑色,左孩子是红色
对策:将x兄弟节点的左孩子设为黑色,将x兄弟节点设置红色,将x的兄弟节点右旋,右旋后,重新设置x的兄弟节点。
7.png
2.3.3 兄弟节点的右孩子是红色
对策:把兄弟节点染成当前节点父节点颜色,把当前节点父节点染成黑色,兄弟节点右孩子染成黑色,再以当前节点的父节点为支点进行左旋,算法结算
8.png

第二种情况:被删除节点是父节点的右孩子(对策: 把第一种情况的左设置为右)

4、完整代码(代码有详细的注释)

import java.util.LinkedList;

/**
 * 红黑树
 * <p>
 * author: bobo
 * create time: 2018/12/18 3:39 PM
 * email: jqbo84@163.com
 */
public class RBTree<E extends Comparable<E>> {

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

    private Node<E> root;

    private int size;

    public Node<E> getRoot() {
        return root;
    }

    public int getSize() {
        return size;
    }

    /**
     * 获取颜色值
     *
     * @param p
     */
    private boolean colorOf(Node<E> p) {
        return (p == null ? BLACK : p.color);
    }

    /**
     * 获取父节点
     *
     * @param p
     * @return
     */
    private Node<E> parentOf(Node<E> p) {
        return (p == null ? null : p.parent);
    }

    /**
     * 设置节点的颜色
     *
     * @param p
     * @param color
     */
    private void setColor(Node<E> p, boolean color) {
        if (p != null) {
            p.color = color;
        }
    }

    /**
     * 获取当前节点的左节点
     *
     * @param p
     * @return
     */
    private Node<E> leftOf(Node<E> p) {
        return (p == null ? null : p.leftChild);
    }

    /**
     * 获取当前节点的右节点
     *
     * @param p
     * @return
     */
    private Node<E> rightOf(Node<E> p) {
        return (p == null ? null : p.rightChild);
    }

    /**
     * 左旋转
     *
     * @param x
     */
    public void leftRotate(Node<E> x) {
        if (x == null) {
            return;
        }
        //1.先取到x的右结点
        Node<E> y = x.rightChild;
        //2.把𝞫接上x的右结点上
        x.rightChild = y.leftChild;
        if (y.leftChild != null) {
            y.leftChild.parent = x;
        }
        //3.把y移到x的父结点上
        y.parent = x.parent;
        if (x.parent == null) {
            root = y;
        } else {
            if (x.parent.leftChild == x) {
                x.parent.leftChild = y;
            } else {
                x.parent.rightChild = y;
            }
        }
        y.leftChild = x;
        x.parent = y;
    }

    /**
     * 右旋转
     *
     * @param x
     */
    public void rightRotate(Node<E> x) {
        if (x == null) {
            return;
        }
        //1.先取到x的左结点
        Node<E> y = x.leftChild;
        //2.把𝞫接上x的左结点上
        x.leftChild = y.rightChild;
        if (y.rightChild != null) {
            y.rightChild.parent = x;
        }
        //3.把y移到x的父结点上
        y.parent = x.parent;
        if (x.parent == null) {
            root = y;
        } else {
            if (x.parent.leftChild == x) {
                x.parent.leftChild = y;
            } else {
                x.parent.rightChild = y;
            }
        }
        y.rightChild = x;
        x.parent = y;
    }

    /**
     * 插入节点:先按照二叉排序树的方式插入一个节点,再查找最小不平衡子树,以最小不平衡子树进行下面的操作
     *
     * @param element
     * @return
     */
    public boolean insertElement(E element) {
        if (element == null) {
            return false;
        }
        Node<E> t = root;
        if (t == null) {
            root = new Node<>(element, null);
            size = 1;
            return true;
        }
        int cmp = 0;
        Node<E> parent = null;
        Comparable<E> e = element;
        //1.查找可以插入的位置
        do {
            parent = t;
            cmp = e.compareTo(t.element);
            if (cmp < 0) {//比父结点小,那么查左结点
                t = t.leftChild;
            } else if (cmp > 0) {//比父结点大,那么查右结点
                t = t.rightChild;
            } else {//相等,说明是同一个数据,不需要插入
                return false;
            }
        } while (t != null);
        //2.找到可以插入的位置,进行插入
        Node<E> child = new Node<>(element, parent);
        if (cmp < 0) {
            parent.leftChild = child;
        } else {
            parent.rightChild = child;
        }
        //平衡树出现问题,需要修正
        fixAfterInsertion(child);
        size++;

        return true;
    }

    /**
     * 修正插入之后的平衡树
     *
     * @param node
     */
    public void fixAfterInsertion(Node<E> node) {
        if (node == null) return;

        node.color = RED;

        while (node != null && node != root && node.parent.color == RED) {
            //1.父节点是祖父节点的左孩子,有3种情况
            if (parentOf(node) == leftOf(parentOf(parentOf(node)))) {
                //取叔叔节点
                Node<E> uncleNode = rightOf(parentOf(parentOf(node)));
                if (colorOf(uncleNode) == RED) {//情况1:祖父节点的另一个子节点(叔叔节点)是红色
                    //对策: 将当前节点的父节点和叔叔节点涂黑,祖父节点涂红,把当前节点指向祖父节点,从新的当前节点开始算法
                    setColor(parentOf(node), BLACK);
                    setColor(uncleNode, BLACK);
                    setColor(parentOf(parentOf(node)), RED);
                    node = parentOf(parentOf(node));
                } else {//情况2:叔叔节点是黑色
                    if (node == rightOf(parentOf(node))) {//情况2.1:当前节点是其父节点的右孩子
                        //对策:当前节点的父节点做为新的当前节点,以新当前节点为支点左旋。
                        node = parentOf(node);
                        leftRotate(node);
                    }
                    //情况2.2:当前节点是其父节点的左孩子
                    //对策:父节点变为黑色,祖父节点变红色,再祖父节点为支点进行右旋
                    setColor(parentOf(node), BLACK);
                    setColor(parentOf(parentOf(node)), RED);
                    rightRotate(parentOf(parentOf(node)));
                }
            }
            //2.父节点是祖父节点的右孩子
            else {
                //取叔叔节点
                Node<E> uncleNode = leftOf(parentOf(parentOf(node)));
                if (colorOf(uncleNode) == RED) {//情况1:祖父节点的另一个子节点(叔叔节点)是红色
                    //对策: 将当前节点的父节点和叔叔节点涂黑,祖父节点涂红,把当前节点指向祖父节点,从新的当前节点开始算法
                    setColor(parentOf(node), BLACK);
                    setColor(uncleNode, BLACK);
                    setColor(parentOf(parentOf(node)), RED);
                    node = parentOf(parentOf(node));
                } else {//情况2:叔叔节点是黑色
                    if (node == leftOf(parentOf(node))) {//情况2.1:当前节点是其父节点的左孩子
                        //对策:当前节点的父节点做为新的当前节点,以新当前节点为支点右旋。
                        node = parentOf(node);
                        rightRotate(node);
                    }
                    //情况2.2:当前节点是其父节点的右孩子
                    //对策:父节点变为黑色,祖父节点变红色,再祖父节点为支点进行左旋
                    setColor(parentOf(node), BLACK);
                    setColor(parentOf(parentOf(node)), RED);
                    leftRotate(parentOf(parentOf(node)));
                }
            }
        }
        root.color = BLACK;
    }

    /**
     * 根据当前的值,获取当前节点
     *
     * @param element
     * @return
     */
    public Node<E> getNode(E element) {
        if (element == null) {
            return null;
        }
        Node<E> t = root;
        Comparable<E> e = element;
        while (t != null) {
            int cmp = e.compareTo(t.element);
            if (cmp < 0) {
                t = t.leftChild;
            } else if (cmp > 0) {
                t = t.rightChild;
            } else {
                return t;
            }
        }
        return null;
    }

    /**
     * 获取传入P节点下面最小的节点
     *
     * @param p
     * @return
     */
    public Node<E> getMinLeftNode(Node<E> p) {
        if (p == null) {
            return null;
        }
        Node<E> t = p;
        while (t.leftChild != null) {
            t = t.leftChild;
        }
        return t;
    }

    /**
     * 删除元素
     *
     * @param element
     */
    public void remove(E element) {
        //查找当前的节点
        Node<E> node = getNode(element);
        if (node == null) {
            return;
        }
        deleteElement(node);
    }

    /**
     * 删除当前的节点
     *
     * @param node
     */
    public void deleteElement(Node<E> node) {

        if (node.leftChild != null && node.rightChild != null) {
            Node<E> s = getMinLeftNode(node.rightChild);
            node.element = s.element;
            node = s;
        }

        Node<E> t = (node.leftChild != null ? node.leftChild : node.rightChild);

        if (t == null) {
            Node<E> parent = node.parent;
            if (parent == null) {
                root = null;
            } else {
                // 注重:如果删除节点是黑色,那么一定要先修正红黑二叉树,在做下面的删除动作
                if (node.color == BLACK) {
                    fixAfterDeletion(node);
                }

                if (parent.leftChild == node) {
                    parent.leftChild = null;
                } else {
                    parent.rightChild = null;
                }
                node.parent = null;
            }
        } else {
            t.parent = node.parent;
            if (node.parent == null) {
                root = t;
            } else {
                if (node.parent.leftChild == node) {
                    node.parent.leftChild = t;
                } else {
                    node.parent.rightChild = t;
                }
            }
            node.leftChild = node.rightChild = node.parent = null;
            if (node.color == BLACK) {
                fixAfterDeletion(t);
            }
        }

        size--;
    }

    /**
     * 删除节点后,修正红黑树
     * @param node
     */
    public void fixAfterDeletion(Node<E> node) {
        while (node != root && colorOf(node) == BLACK) {
            if (leftOf(parentOf(node)) == node) { //被删除节点是父节点的左孩子
                //拿到node的兄弟节点
                Node<E> sib = rightOf(parentOf(node));
                if (colorOf(sib) == RED) { //2.2 当前节点x的兄弟节点是红色(此时父节点和兄弟节点的子节点分为黑)
                    //对策:把父节点染成红色,兄弟节点染成黑色,对父节点进行左旋,重新设置x的兄弟节点
                    setColor(parentOf(node), RED);
                    setColor(sib, BLACK);
                    leftRotate(parentOf(node));
                    sib = rightOf(parentOf(node));
                }
                if (colorOf(sib) == BLACK) {//2.3 当前节点x 的兄弟节点是黑色
                    if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) {//2.3.1 兄弟节点的两个孩子都是黑色
                        //对策:将x的兄弟节点设为红色,设置x的父节点为新的x节点
                        setColor(sib, RED);
                        node = parentOf(node);
                    } else {
                        if (colorOf(rightOf(sib)) == BLACK) {//2.3.2 兄弟的右孩子是黑色,左孩子是红色
                            //对策:将x兄弟节点的左孩子设为黑色,将x兄弟节点设置红色,将x的兄弟节点右旋,右旋后,重新设置x的兄弟节点。
                            setColor(leftOf(sib), BLACK);
                            setColor(sib, RED);
                            rightRotate(sib);
                            sib = rightOf(parentOf(node));
                        }
                        //2.3.3 兄弟节点的右孩子是红色
                        //对策:把兄弟节点染成当前节点父节点颜色,把当前节点父节点染成黑色,兄弟节点右孩子染成黑色,再以当前节点的父节点为支点进行左旋,算法结算
                        setColor(sib, colorOf(parentOf(node)));
                        setColor(parentOf(node), BLACK);
                        setColor(rightOf(sib), BLACK);
                        leftRotate(parentOf(node));
                        node = root;
                    }
                }
            } else { //被删除节点是父节点的右孩子, 对策: 把上面的左设置为右
                //拿到node的兄弟节点
                Node<E> sib = leftOf(parentOf(node));
                if (colorOf(sib) == RED) { //2.2 当前节点x的兄弟节点是红色(此时父节点和兄弟节点的子节点分为黑)
                    //对策:把父节点染成红色,兄弟节点染成黑色,对父节点进行右旋,重新设置x的兄弟节点
                    setColor(parentOf(node), RED);
                    setColor(sib, BLACK);
                    rightRotate(parentOf(node));
                    sib = leftOf(parentOf(node));
                }
                if (colorOf(sib) == BLACK) {//2.3 当前节点x 的兄弟节点是黑色
                    if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) {//2.3.1 兄弟节点的两个孩子都是黑色
                        //对策:将x的兄弟节点设为红色,设置x的父节点为新的x节点
                        setColor(sib, RED);
                        node = parentOf(node);
                    } else {
                        if (colorOf(leftOf(sib)) == BLACK) {//2.3.2 兄弟的左孩子是黑色,右孩子是红色
                            //对策:将x兄弟节点的右孩子设为黑色,将x兄弟节点设置红色,将x的兄弟节点左旋,左旋后,重新设置x的兄弟节点。
                            setColor(rightOf(sib), BLACK);
                            setColor(sib, RED);
                            leftRotate(sib);
                            sib = leftOf(parentOf(node));
                        }
                        //2.3.3 兄弟节点的左孩子是红色
                        //对策:把兄弟节点染成当前节点父节点颜色,把当前节点父节点染成黑色,兄弟节点左孩子染成黑色,再以当前节点的父节点为支点进行右旋,算法结算
                        setColor(sib, colorOf(parentOf(node)));
                        setColor(parentOf(node), BLACK);
                        setColor(leftOf(sib), BLACK);
                        rightRotate(parentOf(node));
                        node = root;
                    }
                }
            }
        }
    }

    /**
     * 一层一层打印出数据
     *
     * @param root)
     */
    public void showRBTree(Node<E> root) {
        if (root == null) {
            return;
        }
        LinkedList<Node<E>> list = new LinkedList<>();
        list.offer(root);
        while (!list.isEmpty()) {
            Node<E> node = list.pop();
            System.out.println("node.element = " + node.element + ", color = " + node.color);
            if (node.leftChild != null) {
                list.offer(node.leftChild);
            }
            if (node.rightChild != null) {
                list.offer(node.rightChild);
            }
        }
    }

    /**
     * 红黑树结点
     *
     * @param <E>
     */
    public class Node<E extends Comparable>{
        E element;
        Node<E> leftChild;
        Node<E> rightChild;
        Node<E> parent;

        //0:黑色  1:红色
        boolean color = BLACK;

        public Node(E element, Node<E> parent) {
            this.element = element;
            this.parent = parent;
        }
    }
}

5、测试

@Test
public void testRBTree(){
    Integer[] nums = {13,8,5,11,6,22,27,25,14,17};
    RBTree<Integer> tree = new RBTree<>();
    for (int i = 0; i < nums.length; i++) {
        Integer num = nums[i];
        tree.insertElement(num);
    }
    tree.showRBTree(tree.getRoot());

    tree.remove(25);
    System.out.println("---------------------------------------- ");
    tree.showRBTree(tree.getRoot());

    System.out.println("---------------------------------------- ");
    for (Integer num : nums) {
        tree.remove(num);
        tree.showRBTree(tree.getRoot());
        System.out.println("------------------------------------ : " + num);
    }

}

测试结果: true:是黑色,false:是红色
---------------打印插入数据--------------- 
node.element = 13, color = true
node.element = 8, color = false
node.element = 25, color = false
node.element = 5, color = true
node.element = 11, color = true
node.element = 17, color = true
node.element = 27, color = true
node.element = 6, color = false
node.element = 14, color = false
node.element = 22, color = false
---------------------------------------- 删除: 25
node.element = 13, color = true
node.element = 8, color = false
node.element = 17, color = false
node.element = 5, color = true
node.element = 11, color = true
node.element = 14, color = true
node.element = 27, color = true
node.element = 6, color = false
node.element = 22, color = false
----------------循环删除----------------- 
------------------------------------ 删除: 13
node.element = 14, color = true
node.element = 8, color = false
node.element = 22, color = false
node.element = 5, color = true
node.element = 11, color = true
node.element = 17, color = true
node.element = 27, color = true
node.element = 6, color = false
------------------------------------ 删除: 8
node.element = 14, color = true
node.element = 6, color = false
node.element = 22, color = false
node.element = 5, color = true
node.element = 11, color = true
node.element = 17, color = true
node.element = 27, color = true
------------------------------------ 删除: 5
node.element = 14, color = true
node.element = 6, color = false
node.element = 22, color = false
node.element = 11, color = false
node.element = 17, color = true
node.element = 27, color = true
------------------------------------ 删除: 11
node.element = 14, color = true
node.element = 6, color = false
node.element = 22, color = false
node.element = 17, color = true
node.element = 27, color = true
------------------------------------ 删除: 6
node.element = 14, color = true
node.element = 22, color = false
node.element = 17, color = true
node.element = 27, color = true
------------------------------------ 删除: 22
node.element = 14, color = true
node.element = 27, color = false
node.element = 17, color = false
------------------------------------ 删除: 27
node.element = 14, color = true
node.element = 17, color = false
------------------------------------ 删除: 25
node.element = 14, color = true
node.element = 17, color = false
------------------------------------ 删除: 14
node.element = 17, color = false
------------------------------------ 删除: 17

附上插入数据的红黑树 笔记 如图:

9.jpg
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容