【数据结构】红黑树简单实现 JAVA版

通过java简单实现红黑树

package com.example.demo.rbt;

import lombok.Data;

public class RBTree<K extends Comparable<K>, V> {

    /**
     * 红色
     */
    public static final boolean RED = true;

    /**
     * 黑色
     */
    public static final boolean BLACK = false;

    /**
     * 根节点
     */
    private RBNode<K, V> root;

    /**
     * 获取父节点
     * @param node 当前节点
     * @return 父节点
     */
    private RBNode<K, V> getParentNode(RBNode<K, V> node){
        if (node != null) {
            return node.parent;
        }
        return null;
    }

    /**
     * 判断节点是否是红色
     * @param node 当前节点
     * @return true or false
     */
    private boolean isRed(RBNode<K, V> node){
        return node != null && node.color;
    }

    /**
     * 判断节点是否是黑色
     * @param node 当前节点
     * @return true or false
     */
    private boolean isBlack(RBNode<K, V> node){
        return node != null && !node.color;
    }

    /**
     * 设置为红色
     * @param node 当前节点
     */
    private void setRed(RBNode<K, V> node){
        if (node != null) {
            node.setColor(RED);
        }
    }

    /**
     * 设置为黑色
     * @param node 当前节点
     */
    private void setBlack(RBNode<K, V> node){
        if (node != null) {
            node.setColor(BLACK);
        }
    }

    /**
     * 右旋:以某个节点作为支点(旋转节点),其左子节点变为旋转节点的父节点,左子节点的右子节点变为旋转节点的左子节点,右子节点保持不变
     *           F                          F
     *           |                          |
     *           K (旋转节点)                H
     *          / \          ==>           / \
     *         H   M                     NIL  K
     *        / \                            / \
     *      NIL  NIL                       NIL  M
     * @param node 旋转节点
     */
    private void rightSpin(RBNode<K, V> node){

        // 当前节点的父节点 F
        RBNode<K, V> pN = node.parent;
        // 当前节点的左子节点 H
        RBNode<K, V> lN = node.left;

        // 将K的左子节点指向H的右子节点NIL,将H的右子节点的父节点更新为K  K--NIL
        node.left = lN.right;
        if (lN.right != null) {
            lN.right.parent = node;
        }

        // 将K的父节点更新为H,将H的右子节点更新为K  H--K
        node.parent = lN;
        lN.right = node;

        // 当F不为空时(如果K是根节点就有可能为null),将H的父节点更新为F,将F的左(右)子节点更新为H  H--F
        if (pN != null) {
            lN.parent = pN;
            if (pN.left == node) {
                pN.left = lN;
            }else {
                pN.right = lN;
            }
        }else {
            // 将H设置为根节点
            this.root = lN;
        }
    }

    /**
     * 左旋:以某个节点作为支点(旋转节点),其右子结点变为旋转节点的父节点,右子结点的左子节点变为旋转节点的右子节点,左子节点保持不变
     *           F                           F
     *           |                           |
     *           K (旋转节点)                 M
     *          / \            ==>          / \
     *         H   M                       K  NIL
     *            / \                     / \
     *          NIL NIL                  H  NIL
     * @param node 旋转节点
     */
    private void leftSpin(RBNode<K, V> node){

        // 当前节点的父节点 F
        RBNode<K, V> pN = node.parent;
        // 当前节点的右子节点 M
        RBNode<K, V> rN = node.right;

        // 将K的右子节点指向M的左子节点NIL,将M的左子节点的父节点更新为K  K--NIL
        node.right = rN.left;
        if (rN.left != null) {
            rN.left.parent = node;
        }

        // 将K的父节点更新为M,将M的左子节点更新为K  K--M
        node.parent = rN;
        rN.left = node;

        // 当F不为空时(如果K是根节点就有可能为null),将M的父节点更新为F,将F的左(右)子节点更新为M  M--F
        rN.parent = pN;
        if (pN != null) {
            // 如果K原先是左节点则将F的左节点更新为M,如果是右节点则将F的右节点更新为M
            if (pN.left == node) {
                pN.left = rN;
            }else {
                pN.right = rN;
            }
        }else {
            // 将M设置为根节点
            this.root = rN;
        }
    }

    /**
     * 中序遍历
     */
    public void inOrderPrint(){
        System.out.print("中序遍历:");
        inOrderPrint(this.root);
    }

    public RBNode<K, V> getRoot(){
        return this.root;
    }

    private void inOrderPrint(RBNode<K, V> node){
        if (node != null) {
            inOrderPrint(node.left);
            System.out.print(node.key + " ");
            inOrderPrint(node.right);
        }
    }

    public RBNode<K, V> get(K key){
        return getNode(this.root, key);
    }

    private RBNode<K, V> getNode(RBNode<K, V> node, K key){
        while (node != null){
            K k = node.key;
            if (key.compareTo(k) > 0) {
                node = node.right;
            }else if(key.compareTo(k) < 0){
                node = node.left;
            }else {
                return node;
            }
        }
        return null;
    }

