高级数据结构之红黑树

红黑树性质

红黑树的性质(重点):

  1. 每个结点不是红色就是黑色
  2. 不可能有连在一起的红色结点(黑色的就可以),每个叶子节点都是黑色的空节点(NIL),也就是说,叶子节点不存储数据
  3. 根结点一定是黑色
  4. 每个节点,从该节点到达其可达叶子节点的所有路径,都包含相同数目的黑色节点

红黑树的性能

插入查找删除
近似:nlognlogn近似logn

红黑树的应用:

  1. HashMap
  2. TreeMap
  3. Windows底层:查找
  4. Linux进程调度,nginx等

为了满足红黑树的性质,因此出现了旋转:3种变换

1、改变颜色 2、左旋 3.、右旋

变化规则

颜色变化

条件:当前结点的父亲是红色,叔叔结点也是红色:

  1. 把父结点设为黑色
  2. 把叔叔结点也设为黑色
  3. 把爷爷结点设置为红色
  4. 把指针定义到爷爷结点设为当前要操作的结点
    private boolean colorChange(MyTreeNode node) {
        MyTreeNode uncleNode = node.uncleNode(); // 叔叔节点
        // 父红叔红
        if (node.parent.color == R && uncleNode != nil &&  uncleNode.color == R) {
            // 把父结点设为黑色
            node.parent.color = B;
            // 把叔叔结点也设为黑色
            uncleNode.color = B;
            // 爷爷节点设置为红色,如果爷爷为根颜色为黑
            if (node.parent.parent.parent != nil){
                node.parent.parent.color = R;
            }
            // 把指针定义到爷爷结点结点设为当前要操作的结点
            node = node.parent.parent;
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

左旋

  1. 当前结点是右子树:父结点是红色,叔叔结点黑色,则进行左旋。左旋过程不需要进行颜色变换
  2. 当前结点是左子树:父结点是红色,叔叔结点黑色,则进行左旋。左旋过程需要进行颜色变换
if (node.parent.color == R && node.uncleNode().color == B ) { // 父红叔黑
    if (node.parent.isLeftNode() && node.isRightNode()) { // 父左子右
            node = node.parent; // 自底向上
            rotateLeft(node); // 左旋
    }
    if (node.parent.isRightNode() && node == node.parent.right) { // 父右子右
            node.parent.color = B;  // 父亲设置为黑
            node.parent.parent.color = R; // 爷爷设置为红
            rotateLeft(node.parent.parent); // 以爷爷结点进行左边旋
    }
}

右旋

  1. 当前结点是右子树:父结点红色,叔叔结点黑色,则进行右旋。右旋过程中不需要进行颜色变换
  2. 当前结点是左子树:父结点红色,叔叔结点黑色,则进行右旋。右旋过程中需要进行颜色变换
if (node.parent.color == R && node.uncleNode().color == B ) { // 父红叔黑
    if (node.parent.isLeftNode() && node.isLeftNode()) { // 父左子左
            node.parent.color = B; // 把父结点设为黑色
            node.parent.parent.color = R; // 把爷爷结点设为红色
            rotateRight(node.parent.parent); // 以爷爷结点进行右旋
    }
    if (node.parent.isRightNode() && node == node.parent.right) { // 父右子左
            node = node.parent;
            rotateRight(node); //右旋
    }
}

实现红黑树

节点定义

package datastructure.tree;

import datastructure.queue.LinkedListQueue;

/**
 * 节点
 *
 * @author zw
 * @create 2023-04-03 16:12
 */
public class MyTreeNode<T extends Comparable<T>> {
    public int color;   // 颜色:红黑树属性
    public int weight;     //频率(权重):哈夫曼树属性
    public T data;
    public MyTreeNode<T> left;
    public MyTreeNode<T> right;
    public MyTreeNode<T> parent;

    @Override
    public String toString() {
        return "Node [color=" + color + ", left=" + left.data + ", right=" + right.data + ", parent="
                + parent.data + "]" + "\r\n";
    }

    public MyTreeNode(T data, MyTreeNode<T> left, MyTreeNode<T> right) {
        this.data = data;
        this.left = left;
        this.right = right;
    }

    public MyTreeNode(T data) {
        this.data = data;
    }

    public MyTreeNode(T data, int weight) {
        this.data = data;
        this.weight = weight;
    }

    // 用于节点的层数
    public int getNodeDepth(BinaryNode root) {
        return root.equals(this) ? 0 : (1 + Math.max(getNodeDepth(root.left), getNodeDepth(root.right)));
    }


    /**
     * 查找后继节点
     *
     * @return
     */
    public MyTreeNode findSuccessorsNode() { // 查找node的后继节点
        if (this.right == null) { // 表示没有右边 那就没有后继
            return this;
        }
        MyTreeNode cur = this.right;
        MyTreeNode pre = this.right; // 开一个额外的空间 用来返回后继节点,因为我们要找到为空的时候,那么其实返回的是上一个节点
        while (cur != null) {
            pre = cur;
            cur = cur.left; // 注意后继节点是要往左边找,因为右边的肯定比左边的大,我们要找的是第一个比根节点小的,所以只能往左边
        }
        return pre; // 因为cur会变成null,实际我们是要cur的上一个点,所以就是pre来代替
    }

    /**
     * 前序遍历:根(输出) 左 右 时间复杂度?O(n) N^2 O(2*n)=>O(n);
     */
    public void pre(MyTreeNode root) {
        System.out.print(root.data); // 根
        if (root.left != null) pre(root.left); // 左
        if (root.right != null) pre(root.right); // 右
    }

    public void pre() {
        pre(this);
    }

    /**
     * 中序遍历:左 根(输出)  右
     */
    public void in(MyTreeNode root) {
        if (root.left != null) in(root.left); // 左
        System.out.print(root); // 根
        if (root.right != null) in(root.right); // 右
    }

    public void in() {
        in(this);
    }

    /**
     * 后序遍历:左  右 根(输出)
     */
    public void post(MyTreeNode root) {
        if (root.left != null) post(root.left); // 左
        if (root.right != null) post(root.right); // 右
        System.out.print(root.data); // 根
    }

    public void post() {
        post(this);
    }

    /**
     * 广度优先遍历:层次遍历
     * 算法思想:
     * (1)我们定义一个队列,先将根结点入队;
     * (2)当前结点是队头结点,将其出队并访问;
     * (3)若当前结点的左结点不为空将左结点入队;若当前结点的右结点不为空将其入队即可。
     */
    private void bfs(MyTreeNode root) {
        LinkedListQueue<MyTreeNode> queue = new LinkedListQueue<MyTreeNode>(100);
        // 添加元素到队尾
        queue.add(root);
        while (!queue.isEmpty()) {
            // 取出队头元素
            MyTreeNode head = queue.poll();
            System.out.print(head.data);
            // 将左节点添加到队列
            if (head.left != null) queue.add(head.left);
            // 将右边节点添加队列
            if (head.right != null) queue.add(head.right);
        }
    }

    public void bfs() {
        bfs(this);
    }


    /**
     * 兄弟节点
     *
     * @return
     */
    public MyTreeNode brotherNode() {
        if (this.isLeftNode()) return this.parent.right;
        if (this.isRightNode()) return this.parent.left;
        return null;
    }

    /**
     * 叔叔节点
     *
     * @return
     */
    public MyTreeNode uncleNode() {
        if (this.parent == null) return null;
        else if (this.parent.isLeftNode()) return this.parent.parent.right;
        else if (this.parent.isRightNode()) return this.parent.parent.left;
        return null;
    }

    /**
     * 大于
     *
     * @param data
     * @return
     */
    public boolean gt(T data) {
        return this.data.compareTo(data) > 0;
    }

    public boolean gt(MyTreeNode<T> node) {
        return this.data.compareTo(node.data) > 0;
    }

    /**
     * 等于
     *
     * @param data
     * @return
     */
    public boolean equals(T data) {
        return this.data.compareTo(data) == 0;
    }

    /**
     * 小于
     *
     * @param data
     * @return
     */
    public boolean lt(T data) {
        return this.data.compareTo(data) < 0;
    }

    public boolean lt(MyTreeNode<T> node) {
        return this.data.compareTo(node.data) < 0;
    }

    public boolean isRootNode() {
        return this.parent == null;
    }

    public boolean isLeftNode() {
        return this.equals(this.parent.left);
    }

    public boolean isRightNode() {
        return this.equals(this.parent.right);
    }

    public boolean isLeaf() {
        return this.left == null && this.right == null;
    }

    public boolean isLeftLeaf() {
        return (this.left == null && this.right == null)
                && this.equals(this.parent.left);
    }

    public boolean isRightLeaf() {
        return (this.left == null && this.right == null)
                && this.equals(this.parent.right);
    }

    /**
     * 存在2个子节点
     *
     * @return
     */
    public boolean have2ChildNode() {
        return this.left != null && this.right != null;
    }

    /**
     * 只存在左子节点
     *
     * @return
     */
    public boolean onlyLeftChildNode() {
        return this.left != null && this.right == null;
    }

    /**
     * 只存在右子节点
     *
     * @return
     */
    public boolean onlyRightChildNode() {
        return this.left == null && this.right != null;
    }

    /**
     * 打印当前节点树
     */
    public void show() {
        // 得到树的深度
        int treeDepth = getTreeDepth(this);

        // 最后一行的宽度为2的(n - 1)次方乘3,再加1
        // 作为整个二维数组的宽度
        int arrayHeight = treeDepth * 2 - 1;
        int arrayWidth = (2 << (treeDepth - 2)) * 3 + 1;
        // 用一个字符串数组来存储每个位置应显示的元素
        String[][] res = new String[arrayHeight][arrayWidth];
        // 对数组进行初始化,默认为一个空格
        for (int i = 0; i < arrayHeight; i++) {
            for (int j = 0; j < arrayWidth; j++) {
                res[i][j] = " ";
            }
        }

        // 从根节点开始,递归处理整个树
        writeArray(this, 0, arrayWidth / 2, res, treeDepth);

        // 此时,已经将所有需要显示的元素储存到了二维数组中,将其拼接并打印即可
        for (String[] line : res) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < line.length; i++) {
                sb.append(line[i]);
                if (line[i].length() > 1 && i <= line.length - 1) {
                    i += line[i].length() > 4 ? 2 : line[i].length() - 1;
                }
            }
            System.out.println(sb.toString());
        }
    }

    private void writeArray(MyTreeNode<T> currNode, int rowIndex, int columnIndex, String[][] res, int treeDepth) {
        // 保证输入的树不为空
        if (currNode == null) return;
        // 先将当前节点保存到二维数组中
        res[rowIndex][columnIndex] = String.valueOf(currNode.data)+"|" + String.valueOf(currNode.color);

        // 计算当前位于树的第几层
        int currLevel = ((rowIndex + 1) / 2);
        // 若到了最后一层,则返回
        if (currLevel == treeDepth)
            return;
        // 计算当前行到下一行,每个元素之间的间隔(下一行的列索引与当前元素的列索引之间的间隔)
        int gap = treeDepth - currLevel - 1;

        // 对左儿子进行判断,若有左儿子,则记录相应的"/"与左儿子的值
        if (currNode.left != null) {
            res[rowIndex + 1][columnIndex - gap] = "/";
            writeArray(currNode.left, rowIndex + 2, columnIndex - gap * 2, res, treeDepth);
        }

        // 对右儿子进行判断,若有右儿子,则记录相应的"\"与右儿子的值
        if (currNode.right != null) {
            res[rowIndex + 1][columnIndex + gap] = "\\";
            writeArray(currNode.right, rowIndex + 2, columnIndex + gap * 2, res, treeDepth);
        }
    }


    /**
     * 用于获得树的层数
     *
     * @return
     */
    public int getTreeDepth(MyTreeNode<T> currNode) {
        return currNode == null ? 0 : (1 + Math.max(getTreeDepth(currNode.left), getTreeDepth(currNode.right)));
    }

}

