前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >红黑树的构建

红黑树的构建

作者头像
theanarkh
发布2019-11-15 12:18:59
4770
发布2019-11-15 12:18:59
举报
文章被收录于专栏:原创分享原创分享
代码语言:javascript
复制
let sentinel = {color: 'black', value: null};
let root = sentinel;

function data(data) {
    return typeof data === 'object' && data !== null ? data.key : data;
}

function Node({value, left = sentinel, right = sentinel, parent = null, color}) {
    Object.assign(this, {
        left,
        right,
        parent,
        value,
        color,
    });
};

function black(node) {
    node.color = 'black';
}

function red(node) {
    node.color = 'red';
}

function isBlack(node) {
    return node.color === 'black';
}

function isRed(node) {
    return node.color === 'red';
}

function rbtree_insert(node) {
    // root为空说明当前插入第一个节点
    if (root === sentinel) {
        root = node;
        return;
    }

    let current = root;
    // 先插入
    while(current) {
        // 比当前节点大则往右走,如果到达最后一个右孩子,则设置当前节点为他的右孩子
        if (data(node.value) > data(current.value)) {
            if (current.right === sentinel) {
                current.right = node;
                node.parent = current;
                break;
            }
            current = current.right;
        } else {
            // 同上
            if (current.left === sentinel) {
                current.left = node;
                node.parent = current;
                break;
            }
            current = current.left;
        } 
    }

    // 插入成功后,标记插入节点为红色,尽量满足各种约束
    red(node);

    /*  
        父节点是红色的话,需要开始调整
        node节点是根节点的孩子时,不进入while。
        node不是根节点并且父节点是红色,说明父节点还有父节点,
        即父节点不是根节点,因为根节点必须是黑色
    */
    while(node !== root && isRed(node.parent)) {
        // node的父节点是node父节点的父节点的左孩子
        if (node.parent === node.parent.parent.left) {
            /*
                node的父节点是红色,说明祖父节点是黑色,node的叔叔节点是红色,
                则只需要做下面一步调整,即node的父节点和叔叔好节点变红,祖父节点变红。
                因为以祖父节点为根的这棵子树中,调整前,父节点和叔叔节点共享
                祖父节点的黑色,调整后,祖父节点为红色,但是父节点和叔叔节点为黑色了,
                不影响以祖父节点为根节点的子树的黑高度。因为祖父节点变红了,如果祖父节点的父节点
                也是红色,则又回到了一开始的问题,即跳到while。
                1 当node.parent.parent是根节点的时候,直接退出循环,但这是他是红色的,所以循环外需要
                2 把他强制变黑。如果node.parent.parent是根节点的孩子,则isRed(node.parent)不会成立,也会退出循环。
            */
            if (isRed(node.parent.parent.right)) {
                black(node.parent);
                black(node.parent.parent.right);
                red(node.parent.parent);
                node = node.parent.parent;
            } else {
                /*
                    如果叔叔节点是黑色,这时候的结构是,
                    父节点是红色,祖父是黑色,叔叔是黑色。node是红色。
                    调整理论也是类似上面,即父节点变黑,祖父节点变红,叔叔变黑。
                    但是因为调整前,以祖父节点为根的子树中,父节点和叔叔共享祖父的一个黑节点,
                    现在祖父变红,父节点变黑,对祖父节点到父节点这条路径的黑高度没影响,但是对
                    祖父到叔叔这条路径有影响,少了一个黑高度。这时候就要右旋转(见下面右旋转函数的解释)。
                    右旋导致父节点上升,替换祖父节点的位置,祖父下降成为父节点的右孩子,从而导致父节点原来的
                    右孩子(如果有的话)没有地方挂载。所以右旋转前,要先把以父节点为根的子树,左旋转(见下面左旋函数的结束)一下。
                    因为父节点的右孩子比父节点大,所以右孩子会替换父节点成为该子树的新根节点。而父节点变成右孩子的左孩子。
                    这时候,因为右孩子上升为根节点,并且左孩子是原来的父节点。会导致右孩子原来的左孩子(有的话)没有地方挂载。
                    所以这个左孩子会成为父节点的右孩子。我们会发现,这样左旋或右旋,是不是破坏红黑数的规则的。
                */
                if (node === node.parent.right) {
                    rbtreeLeftRotate(node.parent);
                }
                black(node.parent);
                black(node.parent.parent.right);
                red(node.parent.parent);
                rbtreeRightRotate(node.parent.parent);
            }
        } else {
            // 类似上面的解释
            if (isRed(node.parent.parent.left)) {
                black(node.parent);
                black(node.parent.parent.left);
                red(node.parent.parent);
                node = node.parent.parent;
            } else {
                if (node === node.parent.left) {
                    rbtreeRightRotate(node.parent);
                }
                black(node.parent);
                black(node.parent.parent.left);
                red(node.parent.parent);
                rbtreeLeftRotate(node.parent.parent);
            }
        }
    }
    // 根节点可能会被变红,这里强制变黑
    black(root);
}

// 左旋转,node成为右节点的左孩子,node右孩子的左孩子成为node的右孩子 //
function rbtreeLeftRotate(node) {
    // 保存右孩子的地址,因为node的右指针即将被修改
    let right = node.right;

    // 第一步挂载右孩子的节点到父节点
    // 右孩子的左孩子成为node的右孩子
    node.right = right.left;
    // 可能没有左孩子,有的话他的父指针指向node
    if (node.right !== sentinel) {
        node.right.parent = node;
    }

    // 第二步,替换node成为子树根节点
    // 到达根,则right变成新的根节点,否则修改node的父节点对应指针的指向,即替换node的位置,
    if (node === root) {
        root = right;
    } else if (node === node.parent.left){
        node.parent.left = right;
    } else {
        node.parent.right = right;
    }
    // 更新右孩子的父节点,不再是node,而是他的父节点
    right.parent = node.parent;

    // 第三步,建立右孩子和node的连接
    right.left = node;
    node.parent = right;
}

// 右旋转,node成为左节点的右孩子,node左孩子的右孩子成为node的左孩子
function rbtreeRightRotate(node) {
    let left = node.left;
    node.left = left.right;
    if (node.left !== sentinel) {
        node.left.parent = node;
    }
    if (node === root) {
        root = left;
    } else if (node === node.parent.left) {
        node.parent.left = left;
    } else {
        node.parent.right = right;
    }
    left.parent = node.parent;
    left.right = node;
    node.parent = left;
}   

(function test(){
    const data = [];
    let i = 0;
    while(i < 5) {
        rbtree_insert(new Node({value: i++}));
    }
    console.log(root)
}())
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程杂技 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档