算法第四版 红黑树笔记

前言

红黑树是一种二叉查找树,二叉查找树就不再赘述,分析性能时需要研究最差性能,一般的二叉查找树有时会退化成线性表,比如顺序插入时。那么就需要保证二叉查找树的平衡性,比如AVL树,但是要严格保证这种平衡需要的代价太高了,由此我们先引出2-3查找树。

2-3查找树

定义:

  • 2-结点, 含有一个键(及对应的值)和两条链接,左链接指向的2-3子树中的键都小于该结点,右链接相反。
  • 3-结点,含有两个键(以及对应的值)和三条链接,指向的子树与2-结点类似。
2-3查找树

2-3树的查找就不用多说了,叙述一下如何插入。

2-3查找树的插入

插入总是在叶子结点发生,包括两种情况,插入的结点为2-结点或者3-结点,如果是前者,直接插入即可,如果为后者,先暂时形成4-结点,然后转换为3个2-结点。

3-结点的插入过程

此时又分两种情况,插入结点的父节点为2-结点或者3-结点,如果是前者,将转换形成的父结点插入之前的父结点。

父结点为2-结点的插入

如果父结点为3-结点,重复结点为2-结点的过程,那么父结点就会形成4结点,转换为3-结点,重复已上过程,直到不再形成4-结点,若直到根结点还是4-结点,将之转换为3个2-结点,此时树高加一。

父结点为3-结点的插入

将一个4-结点分解为2-3树可能有6中情况,此处不再赘述,可参考书中P273。

全局性质:

  • 局部变换不会影响全局有序性和平衡性:任意空链接到根结点的路径长度都是相等的。
  • 2-3查找树的增长是自下向上的。

红黑树

前文所述的2-3查找树实现起来需要维护两种不同类型的结点,实现操作并不统一,情况太多,比较麻烦。为此,我们需要“红黑二叉查找树”的数据结构来实现它。

基本思想

用标准的二叉查找树(完全由2-结点构成)和一些额外的信息(替换3-结点)来表示2-3树。我们讲树中的链接分为两种类型:红链接将两个2-结点连接起来构成一个3-结点,黑链接则是2-3树中的普通链接。确切的说,我们讲3-结点表示为由一条左斜的红色链接相连的两个2-结点。

3-结点在红黑树中的表示
  • 红链接均为左连接
  • 没有任何一个结点同时和两条红链接相连
  • 该树是完美黑色平衡的
Node定义
private static final boolean RED = true;
private static final boolean BLACK = false;

private class Node {
    Key key;                 //键
    Value val;               //值
    Node left, right;      //左右链接
    int N;                      //这颗子树中的结点总数
    boolean color;       //由父结点指向它的链接的颜色

    public Node(Key key, Value val, int N, boolean color) {
        this.key = key;
        this.val = val;
        this.N = N;
        this.color = color;
    } 
}

private boolean isRed(Node x) {
    if (x == null) return false;
    return x.color == RED;
}
转换操作

我们需要这些操作来保持红黑树的两个重要性质, 不存在两条连续的红链接和红色的右链接。


转换操作
2-结点插入

我们向2-3树中插入新键时,总是插入到已经存在的叶子结点,或者是2-结点,或者是3-结点,所以在红黑树中新插入的结点总是红色的。
对于2-结点的插入直接形成3-结点,新键对于原有键或左边或右边。对应于红黑树,新键成为3-结点中原有键的左子树或者右子树,其中右子树的情况需要调整,因为我们规定只存在左斜的红色链接,用上文转换操作中的rotateLeft(Node h)即可

3-结点插入

3-结点的插入有3中情况,左中右。对应于红黑树中,会有一下这几种情况:

3结点插入的各种情况已经如何转换
插入实现
public class RedBlackBST<Key extends Comparable<Key>, Value> {
    private Node root;
    private class Node //见前文
    
    private boolean isRed(Node h) //见前文
    private Node rotateLeft(Node h)
    private Node rotateRight(Node h)
    private void flipColors(Node h)

    private int size() {
        return size(root);
    }
    
    private int size(Node h) {
        if (h == null) return 0;
        return h.N;
    }

    public void put(Key key, Value val) {
        //查找key, 找到则更新其值,否则为它新建一个结点
        root = put(root, key, val);
        root.color = BLACK;
    }

    private Node put(Node h, Key key, Value val) {
        if (h == null) {
            //没找到,新键结点,注意color为RED
            return new Node(key, val, 1, RED);
        }

        int cmp = key.compareTo(h.key);
        if (cmp < 0) h.left = put(h.left, key, val);
        else if (cmp > 0) h.right = put(h.right, key, val);
        else h.val = val;
    }

    //2-结点右插入以及3-结点中插入的调整
    if (isRed(h.right) && !isRed(h.left)) h = rotateLeft(h);
    
    //3-结点左插入的调整
    if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h);

    //3-结点右插入以及3-结点左插入调整后的
    if (isRed(h.left) && isRed(h.right)) flipColors(h);

    h.N = size(h.left) + size(h.right) + 1;
    return h;
}
红黑树的删除

首先,需要说明的是,2-3树是完美平衡的,即就是说,非叶子结点不存在空链接。
我们先来看看怎么实现deleteMin():
刚才说过非叶子结点不存在空链接,如果我们删除的2-结点,那么就麻烦了,不能直接删除,为什么?因为2-3树中最小的点是在左下角,如果这个结点是2-结点,直接删除的话,它的父结点会出现空链接,破坏了2-3树的完美平衡性。那么该怎么办?如果这个结点是3-结点,那么我直接删除就好了,所以我们可不可能把2-结点转换为3-结点?所以,在沿着左连接向下的过程中:

  • 如果当前结点的左子结点不是2-结点,完成;
  • 如果当前结点的左子结点是2-结点而它的亲兄弟结点不是2-结点,将左子结点的兄弟结点中的一个键移动到左子结点中(其实是将它移动到父亲节点,而父亲结点的键移动到左子结点);
  • 如果当前结点的左子结点和它的亲兄弟结点都是2-结点,将它们三个合并为一个临时的4-结点,使父结点由3-结点变成2-结点或者由4-结点变成3-结点。(由上向下的过程我们允许临时生成4-结点,因为随后的由下向上的过程会分解)