红黑树

package datastructure.tree;

import java.util.Arrays;

/**
 * 红黑树
 * 特性:
 * 1) 每个结点不是红色就是黑色
 * 2) 不可能有连在一起的红色结点(黑色的就可以)
 * 3) 每个叶子节点都是黑色的空节点(NIL),也就是说,叶子节点不存储数据
 * 4) 根结点一定都是黑色
 * 5) 每个节点,从该节点到达其可达叶子节点的所有路径,都包含相同数目的黑色节点
 *
 * @author zw
 * @create 2023-04-09 18:00
 */
public class MyRedBlackTree<T extends Comparable<T>> {

    // nil表示的是叶子结点
    private final MyTreeNode nil = new MyTreeNode(-1);
    private MyTreeNode root = nil;
    private final int R = 0; // 红色
    private final int B = 1; // 黑色

    /**
     * 插入
     *
     * @param node
     */
    private void insert(MyTreeNode<T> node) {
        node.left = nil;
        node.right = nil;
        MyTreeNode temp = root;
        // 插入结点染色情况
        //1.如果插入前是空树,那么新插入的元素就会成为根节点,根据特征,需要将根节点染成黑色。
        if (root == nil) {
            root = node;
            node.color = B;
            node.parent = nil;
        }
        //2.如果红黑树非空,那么在红黑树中插入新的结点时,所有的点都默认是红色结点
        else {
            node.color = R;  // 插入节点默认为红
            //System.out.println(String.format("插入%s:修正前打印树",node.data));
            //root.show();
            insert(root, node);
            fixTree(node); // 修正树
            System.out.println(String.format("插入%s:修正后打印树", node.data));
            root.show();
        }
    }

