【數據結構】紅黑樹簡單實現 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

通過控制檯輸出各種顏色的字符

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