实现红黑树(已leetcode验证)

代码已在leetcode验证了。是正确的,内存稍微多一些,时间上要好一些。大家可以看看下提交记录中的内存和时间差异。
其他两种平衡二叉树参考 [[AVL 树]] [[跳表]]

1.png
2.png
3.png

在红黑树的学习过程中,我接触到两种红黑树的描述,一种是左偏红黑树,另一种是算法导论中一般意义上的红黑树。前者是红黑树的简化版本。本偏只是原理和实现,没有说应用。

红黑树的左偏和右偏版本

左偏红黑树被设计为和一种叫2-3树的等价树,左偏红黑树是2-3树的另一种结构表现形式。
这很好,因为2-3树是绝对平衡的二叉搜索树!
2-3树的内部节点只有两种,一种只存一个节点,两个孩子,叫二节点
另一种则有两个节点,三个孩子,叫三节点。
当插入一个元素的时候,会按照二叉搜索树性质把元素合并到叶子节点。当叶子节点只有一个元素时,合并。当有两个元素时,中间的元素会向上传递,与父节点合并,父亲节点有一个或两个节点,分情况重复在叶子节点的合并过程,知道合并到达跟节点,或者生成新的根节点。
那么左偏红黑树是如何与2-3树等价的呢?
很简单,对2-3树的三节点做些等价表变换,将左边节点变成右边节点的左孩子
你如果反着来,那就叫做右偏树。
不难验证,这样变换后的树是复合红黑树定义的。
以红黑树的黑平衡为例,2-3树的绝对平衡可推出黑色节点的平衡。其他性质稍加思考不难验证其正确性,这里不再赘述。
同理,红黑树的插入和删除的等价于2-3树的插入删除等价操作。具体不展开。不是本文的重点,本文的重点是实现一般意义上的红黑树。

算法导论中的红黑树

有人说,导论中的红黑树也等价于一棵什么什么树,我觉得不是很重要,因为算法导论讲的已经相当好了。在这里也只是复述一些关键节点,从而能写出正确的代码。本文中的代码在leetcode 设计跳表中验证通过,可靠性比较高。

我们不去关心红黑树怎么来的,这是一种设计。因为这么设计,可以达到黑平衡,可以有logn的时间复杂度,插入删除也比较高效。红黑树定义请大家移步算法导论。
特别的,红黑树的叶子节点是黑色空节点,具体在定义的时候,所有叶子节点统一指向同一个哨兵节点
根节点的父节点也指向哨兵节点,所以程序初始化时候,0号点为哨兵的话,那0号先染黑

红黑树和二叉搜索树一样,具有查找,增加,删除元素的接口。我们允许保存重复元素。哦,原来增删改查已经很难了,不明白为什么有人觉得CRUD无脑。

关于查找,递归循环都可以,与二叉树实现一致。难的是增加和删除。

如何分情况讨论,把事情讲清楚,是红黑树算法的核心。

在开始之前,我们先来说两种操作,旋转和颜色反转。

红黑树的左旋和右旋,与其他平衡树的旋转是一样的,它们不改变其二叉搜索树的性质,只不过会打破红黑树的性质。后边说。
颜色反转是什么,考虑一个节点是黑色的,两个孩子节点是红色的,我们把孩子染成黑色,父亲是红色,这就是颜色反转,有什么作用呢,可以看下算法导论。我后边也可能会有蛛丝马迹。

进入正题。我建议下面对着算法导论的图看,或者,直接看算法导论也挺好的。

插入元素

首先按照二叉搜索树的插入在某个空子树插入一个节点,我们约定,新插入的节点都是红色的。
好了,问问题的本能被激发了。我新来的善良的节点扪心自问,我会不会破坏红黑树性质?
原来那棵树,有可能是空的
我把自己染成黑色,自己变成根节点,没问题
就算原来那树不是空的,我一定有父节点,那我的父节点是黑的的怎么办?
黑的就黑的,也不用调整了。
那父节点是红的呢?
这就麻烦了,需要分情况调整了,要么把红的移到叔叔那个家族里去,前提是它们能接纳的话。不然只能往祖宗传了,让爷爷的叔叔解决。
我们只讨论父亲节点是左子树的情况,另一种情况是对称的
我们分三种情况讨论:

  1. 叔叔节点是红色的
    把爷爷和它的两个孩子颜色反转,把锅甩给爷爷,意思就是爸爸叔叔都没事了,爷爷你和你的爸爸叔叔商量吧,好了,下个迭代看爷爷的了。

  2. 叔叔节点是黑色的,我是右孩子
    以父节点为轴左旋一次。转换为情况3。

  3. 叔叔节点是黑色的,我是左孩子
    以爷爷节点为轴,向右旋转一次,并且把爸爸染成爷爷的颜色,爷爷变红了,爷爷变成了爸爸的儿子,红色成功被转嫁给叔叔,哈哈哈,新的叔叔,原来的爷爷,爷爷年轻了50岁,我有种莫名的自责,为什么要用这样的比喻,罪过罪过。