    public void add(K key, V value){
        RBNode<K, V> node = new RBNode<>(RED, key, value);
        addNode(node);
    }

    private void addNode(RBNode<K, V> node){
        // 查找当前节点的父节点,从root开始
        RBNode<K, V> parent = null;
        RBNode<K, V> temp = this.root;
        while (temp != null){
            parent = temp;
            K k = temp.key;
            int i = node.key.compareTo(k);
            if (i > 0) {
                // 插入节点的Key大于当前节点的Key,从当前节点的右子树找
                temp = temp.right;
            } else if (i < 0) {
                // 插入节点的Key小于当前节点的Key,从当前节点的左子树找
                temp = temp.left;
            } else {
                // 插入节点的Key等于当前节点的Key,直接替换value  TODO 场景二
                temp.value = node.value;
                return;
            }
        }
        // 设置插入节点的父节点
        node.parent = parent;

        if (parent != null) {
            int i = node.key.compareTo(parent.key);
            if (i > 0) {
                // 如果插入节点的Key大于父节点的Key,则放在父节点的右子节点上
                parent.right = node;
            }else {
                // 如果插入节点的Key小于父节点的Key,则放在父节点的左子节点上
                parent.left = node;
            }
        }else {
            // 说明是颗空树,将插入节点设置为根节点 TODO 场景一
            this.root = node;
        }

        // 插入完后走自平衡处理
        selfBalance(node);

    }


    /**
     * 插入节点的后续处理
     *  |--- 1.红黑树为空数,将插入节点设置为根节点并置为黑色
     *  |--- 2.插入节点的Key已存在,则更新已存在节点的Value为插入节点的Value
     *  |--- 3.插入节点的父节点是黑色的,则直接插入,不会影响平衡
     *  |--- 4.插入节点的父节点是红色的
     *      |--- 4.1 叔叔节点存在并且为红节点
     *          - 将父节点(F)和叔叔节点(V)设置为黑色
     *          - 将祖父节点(P)设置为红色
     *          - 将祖父节点设置为当前插入节点
     *      |--- 4.2 叔叔节点不存在或者为黑节点,并且插入节点的父节点是祖父节点的左子节点
     *          |--- 4.2.1 插入节点是其父节点的左子节点(LL)
     *              - 将父节点(F)设置为黑色
     *              - 将祖父节点(P)设置为红色
     *              - 对祖父节点(P)进行右旋
     *          |--- 4.2.1 插入节点是其父节点的右子节点(LR)
     *              - 对父节点(F)进行左旋
     *              - 把父节点(F)设置为插入结点,得到情景4.2.1
     *              - 进行情景4.2.1的处理
     *      |--- 4.3 叔叔节点不存在或者为黑节点,并且插入节点的父节点是祖父节点的右子节点
     *          |--- 4.3.1 插入节点是其父节点的右子节点(RR)
     *              - 将父节点(F)设置为黑色
     *              - 将祖父节点(P)设置为红色
     *              - 对祖父节点(P)进行左旋
     *          |--- 4.3.2 插入节点是其父节点的左子节点(RL)
     *              - 对父节点(F)进行右旋
     *              - 把父节点(F)设置为插入结点,得到情景4.3.1
     *              - 进行情景4.3.1的处理

     * @param node
     */
    private void selfBalance(RBNode<K, V> node){
        // 1.红黑树为空数,将插入节点设置为根节点并置为黑色
        if (this.root == node) {
            setBlack(node);
            return;
        }

        // 2.插入节点的Key已存在,则更新已存在节点的Value为插入节点的Value


        // 3.插入节点的父节点是黑色的,则直接插入,不会影响平衡


        // 4.插入节点的父节点是红色的

        // 父节点
        RBNode<K, V> pN = getParentNode(node);
        // 祖父节点
        RBNode<K, V> gpN = getParentNode(pN);

        if (isRed(pN)) {
            // 叔叔节点
            RBNode<K, V> uN;
            if (gpN.left == pN) {
                uN = gpN.right;
            }else {
                uN = gpN.left;
            }
            // 4.1 叔叔节点存在并且为红节点
            if (isRed(uN)) {
                // 将父节点和叔叔节点设置为黑色,祖父节点设置为红色,然后将祖父节点设置为当前节点进行递归
                setBlack(pN);
                setBlack(uN);
                setRed(gpN);
                selfBalance(gpN);
                return;
            }
            // 4.2 叔叔节点不存在或者为黑节点
            if (uN == null || isBlack(uN)) {
                // 插入节点的父节点是祖父节点的左子节点
                if (pN == gpN.left) {
                    // 4.2.1 插入节点是其父节点的左子节点(LL)
                    if (node == pN.left) {
                        setBlack(pN);
                        setRed(gpN);
                        rightSpin(gpN);
                    }else {
                        // 4.2.2 插入节点是其父节点的右子节点(LR)
                        leftSpin(pN );
                        selfBalance(pN);
                    }

                }else {
                    // 插入节点的父节点是祖父节点的右子节点
                    if (node == node.parent.left) {
                        // 4.3.2 插入节点是其父节点的左子节点(RL)
                        rightSpin(pN);
                        selfBalance(pN);
                    }else {
                        // 4.3.1 插入节点是其父节点的右子节点(RR)
                        setBlack(pN);
                        setRed(gpN);
                        leftSpin(gpN);
                    }
                }
            }
        }
    }