deleteMin()实现
public Node deleteMin() {
    if (!isRed(root.left) && !isRed(root.right)) {
        root.color = RED;
    }
    root = deleteMin(root)
    if (!isEmpty()) root.color = BLACK;
}

private Node deleteMin(Node h) {
    if (h.left == null) 
        return null;   //找到了最小的,删除

    //如果当前结点不是3-结点并且它的左孩子不是一个3-结点,需要调整
    if (!isRed(h.left) && !isRed(h.left.left))
        h = moveRedLeft(h);

    //递归删除
    h.left = deleteMin(h.left);

    //借键以及融合过程中产生连续的以及右斜的红链接需要调整
    return balance(h);
}

private Node moveRedLeft(Node h) {
    //融合当前结点,左孩子,右孩子为新的4-结点
    flipColors(h);
    //如果右孩子是3-结点,这个时候需要上述的借键给左孩子
    if (isRed(h.right.left)) {
        h.right = rotateRight(h.right);
        h.rotateLeft(h);
    }
    return h;
}

private Node balance(Node h) {
    //右斜红链接调整
    if (isRed(h.right))    h = rotateLeft(h);
    //连续的红链接调整
    if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h)
    //4-结点调整
    if ()isRed(h.left && isRed(h.right)) flipColors(h);

    return h;
}
deleteMax()实现

删除最大键的过程与删除最小键的过程类似,但有一点需要注意,树中红色链接都是左斜的。如果最大键在3-结点,不能直接删除,rotateRight(h)处理一下。我们可以在自上向下的过程中将根结点右子树的红链接右斜,然后递归返回时自下向上的修正即可。

public void deleteMax() {
    if (!isRed(root.left) && !isRed(root.right)) {
        root.color = RED;
    }

    root = deleteMax(root);
    if (!isEmpty()) root.color = BLACK;
}

private Node deleteMax(Node h) {
    //h的红链接右斜
    if (isRed(h.left))
        h = rotateRight(h);
    if (h.right == null)
        return null;
    //h的红链接右斜,右孩子的红链接还是左斜的(如果有)
    if (!isRed(h.right) && !isRed(h.right.left)) 
        h = moveRedRight(h);
    h.right = deleteMax(h.right);
    return balance(h);
}

private Node moveRedRight(Node h) {
    flipColors(h);
    if (!isRed(h.left.left))
        h = rotateRight(h);
    return h;
}
delete()实现

在查找路径上进行与删除最小键相同的变换可以保证在查找过程中任意当前结点均不是2-结点。如果被查找的键在树的底部,可以直接删除。如果不在,需要将它与它的后继结点(即它的右子树的最小键)交换,因为是递归调用,删除之后回溯分解产生的不合法的红色链接。

public void delete(Key key) {
    if (!isRed(root.left) && !isRed(root.right)) 
        root.color = RED;
    root = delete(root, key);
    if (!isEmpty()) root.color = BLACK;
}

public 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 {
        //保证链接右斜,因为删除键在3-结点中的右边时需要调整
        if (isRed(h.left))
            h = rotateRight(h);
        //找到了,并且在叶子结点可以直接删除,在向下查找的过程中,已经保证了结点不可能是2-结点
        //只需要检查右孩子即可,因为如果左孩子非空,右孩子空只可能是3-结点,然而3-结点的红链接已经右斜了
        if (key.compareTo(h.key) == 0 && (h.right == null))
            return null;
        //右子树遍历时保证结点非2-结点
        if (!isRed(h.right) && !isRed(h.right.left)) 
            h = moveRedRIght(h);
        //找到了,将结点替换为后继结点
        if (key.compareTo(h.key) == 0) {
            Node x = min(h.right);
            h.key = x.key;
            h.val = x.val;
            h.right = deleteMin(h.right);
        }
        没找到,继续在右子树找
        else h.right = delete(h.right, key);
    }
    
    //处理非法红色链接
    return balance(h);
}

private Node min(Node h) {
        if (h == null) return h;
        else return min(h.left);
}
性能

红黑树的查找,插入均在log(n)级别

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

推荐阅读更多精彩内容

  • 数据结构与算法--从平衡二叉树(AVL)到红黑树 上节学习了二叉查找树。算法的性能取决于树的形状,而树的形状取决于...
    sunhaiyu阅读 7,637评论 4 32
  • 一. 算法之变,结构为宗 计算机在很多情况下被应用于检索数据,比如航空和铁路运输业的航班信息和列车时刻表的查询,都...
    Leesper阅读 6,826评论 13 42
  • B树的定义 一棵m阶的B树满足下列条件: 树中每个结点至多有m个孩子。 除根结点和叶子结点外,其它每个结点至少有m...
    文档随手记阅读 13,144评论 0 25
  • 原文链接 二叉查找树 由于红黑树本质上就是一棵二叉查找树,所以在了解红黑树之前,咱们先来看下二叉查找树。 二叉查找...
    非典型程序员阅读 2,818评论 2 5
  • B树 1.前言: 动态查找树主要有:二叉查找树(Binary Search Tree),平衡二叉查找树(Balan...
    铁甲依然在_978f阅读 1,443评论 0 4