父节点是右孩子的情况与上述情况对称。

删除元素

首先按照二叉树删除方式删除一个元素,记录下来填补删除空缺的元素的索引(你不是用数组模拟的就叫指针)。

二叉树的删除还记得吗,
就是首先找到那个元素
如果有一个子树是空的,就让非空儿子替代它,孩子都是空的就直接删除就行了
如果都不是空的,就找右子树最左节点,也就是后继节点,删除后拿过来替代被删除的元素。
好了,问题来了,不妨设被删除的元素是颜色是 c1,替代它的是c2。
c1 是红色的,删了就删了,红的我们希望越少越好。
要是c1是黑的,那平衡大概率会被打破。除非c1是根节点。
我们不妨把这节点删除了,把黑色留下来被替代它的节点继承,这样的树依然黑平衡。来研究一下这个具备二重颜色的特殊节点吧。
这个节点是根节点就好了,去掉一层不改变红黑树的特性。
如果不是根节点。那就麻烦了,我们分情况讨论。我们设这个节点为x
x是个红黑的,直接染成黑色的完事。
x是个双重黑的,那我们看能不能让兄弟挤一挤拿出1重黑上交父亲,我也能如释重负,让这两种黑锅给爸爸背吧,爸爸再和叔叔商量,最终总能解决掉的不是吗,根祖宗什么事都能给办了。要是可以,一百重黑在根祖宗哪里也轻松拿掉99层没任何压力,也不用和谁商量。

同样只考虑x为左孩子的情况,右孩子的情况是对称的。

  1. 兄弟节点是红色的
    以父节点为轴,左旋一次,往这边派了一个红色当双重黑的爸爸,下次循环就黑色就被扔给这个红色的新爸爸,其实还是原来的爸爸,只不过爸爸的某个孙子变成了儿子了,这是左旋造成的。基本下次循环就处理结束。

  2. 兄弟节点是黑色的,兄弟的两个孩子也是黑的。
    兄弟上交一层黑色,这边也上交一层黑色,爸爸背负多一层黑色,就可以进入下次循环

  3. 兄弟节点是黑色的,左孩子是红色的,右孩子是黑色的
    以兄弟为轴右旋一次,右孩子变它爸(我兄弟)的颜色,兄弟变成右儿子,原来右孩子是黑色,所以把我原来的兄弟(现在的右儿子)染成红色就可以,这就转化为情况4

  4. 兄弟节点是黑色的,右孩子是红色的。
    以父节点为轴,左旋一次,兄弟的红色右孩子继承兄弟的黑色,兄弟变成了爸爸节点,夜色也继承了,爸爸节点被转到左边变成了红色,下次循环就承接了黑色(算法导论图直接染成黑色了,应该是多做了一步,我代码对于B的染色放到了下次循环做,是等价的)。

代码

class Skiplist {
public:

    static const int N = 50010;
    static const bool BLACK_NODE = true;
    static const bool RED_NODE = false;

	// 下面变量初始化是必须的,不然在leetcode会有各种各样的错误
    struct Node {
        int parent = 0, left = 0, right = 0;
        int key = -1;//, val;
        bool color = false;

        void init(int k, int v) {
            parent = 0, left = 0, right = 0; // 指向哨兵节点
            key = k; //, val = v;
            color = RED_NODE;
        }

    } tr[N];

    Skiplist() {
        tr[0].color = BLACK_NODE;
    }

    bool search(int target) {
        return find(target) != 0;
    }

    void add(int num) {
        add(num, num);
    }

    bool erase(int num) {
        return remove(num);
    }