    /**
     * 二叉搜索树的插入
     *
     * @param node
     * @param insertNode
     */
    public void insert(MyTreeNode node, MyTreeNode insertNode) {
        if (node.lt(insertNode.data)) {
            if (node.right != nil) {
                insert(node.right, insertNode);
            } else {
                insertNode.parent = node;
                node.right = insertNode;
            }
        } else {
            if (node.left != nil) {
                insert(node.left, insertNode);
            } else {
                insertNode.parent = node;
                node.left = insertNode;
            }
        }
    }

    /**
     * 插入结点后调整和平衡过程
     * @param node
     */
    private void fixTree(MyTreeNode node) {
        // 1.变颜色的情况: 当前结点的父亲是红色,且它的祖父结点的另一个结点(也就是叔叔结点)也是红色:
        while (node.parent.color == R) { // 父节点为红色
            // 颜色变化
            if (colorChange(node)) continue; // 叔叔节点为黑色才不会执行continue
            // 左旋
            rotateLeftFixTree(node);
            // 右旋
            rotateRightFixTree(node);
        }
    }

    /**
     * 右旋转修复树的情况
     *
     * @param node
     */
    private void rotateRightFixTree(MyTreeNode node) {
        if (node.parent.color == R && node.uncleNode().color == B) { // 父红叔黑
            if (node.parent.isLeftNode() && node.isLeftNode()) { // 父左子左
                node.parent.color = B; // 把父结点设为黑色
                node.parent.parent.color = R; // 把爷爷结点设为红色
                rotateRight(node.parent.parent); // 以爷爷结点进行右旋
            }
            if (node.parent.isRightNode() && node == node.parent.right) { // 父右子左
                node = node.parent;
                rotateRight(node); //右旋
            }
        }
    }