    @Data
    static class RBNode<K extends Comparable<K>, V>{
        /**
         * 父节点
         */
        private RBNode<K, V> parent;

        /**
         * 右子节点
         */
        private RBNode<K, V> right;

        /**
         * 左子节点
         */
        private RBNode<K, V> left;

        /**
         * 颜色
         */
        private boolean color;

        /**
         * 键
         */
        private K key;

        /**
         * 值
         */
        private V value;

        public RBNode() {
        }

        public RBNode(boolean color, K key, V value) {
            this.color = color;
            this.key = key;
            this.value = value;
        }

        /**
         * 这里重写toString方法是因为子节点包含父节点的引用,父节点也包含子节点的引用,
         * 在debug的时候会调用toString方法显示结构,相互引用造成StackOverflowError
         */
        @Override
        public String toString() {
            return "RBNode{" +
                    "color=" + color +
                    ", key=" + key +
                    ", value=" + value +
                    '}';
        }
    }
}

打印红黑树结构(网上找的一个方法)

package com.example.demo.rbt;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;


public class RBTreePrinterTest {

    public static <K extends Comparable<K>, V> void printNode(RBTree.RBNode<K, V> root) {
        int maxLevel = RBTreePrinterTest.maxLevel(root);

        printNodeInternal(Collections.singletonList(root), 1, maxLevel);
    }

    private static <K extends Comparable<K>, V> void printNodeInternal(List<RBTree.RBNode<K, V>> nodes, int level, int maxLevel) {
        if (nodes.isEmpty() || RBTreePrinterTest.isAllElementsNull(nodes)) {
            return;
        }

        int floor = maxLevel - level;
        int endgeLines = (int) Math.pow(2, (Math.max(floor - 1, 0)));
        int firstSpaces = (int) Math.pow(2, (floor)) - 1;
        int betweenSpaces = (int) Math.pow(2, (floor + 1)) - 1;

        RBTreePrinterTest.printWhitespaces(firstSpaces);

        List<RBTree.RBNode<K, V>> newNodes = new ArrayList<>();
        for (RBTree.RBNode<K, V> node : nodes) {
            if (node != null) {
                String key = node.isColor() ? "\033[31;4m" + node.getKey() + "\033[0m" : node.getKey().toString();
                System.out.print(key);
                newNodes.add(node.getLeft());
                newNodes.add(node.getRight());
            } else {
                newNodes.add(null);
                newNodes.add(null);
                System.out.print(" ");
            }

            RBTreePrinterTest.printWhitespaces(betweenSpaces);
        }
        System.out.println();

        for (int i = 1; i <= endgeLines; i++) {
            for (RBTree.RBNode<?, ?> node : nodes) {
                RBTreePrinterTest.printWhitespaces(firstSpaces - i);
                if (node == null) {
                    RBTreePrinterTest.printWhitespaces(endgeLines + endgeLines + i + 1);
                    continue;
                }

                if (node.getLeft() != null) {
                    System.out.print("/");
                } else {
                    RBTreePrinterTest.printWhitespaces(1);
                }

                RBTreePrinterTest.printWhitespaces(i + i - 1);

                if (node.getRight() != null) {
                    System.out.print("\\");
                } else {
                    RBTreePrinterTest.printWhitespaces(1);
                }

                RBTreePrinterTest.printWhitespaces(endgeLines + endgeLines - i);
            }

            System.out.println("");
        }

        printNodeInternal(newNodes, level + 1, maxLevel);
    }

    private static void printWhitespaces(int count) {
        for (int i = 0; i < count; i++) {
            System.out.print(" ");
        }
    }

    private static <K extends Comparable<K>, V> int maxLevel(RBTree.RBNode<K, V> node) {
        if (node == null) {
            return 0;
        }
        return Math.max(RBTreePrinterTest.maxLevel(node.getLeft()), RBTreePrinterTest.maxLevel(node.getRight())) + 1;
    }

    private static <K extends Comparable<K>, V> boolean isAllElementsNull(List<RBTree.RBNode<K, V>> list) {
        for (Object object : list) {
            if (object != null) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {

        RBTree<Integer, Object> rbTree = new RBTree<>();
        // 随机15位数插入
        new Random().ints(15, 1, 50).forEach(k -> rbTree.add(k, null));
        RBTreePrinterTest.printNode(rbTree.getRoot());
        rbTree.inOrderPrint();

    }
}

实现效果

在这里插入图片描述

红色字体需要安装IDEA插件 ANSI Highlighter

通过控制台输出各种颜色的字符

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值