红黑树详解

二叉查找树

此为《算法 第四版》笔记

二叉查找树是一颗有序的二叉树, 它的每一个结点的键都大于它左子树的根结点, 小于它右子树的根结点
二叉查找树
所以我们在查找某个结点时可以向二分查找一样, 递归执行, 我们查找的键的值若大于根结点, 则访问它的右子树, 若小于根结点, 则访问它的左子树, 这样查找的效率和二分查找一样, 平均只需要 O ( l o g n ) O(logn) O(logn)的时间
但是, 二叉查找树有一个最糟糕的情况, 就是我们在构建二叉查找树的时候每次插入的都时最大值或者最小值, 这样我们通过这个数查找时就是线性时间了
不平衡的二叉查找树
所以说, 二叉查找树的性能是不稳定的

2-3查找树

为了保证查找树的平衡性, 我们需要一些灵活性, 因此在这里我们允许树中的一个结点保存多个键, 我们称保存了两个键的结点为3-结点(其有三棵子树), 和原来一样只有一个键的结点为2-结点(有两棵子树)
在这里插入图片描述
上图是一颗完美平衡的2-3查找树, 而且我们在插入或删除键的时候可以通过一些操作始终保持这棵树的平衡
当我们插入一个结点是, 若它的父结点是2-结点, 则我们可以将这个结点和它的父结点合并为一个3-结点, 这样是的高度就不会改变, 若它的父结点为3-结点, 我们可以将它和它的父结点合并为一个临时的4-结点, 然后将这个4-结点分解为一个2-结点和它的两个子2-结点, 这样虽然树的高度增加了 1 1 1, 但是它依然是平衡的

在这里插入图片描述
在这里插入图片描述

红黑树

2-3查找树完美地避免了二叉查找树查找时的最坏情况, 但是它有两种不同的结点, 是否就意味着我们定义结构体或者类时需要定义两种? 通过红黑树, 我们其实只需要定义和以前二叉查找树一样的一种结点就行了
红黑树是怎么用2-结点取代3-结点的呢? 它定义了两种结点之间的连接, 它把原来的3-结点定义为了两个2-结点的结合, 用红链接相连接, 而2-结点之间还有2-结点与3-结点则用黑链接相连接.
在这里插入图片描述

红黑树的定义是含有红黑连接并满足以下条件的二叉查找树:

  1. 红链接均为左链接
  2. 没有任何一个结点同时和两个红链接相连
  3. 该树是完美黑色平衡的, 即任意空节点带根结点的路径上的黑链接数量相同
    这样我们可以定义红黑树的结点(Java)
    private class Node {
		int value; 		// 结点的值
        Node left; 		// 左子树
        Node right; 	// 右子树
        int N;			// 这棵树中结点的总数
        boolean color;	// 由父结点指向它的链接的颜色 

        public Node(Key key, Value value, int n) {
            this.key = key;
            this.value = value;
            N = n;
        }
    }

链接颜色color为:

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

旋转

在操作红黑树时, 我们有时会出现一个结点的右链接是红链接, 这时我们需要通过旋转来解决
在这里插入图片描述

染色

有时候我也没也会遇上一个结点有两个红链接, 这时我们需要将这两个红链接变黑, 但是这样就造成了一个问题: 这棵树的高度会加 1 1 1, 从而导致它比它父亲的另一颗子树要高, 所以我们需要将这棵树的根结点和它的父亲之间的链接染红, 这样高度就平衡了.

插入