    /**
     * 左旋转修复树的情况
     *
     * @param node
     */
    private void rotateLeftFixTree(MyTreeNode node) {
        if (node.parent.color == R && node.uncleNode().color == B) { // 父红叔黑
            if (node.parent.isLeftNode() && node.isRightNode()) { // 父左子右
                node = node.parent; // 自底向上
                rotateLeft(node); // 左旋
            }
            if (node.parent.isRightNode() && node == node.parent.right) { // 父右子右
                node.parent.color = B;  // 父亲设置为黑
                node.parent.parent.color = R; // 爷爷设置为红
                rotateLeft(node.parent.parent); // 以爷爷结点进行左边旋
            }
        }
    }

    /**
     * 变色
     *
     * @param node
     * @return
     */
    private boolean colorChange(MyTreeNode node) {
        MyTreeNode uncleNode = node.uncleNode(); // 叔叔节点
        // 叔叔节点也为红色的情况:
        if (node.parent.color == R &&
                uncleNode != nil &&
                uncleNode.color == R) {
            // 把父结点设为黑色
            node.parent.color = B;
            // 把叔叔结点也设为黑色
            uncleNode.color = B;
            // 爷爷节点设置为红色,如果爷爷为根颜色为黑
            if (node.parent.parent.parent != nil) {
                node.parent.parent.color = R;
            }
            // 把指针定义到爷爷结点结点设为当前要操作的结点
            node = node.parent.parent;
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    /**
     * 向左旋转,两种情况
     * 1、左旋转为根节点
     * 2、左旋转为有父节点
     *
     * @param node
     */
    void rotateLeft(MyTreeNode node) {
        // 1、左旋转为根节点
        //    node
        // left  right
        //      rl
        if (node.parent == null) {
            MyTreeNode oldRightLeft = node.right.left;
            // 1.1  node右节点上浮,node下沉
            root = node.right;
            node.right.parent = null;
            root.left = node;
            node.parent = root;
            // 1.2 将左旋节点node右子树的左子树,变为node的右子树
            if (oldRightLeft != nil) {
                oldRightLeft.parent = node;
            }
            node.right = oldRightLeft;
            oldRightLeft.parent = node;
        }
        // 2、左旋转为有父节点
        else {
            MyTreeNode oldParent = node.parent;
            // 2.1 当前节点为左节点
            //      oldParent
            //    node
            // left  right
            //      rl
            if (oldParent.left == node) {
                oldParent.left = node.right; // 上浮
                node.right.parent = oldParent;
                oldParent.left.left = node; // node下沉
                node.parent = oldParent.left;
            }
            // 2.1 当前节点为右节点
            //  oldParent
            //         node
            //      left   right
            //            rl
            else {
                oldParent.right = node.left; // 上浮
                node.left.parent = oldParent;
                oldParent.right.left = node;  // node下沉
                node.parent = oldParent.right;
            }
            // 2.3 node右侧左子树,挂成node右子树
            if (node.right.left != nil) {
                node.right.left.parent = node;
            }
            node.right = node.right.left;
            node.parent.left = node;
        }


    }

    /**
     * 向右旋转
     * 1、左旋转为根节点
     * 2、左旋转为有父节点
     *
     * @param node
     */
    void rotateRight(MyTreeNode node) {
        MyTreeNode oldLeftRight = node.left.right;
        // 1、右旋转节点为根节点
        //    node
        // left  right
        //    lr
        if (node.parent == null) {
            // nodo 左侧上浮,右侧下沉
            root = node.left;
            node.left.parent = null;
            root.right = node;
            node.parent = root;
            // lr
            if (oldLeftRight != nil) {
                oldLeftRight.parent = node;
            }
            node.left = oldLeftRight;
            oldLeftRight.parent = node;
        }
        // 2、右旋转节点不为根节点
        else {
            MyTreeNode oldParent = node.parent;
            MyTreeNode rotationNode = node;
            // 2.1 当前节点为左节点
            //      oldParent
            //    node
            // left  right
            //    lr
            if (node.parent.left == node) {
                oldParent.left = node.left; // 上浮
                node.left.parent = oldParent;
                oldParent.left.right = node;  // node下沉
                node.parent = oldParent.right;
            }
            // 2.1 当前节点为右节点
            // oldParent
            //        node
            //     left  right
            //         lr
            else {
                oldParent.right = node.left; // 上浮
                node.left.parent = oldParent;
                oldParent.right.right = node; // node下沉
                node.parent = oldParent.right;
            }
        }
        // lr变为node左子节点
        if (oldLeftRight != nil) {
            oldLeftRight.parent = node;
        }
        node.left = oldLeftRight;
    }
}

测试用例

public void printTree(MyTreeNode node) {
    if (node == nil) {
        return;
    }
    printTree(node.left);
    System.out.print(node.toString());
    printTree(node.right);
}
public static void main(String[] args) {
    MyRedBlackTree<Integer> redBlackTree = new MyRedBlackTree<>();
    int data[] = {53, 34, 80, 18, 46, 74, 88, 17, 33, 50, 72};
    System.out.println(Arrays.toString(data));
    for (int i = 0; i < data.length; i++) {
        MyTreeNode node = new MyTreeNode<Integer>(data[i]);
        redBlackTree.insert(node);
    }
    redBlackTree.printTree(redBlackTree.root);
}

运行结果

打印结果中|后跟节点颜色,0红1黑

[53, 34, 80, 18, 46, 74, 88, 17, 33, 50, 72]
插入34:修正后打印树
      53|1   
    /   \    
  34|0    -1|0
 / \         
-1|0-1|0     
插入80:修正后打印树
      53|1   
    /   \    
  34|0    80|0
 / \     / \ 
-1|0-1|0-1|0-1|0
插入18:修正后打印树
            53|1         
         /     \         
      34|1        80|1   
    /   \       /   \    
  18|0    -1|0-1|0    -1|0
 / \                     
-1|0-1|0                 
插入46:修正后打印树
            53|1         
         /     \         
      34|1        80|1   
    /   \       /   \    
  18|0    46|0-1|0    -1|0
 / \     / \             
-1|0-1|0-1|0-1|0         
插入74:修正后打印树
            53|1         
         /     \         
      34|1        80|1   
    /   \       /   \    
  18|0    46|074|0    -1|0
 / \     / \ / \         
-1|0-1|0-1|0-1|0-1|0     
插入88:修正后打印树
            53|1         
         /     \         
      34|1        80|1   
    /   \       /   \    
  18|0    46|074|0    88|0
 / \     / \ / \     / \ 
-1|0-1|0-1|0-1|0-1|0-1|0-1|0
插入17:修正后打印树
                        53|1                     
                    /       \                    
                34|0            80|1             
             /     \         /     \             
          18|1        46|174|0        88|0       
        /   \       /   /   \       /   \        
      17|0    -1|0-1|0-1|0-1|0-1|0-1|0    -1|0   
     / \                                         
    -1|0-1|0                                     
插入33:修正后打印树
                        53|1                     
                    /       \                    
                34|0            80|1             
             /     \         /     \             
          18|1        46|174|0        88|0       
        /   \       /   /   \       /   \        
      17|0    33|0-1|0-1|0-1|0-1|0-1|0    -1|0   
     / \     / \                                 
    -1|0-1|0-1|0-1|0                             
插入50:修正后打印树
                        53|1                     
                    /       \                    
                34|0            80|1             
             /     \         /     \             
          18|1        46|174|0        88|0       
        /   \       /   /   \       /   \        
      17|0    33|0-1|0-1|050|0-1|0-1|0    -1|0   
     / \     / \         / \                     
    -1|0-1|0-1|0-1|0    -1|0-1|0                 
插入72:修正后打印树
                        53|1                     
                    /       \                    
                34|0            80|0             
             /     \         /     \             
          18|1        46|174|1        88|1       
        /   \       /   /   \       /   \        
      17|0    33|0-1|072|050|0-1|0-1|0    -1|0   
     / \     / \     / \ / \                     
    -1|0-1|0-1|0-1|0-1|0-1|0-1|0     

Node [color=0, left=-1, right=-1, parent=18]
Node [color=1, left=17, right=33, parent=34]
Node [color=0, left=-1, right=-1, parent=18]
Node [color=0, left=18, right=46, parent=53]
Node [color=1, left=-1, right=50, parent=34]
Node [color=0, left=-1, right=-1, parent=46]
Node [color=1, left=34, right=80, parent=-1]
Node [color=0, left=-1, right=-1, parent=74]
Node [color=1, left=72, right=-1, parent=80]
Node [color=0, left=74, right=88, parent=53]
Node [color=1, left=-1, right=-1, parent=80]

演示插入过程验证正确性

image.png
演示网站:演示插入过程
image.png->image.png->image.png->image.png->image.png->image.png->image.png->image.png->image.png->image.png

红黑树的删除

这不提供删除代码,了解即可,有兴趣的同学可根据如下文件提供思路进行实现
红黑树的删除.docx

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值