    int root = 0, idx = 0;

// 启动垃圾回收机制
    int nodes[N], tt = -1;

// idx = 0 的节点是哥哨兵,根节点父节点和叶子节点都指向哨兵

// 注意基本操作当中维护颜色信息,以及父辈信息,因为在讨论的过程中需要区分叔节点的颜色
// 维护全局的根节点的信息

/* 原地调整
 x                y
  \      ->      /
    y           x
*/
    void leftRotate(int x) {
        int y = tr[x].right;

        // 连接 y 左子树和x
        tr[x].right = tr[y].left;
        if (tr[y].left) tr[tr[y].left].parent = x;


        // 连接 y 和 x 的父亲节点
        if (!tr[x].parent) root = y;
        else if ( tr[tr[x].parent].left == x) tr[tr[x].parent].left = y;
        else tr[tr[x].parent].right = y;

        // 对调 y 和 x
        tr[y].parent = tr[x].parent;
        tr[y].left = x;
        tr[x].parent = y;
    }

/*
      x           y
     /     ->      \
    y                x
*/
    void rightRotate(int x) {

        int y = tr[x].left;

        // 连接 y 的右子树和 x
        tr[x].left = tr[y].right;
        if (tr[y].right) tr[tr[y].right].parent = x;

        // 连接y与x的父亲节点
        if (!tr[x].parent) root = y;
        else if(tr[tr[x].parent].left == x) tr[tr[x].parent].left = y;
        else tr[tr[x].parent].right = y;

        // 对调 x 和 y
        tr[y].parent = tr[x].parent;
        tr[y].right = x;
        tr[x].parent = y;
    }

/*
      node(BLCAK)                     node(RED_NODE)
    /            \        ->        /           \
   l(RED_NODE)       r(RED_NODE)         l(BLACK_NODE)         r(BLACK_NODE)
*/
    int flipColor(int node) {
        int l = tr[node].left, r = tr[node].right;
        bool color = tr[node].color;
        tr[node].color = tr[l].color;
        tr[l].color = color;
        tr[r].color = color;
        return node;
    }

    int newNode(int k, int v) {
        if (tt < 0) nodes[++tt] = ++idx;
        int u = nodes[tt--];
        tr[u].init(k, v);
        return u;
    }

// 思考一个问题,为什么tt之前不会有u
    void gcNode(int u) {
        nodes[++tt] = u;
    }


// 现在讨论叔叔节点的红与黑
    void fixRBAfterAdd(int x) {
        while (tr[tr[x].parent].color == RED_NODE) {// 若冲突一定存在叔叔节点,哨兵节点也算,而且爷爷一定是黑的
            int parent = tr[x].parent;
            int pparent = tr[parent].parent;

            if (tr[pparent].left == tr[x].parent) {
                // 当x的父亲节点是祖父节点的左孩子时
                if (tr[tr[pparent].right].color == RED_NODE) {
                    // 情况1: x 的叔叔节点是红色的
                    flipColor(pparent);
                    x = pparent;
                } else {
                    if (tr[parent].right == x) {
                        // 情况2: x 的叔叔节点是黑色的,x 是父亲节点的右孩子, 转化为情况3来考虑
                        leftRotate(parent);// 保证旋转的时候不会跑到另一侧
                    }
                    // 情况3:x 的叔叔节点是黑色的, x 是父亲节点的左孩, 父亲节点旋转到叔叔一侧
                    tr[tr[pparent].left].color = BLACK_NODE;
                    tr[pparent].color = RED_NODE;
                    x = tr[pparent].left;
                    rightRotate(pparent);
                }

            } else {
                // 当x的父亲节点是祖父节点的右孩子时
                if (tr[tr[pparent].left].color == RED_NODE) {
                    // 情况1: x的大叔叔颜色是红色的
                    flipColor(pparent);
                    x = pparent;
                } else {
                    if (tr[parent].left == x) {
                        // 情况2:x 的大叔叔颜色是黑色的,x是父节点的左孩子
                        rightRotate(parent);
                    }
                    // 情况3: x 的大叔叔夜色是黑色的,x是父节点的右孩子
                    tr[tr[pparent].right].color = BLACK_NODE;
                    tr[pparent].color = RED_NODE;
                    leftRotate(pparent);
                    x = parent;// assert parent == tr[x].parent
                }
            }
        }
        tr[root].color = BLACK_NODE;
    }

// v 子树替代替代 u 子树
    void transplant(int u, int v) {
        if (!tr[u].parent) root = v;
        else if (tr[tr[u].parent].left == u) tr[tr[u].parent].left = v;
        else tr[tr[u].parent].right = v;
        tr[v].parent = tr[u].parent;
    }