红黑树和2-3查找树是等价的, 所以我们可以用2-3查找树来思考一些红黑树的问题
对于插入操作, 我们可以分为两种, 一种是在2-结点后插入, 一种是在3-结点后插入
在2-结点后插入很简单, 我们将被插入结点和该2-结点合并为一个3-结点就好, 也就是用红链接将这两个结点相连接起来, 然后判断是否需要旋转操作就好了
对于在3-结点后插入, 我们会的到一个临时的4-结点, 此时我们可以通过左旋或者右旋, 使得中间结点的左右链接都为红链接, 再进行染色操作就可以了
在这里插入图片描述附上代码

  public void put(Key key, Value value) {
        root = put(root, key, value);
        root.color = BLACK;
  }

  private Node put(Node h, Key key, Value value) {
        if (h == null) {
            return new Node(key, value, 1);
        }
        int cmp = key.compareTo(h.key);
        if (cmp < 0) {
            h.left = put(h.left, key, value);
        } else if (cmp > 0) {
            h.right = put(h.right, key, value);
        } else {
            h.value = value;
        }

        // 红黑树结构调整
        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.N = size(h.left) + size(h.right) + 1;
        return h;
   }

删除

首先来考虑删除最小键, 我们知道最小键肯定在树的左下角, 一定是树的叶子结点, 我们同样将其分为两种情况:
若该节点为3-结点, 很简单, 直接删掉
若其为2-结点, 那么我们就要其构造成3-结点了, 如果它的兄弟结点为3-结点, 那么我们可以从它的兄弟那里"借"一个结点过来, 构成一个3-结点, 然后轻松删掉. 如果它和它的兄弟都是2-结点, 我们可以将他们两个和其父亲最小键的结点一起构成4-结点, 然后删掉该结点
此时我们要注意, 只有当父结点不为2-结点时, 经过删除操作后才能保持树的高度不变, 所以我们需要提前将该节点的父结点构建成3-结点或者临时的4-结点, 关于这个, 我们可以从这棵树的根结点开始, 将根结点和它的两个子节点一起构成4-结点, 然后将该4-结点最小键的结点往下移, 是的其最左边子节点构成3-结点或4-结点, 一直递归下去, 这样就能使得该树最小键的结点不是2-结点了, 同属由于我们是从根结点开始变化的, 所以它依然是平衡的
在这里插入图片描述附上代码:

    private Node moveRedLeft(Node h) {
        flipColors(h);
        if (isRed(h.right.left)) {
            h.right = rotateRight(h.right);
            h = rotateLeft(h);
        }
        return h;
    }

    private Node deleteMin(Node h) {
        if (h.left == null) {
            return h.right;
        }
        if (!isRed(h)) {
            h = moveRedLeft(h);
        }
        h.left = deleteMin(h.left);
        h.N = size(h.left) + size(h.right) + 1;
        return balance(h);
    }
    
    public void deleteMin() {
        if (!isRed(root.left) && !isRed(root.right)) {
            root.color = RED;
        }
        root = deleteMin(root);
        if (root != null) {
            root.color = BLACK;
        }
    }

对于删除操作, 如果该节点在树的底部, 我们就直接删了它, 如果不在, 我们就将它和它的后继节点向交换, 知道它在底部, 再删了它

    public void delete(Key key) {
        if (!isRed(root.left) && !isRed(root.right)) {
            root.color = RED;
        }
        root = delete(root, key);
        if (root != null) {
            root.color = BLACK;
        }
    }
    
    private Node delete(Node h, Key key) {
        if (key.compareTo(h.key) < 0) {
            if (!isRed(h.left) && !isRed(h.left.left)) {
                h = moveRedLeft(h);
                h.left = delete(h.left, key);
            }
        } else {
            if (isRed(h.left)) {
                h = rotateRight(h);
            }
            if (key.compareTo(h.key) == 0 && (h.right == null)) {
                return null;
            }
            if (!isRed(h.right) && !isRed(h.right.right)) {
                h = moveRedRight(h);
            }
            if (key.compareTo(h.key) == 0) {
                h.value = get(h.right, min(h.right).key);
                h.key = min(h.right).key;
                h.right = deleteMin(h.right);
            } else {
                h.right = delete(h.right, key);
            }
        }
        return balance(h);
    }
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值