    void add(int k, int v) {

        int y = 0;
        int x = root;
        while(x) {
            y = x;
            if (k < tr[x].key) x = tr[x].left;
            else x = tr[x].right; 
        }
    
        int u = newNode(k, v);
        tr[u].parent = y;
        if (!y)
            root = u;
        else if (k < tr[y].key)
            tr[y].left = u;
        else
            tr[y].right = u;

        // 当前根节点如果是红不被修复
        fixRBAfterAdd(u);
    }



// 返回最靠左的非零元素
    int minimum(int node) {
        int last = 0;
        int u = node;
        while(u) {
            last = u;
            u = tr[u].left;
        }
        return last;
    }


// x指向了一个双重黑色的节点
    void fixAfterRemove(int x) {
        //cc++;
        while (x != root && tr[x].color == BLACK_NODE) {
       
            if (x == tr[tr[x].parent].left) {
                // 双重黑色节点在左侧
                int w = tr[tr[x].parent].right;
                if (tr[w].color == RED_NODE) {
                    // 情况1: 兄弟节点w是红色的,毫无疑问,根据红黑树的性质w的两个孩子也是黑色的
                    tr[w].color = BLACK_NODE;
                    tr[tr[x].parent].color = RED_NODE; // w 是红色,所以x和w的父亲都是黑色
                    leftRotate(tr[x].parent);
                    x = tr[x].parent;
                    // w 不必重新求
                } else {
                    // 情况2: 兄弟节点w是黑色的,两个孩子也是黑色的
                    // 将x和w各抽离掉一层黑色往上传递, w抽掉黑色不影响黑平衡
                    if (tr[tr[w].left].color == BLACK_NODE && tr[tr[w].right].color == BLACK_NODE) {
                        tr[w].color = RED_NODE;
                        x = tr[x].parent;
                    }
                    // 情况3:兄弟节点w是黑色的,左孩子是红色,右孩子是黑色的
                    // w一侧匀过去一个红色节点承接一层黑色,w往上移动了一次,左侧少了一层黑色,所以希望w的原来的右孩子是红的
                    // 可以在旋转之前把w的原始左孩子旋转过去,让右孩子变成红色,也就是情况四
                    else if (tr[tr[w].right].color == BLACK_NODE) {
                        tr[tr[w].left].color = BLACK_NODE;
                        tr[w].color = RED_NODE;
                        rightRotate(w);
                    }

                        // 情况4:兄弟节点w是黑色的,右孩子是红色
                        // 匀到左侧一个节点,这边把顶替上来的红色染成黑色
                    else  {
                        tr[w].color = tr[tr[x].parent].color;
                        tr[tr[x].parent].color = RED_NODE;
                        tr[tr[w].right].color = BLACK_NODE;// 弥 w被抽离了黑色到,父亲节点颜色保持原状并无叠加
                        leftRotate(tr[x].parent);
                        x = tr[x].parent;
                    }
                }
            } else {
                // 双重黑色节点在左侧
                int w = tr[tr[x].parent].left;
                if (tr[w].color == RED_NODE) {
                    // 情况1:兄弟节点是w是红色的
                    tr[w].color = BLACK_NODE;
                    tr[tr[x].parent].color = RED_NODE;
                    rightRotate(tr[x].parent);
                    x = tr[x].parent;
                } else {
                    if (tr[tr[w].left].color == BLACK_NODE && tr[tr[w].right].color == BLACK_NODE) {
                        // 情况2:兄弟节点是w是黑色的,两个孩子节点都是黑色的
                        tr[w].color = RED_NODE;
                        x = tr[x].parent;
                    } else if (tr[tr[w].left].color == BLACK_NODE) {
                        // 情况3 : 兄弟节点的左孩子是黑色的
                        tr[tr[w].right].color = BLACK_NODE;
                        tr[w].color = RED_NODE;
                        leftRotate(w);
                    } else { 
                        tr[w].color = tr[tr[x].parent].color;
                        tr[tr[x].parent].color = RED_NODE;
                        tr[tr[w].left].color = BLACK_NODE;
                        rightRotate(tr[x].parent);
                        x = tr[x].parent;
                    }
                }
            }
        }
        tr[x].color = BLACK_NODE;
    }

    bool rbDelete(int z) {
        // 记录被移除节点的颜色, z有一个子树值是空就移除它本身
        bool yColor = tr[z].color;
        int y = z;
        int x;
        if (!tr[z].left) {
            x = tr[z].right;
            transplant(z, tr[z].right);
        } else if (!tr[z].right) {
            x = tr[z].left;
            transplant(z, tr[z].left);
        } else {
            // 找到 z 的后继节点, z 的左右孩子都不为空
            y = minimum(tr[z].right);
            yColor = tr[y].color;
            x = tr[y].right;// x 有可能是一个黑色的空节点,但是不影响
            yColor = tr[y].color;
            // 移花接木y,并且先接入y的右孩子
            if (tr[y].parent == z) {
                tr[x].parent = y; // 这是一句废话
            } else {
                transplant(y, tr[y].right);
                tr[y].right = tr[z].right;
                tr[tr[y].right].parent = y;
            }
            transplant(z, y);
            // 再接入y的左孩子
            tr[y].left = tr[z].left;
            tr[tr[y].left].parent = y;
            

            tr[y].color = tr[z].color;
            gcNode(z);
            // 接下来只需要考虑被移除节点的颜色即可 
            if (yColor == BLACK_NODE)
                fixAfterRemove(x);
        }
        return true;
    }


    int find(int k) {
        int u = root;
        while(u) {
            if (k < tr[u].key) u = tr[u].left;
            else if (k > tr[u].key) u = tr[u].right;
            else return u;
        }
        return 0;
    }

    bool remove(int k) {
        int z = find(k);
        if(z) return rbDelete(z);
        return false;
    }

};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值