博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
用Js实现红黑树
阅读量:6529 次
发布时间:2019-06-24

本文共 13487 字,大约阅读时间需要 44 分钟。

学习红黑树,用js撸了一个

红黑树是一个效率很高且稳定的数据结构,插入和删除操作的时间复杂度都是logn。

红黑树的性质:

  1. 每一个节点或者着红色,或者着黑色
  2. 根是黑色
  3. 如果一个节点是红色的,那么它的子节点必须是黑色
  4. 从一个节点到一个Null节点(树叶)的每一条简单路径必须包含相同数目的黑色节点

插入操作以后再补~

删除操作(自顶向下的实现方式)

删除操作是红黑树最难的部分,通常有两种实现方式:自顶向下自底向上。《算法导论》里使用的是自底向上的实现方式,对我而言相当晦涩,又看了几篇类似的实现方式,需要罗列出相当多的情形,实现不易。《数据结构与算法 -- C语言实现》里使用的是自顶向下的实现方式,但只讨论了大致逻辑,并未给出具体实现,最后在里找到了完整的实现。自顶向下的方式确实简单易懂,且非常巧妙,我也是用这种方式来实现红黑树的删除操作

在此之前,先复习一下前面列出的红黑树的性质。删除操作之所以复杂,是因为如果需要删除的节点是黑色的,那么直接删除它会破坏性质4。因此,我们需要保证删除该节点之后,能有一种方式修复删除后被破坏的部分。自底向上实现的思路是:先删除节点,然后通过旋转、变色等手段恢复破坏了红黑树性质的部分,而自顶向下实现的思路是:在查找需要删除的节点的路径上,保证每个节点都是红色的(如果不是就要通过变换让它变成红色,且不破坏树的性质),如果它是要删除的节点,就可以安心地删除它。就思路而言,显然自底向上的方式更易理解,好像也更容易实现,但当你去处理修复过程时会发现有相当多的情况需要考虑。而自顶向下的方式看似笨拙,却能够通过巧妙的变换简化变换的过程

总的来说,就是我们要让当前访问的节点X变红,然后再去考虑它是不是需要删除的节点

  1. 启动条件

删除是一个递归函数,在进入递归之前需要确保当前当前结构符合启动条件。这里的结构指以X为中心的部分树结构,可能包含P, S,GP, XL,XR,SL,SR。启动条件如下:

即:X和它的兄弟节点S为黑色,父亲节点P为红色。实现insert时我们做了一个特殊处理,构造了一个假的根节点,值为负无穷,颜色为黑色,因此所有的真实节点都在它的右子树上。它的左节点是一个null节点(黑色),右节点是真正的根节点(必然是黑色)。而自顶向下删除的第一步,就是把根节点涂成红色,这样就天然满足了启动条件

  1. 若X有两个黑色的节点。注意,当X为叶子节点时也适用,因为叶子节点的子节点为Null节点,是黑色

    这时还需要考察S的子节点的情况才能决定如何变换,因此2还需要分几种子情形

    2.1 符合2的条件,同时S也有两个黑色的子节点

    这种情况是最简单的,让X变红只需要变换P,X,S的颜色即可。变换方法如下图

    2.2 符合2的条件,且S有一个红色的左子节点,一个黑色的右子节点

    这种情况下,X变红后,左边就少了一个黑色的节点,只能从P的右子树借一个过来。SL这个红色的节点可以利用起来,让它移到P现在的位置。显然这需要一个双旋转(SL位于整个旋转路径的内侧),变换如下:

    2.3 符合2的条件,且S有一个黑色的左子节点,一个红色的右子节点

    2.2类似,我们要利用SR这个红色节点,因为SR在整个旋转路径的外侧,所以使用一个单旋转

    2的这三种情况完成变换之后,X已经是红色且保证了红黑树的性质,接下来就可以判断X是否为需要删除的节点了。这一步我们也标记一下,叫它D-1好了

    D-1

    如果X不是要删除的节点,那么下降一层,即让P = X, X = XL或者XR, S = X的另一个子节点,然后进入下一个删除循环(即1)。因为X是红色,它的子节点必然为黑色,所以符合启动条件

    如果X正式需要删除的节点,需要分两种情况

    1. X恰好是叶子节点,这种情况直接删除X即可,不会对红黑树的性质有任何影响
    2. X为非叶子节点,如果直接删除X,它的子节点应该如何与它的父节点对接是个很复杂的操作,所以我们采用二叉查找树的节点删除方法:找到该节点的后继或前驱(如果后继不存在,再使用它的前驱节点),用它的值代替X的值,然后再去删除它的后继或前驱,反复这个过程直到我们需要删除的节点是叶子节点

    ok,2这类大的情况就处理好了,下面需要考虑X有至少一个红色节点的情况

  2. X至少有一个红色节点。这种情况需要改变一下思路,因为X有至少一个红色节点,如果X不是要删除的节点,就没必要再把X变红了,如果直接下降一层,X有很大概率直接落到红色的节点上,这能节省很多时间

    所以对于3这种情况,先判断X是否为要删除的节点,这就分成两种情况

    3.1 X不是要删除的节点,那么下降一层。这时X可能落到红色节点上,也可能落到黑色节点上,两种情况都需要考虑

    3.1.1 X是红色节点,那么X已经符合D-1的删除条件,可以直接进入D-1

    3.1.2 X是黑色节点,这时需要作一次节点变换。为了更直观,这里分成两个步骤,第一步下降一层,并让X落到黑色节点上,第二步才是变换

    此时X还是黑色,并不满足进入D-1的条件,但是仔细看X节点的上下结构,P、SR、X构成的子树正好满足1的启动条件。所以下一步是进入1的删除循环

    自此,3.1的所有情况已经处理好了,下面我们来看X是需要删除的节点这种情况

    3.2 X是需要删除的节点。因为X有红色的子节点,所以它不可能是叶子节点,也就是说即使把它变红也不能直接删除。在这种情况下,我们在D-1的基础上稍作修改:找到X的后继或前驱,用它的值代替X的值,之后下降一层,再一次进入到3的逻辑

这就是自顶向下删除需要考虑的所有情形了。我画了一个流程图,梳理了删除的逻辑

按照这个流程图来写代码,会非常清晰~

源码

/** *  RedBlackTreeNode.js *  红黑树的节点实现,比普通的二叉树节点多了color属性 */class RedBlackTreeNode {    constructor(data, color, lchild, rchild) {        // validate color        if (!Color[color]) {            throw new Error(`color can only be RED or BLACK`);        }         this.color = color;        this.data = data;        this.lchild = lchild;        this.rchild = rchild;     } }const Color = {    "RED": "RED",    "BLACK": "BLACK"};module.exports = {    RedBlackTreeNode,    Color,};复制代码
/** * @file 红黑树实现 * @author YDSS * * Created on Sun May 27 2018 */const { RedBlackTreeNode, Color } = require("./RedBlackTreeNode");class RedBlackTree {    constructor(arr) {        this._initialize();        this.create(arr);    }    _initialize() {        // init NullNode        this.NullNode = new RedBlackTreeNode(            Number.NEGATIVE_INFINITY,            Color.BLACK,            null,            null        );        this.NullNode.lchild = this.NullNode;        this.NullNode.rchild = this.NullNode;        // extra attr for recognizing the NullNode        this.NullNode.type = "null";        // init header        this.header = new RedBlackTreeNode(            Number.NEGATIVE_INFINITY,            Color.BLACK,            this.NullNode,            this.NullNode        );        // init nodes to store parent, grandparent and grandgrandparent        this.X = null;        this.P = null;        this.GP = null;        this.GGP = null;        // X's sister        this.S = null;    }    create(arr) {        arr.forEach(item => {            this.header = this.insert(item);        });    }    find(val) {        return this._find(val, this.header);    }    _find(val, T) {        if (!T) {            return null;        }        if (val === T.data) {            return T;        }        if (val > T.data) {            return this._find(val, T.rchild);        }        if (val < T.data) {            return this._find(val, T.lchild);        }    }    insert(data) {        this.X = this.P = this.GP = this.GGP = this.header;        this.NullNode.data = data;        while (data !== this.X.data) {            this.GGP = this.GP;            this.GP = this.P;            this.P = this.X;            if (data < this.X.data) {                this.X = this.X.lchild;            } else {                this.X = this.X.rchild;            }            if (                this.X.lchild.color === Color.RED &&                this.X.rchild.color === Color.RED            )                this._handleReorient(data);        }        // duplicate        if (this.X !== this.NullNode) {            return this.NullNode;        }        this.X = new RedBlackTreeNode(            data,            Color.RED,            this.NullNode,            this.NullNode        );        if (data < this.P.data) {            this.P.lchild = this.X;        } else {            this.P.rchild = this.X;        }        this._handleReorient(data);        return this.header;    }    _handleReorient(data) {        this.X.color = Color.RED;        this.X.lchild.color = Color.BLACK;        this.X.rchild.color = Color.BLACK;        if (this.P.color === Color.RED) {            this.GP.color = Color.RED;            if (data < this.GP.data !== data < this.P.data)                this.P = this._rotate(data, this.GP);            this.X = this._rotate(data, this.GGP);            this.X.color = Color.BLACK;        }        this.header.rchild.color = Color.BLACK;    }    /**     * single rotate     *     * @param {*} data     * @param {RedBlackTreeNode} Parent Parent Node of the subtree will rotate     */    _rotate(data, Parent) {        if (data < Parent.data) {            return (Parent.lchild =                data < Parent.lchild.data                    ? this._singleRotateWithLeft(Parent.lchild)                    : this._singleRotateWithRight(Parent.lchild));        } else {            return (Parent.rchild =                data > Parent.rchild.data                    ? this._singleRotateWithRight(Parent.rchild)                    : this._singleRotateWithLeft(Parent.rchild));        }    }    _singleRotateWithLeft(T) {        let root = T.lchild;        T.lchild = root.rchild;        root.rchild = T;        return root;    }    _singleRotateWithRight(T) {        let root = T.rchild;        T.rchild = root.lchild;        root.lchild = T;        return root;    }    /**     * find precursor node of this node     *  if this node doesn't have the left subtree, return null     *     * @param {*} data data of current node     * @return {BinaryTreeNode|Null}     */    findPrecursor(node) {        // let node = this.find(data);        // if (!node) {
// throw new Error(`node with data(${data}) is not in the tree`); // } if (!node.lchild) { return null; } let pre = node.lchild; let tmp; while (!this._isNilNode(tmp = pre.lchild)) { pre = tmp; } return pre; } /** * find successor node of this node * if this node doesn't have the right subtree, return null * * @param {BinaryTreeNode} current node * @return {BinaryTreeNode|Null} */ findSuccessor(node) { // let node = this.find(data); // if (!node) {
// throw new Error(`node with data(${data}) is not in the tree`); // } if (!node.rchild) { return null; } let suc = node.rchild; let tmp; while (!this._isNilNode(tmp = suc.lchild)) { suc = tmp; } return suc; } /** * delete node by means of top to down * * @param {*} val */ delete(val) { // prepare for deleting this.header.color = Color.RED; this.GP = null; this.P = this.header; this.X = this.header.rchild; this.S = this.header.lchild; this._delete(val); } _delete(val) { if ( this.X.lchild.color === Color.BLACK && this.X.rchild.color === Color.BLACK ) { // S has two black children if ( this.S.lchild.color === Color.BLACK && this.S.rchild.color === Color.BLACK ) { this._handleRotateSisterWithTwoBlackChildren(); // judge if X.data is what we are looking for this._handleDeleteXWhenXhasTwoBlackChildren(val); } // S has at last one red children else { // single rotate when S with it's red child in a line, // reference to avl rotate // 2.3 if ( this.S.data > this.P.data === (this.S.rchild.color === Color.RED) ) { this._rotate(this.S.data, this.GP); // change color this.P.color = Color.BLACK; this.X.color = Color.RED; this.S.color = Color.RED; this.S.lchild.color = Color.BLACK; this.S.rchild.color = Color.BLACK; // judge if X.data is what we are looking for this._handleDeleteXWhenXhasTwoBlackChildren(val); // double rotate when S with it's red child in a z-shape line // 2.2 } else { let firstData = this.S.data < this.P.data ? this.S.rchild.data : this.S.lchild.data; this._rotate(firstData, this.P); this._rotate(this.S.data, this.GP); // change color this.P.color = Color.BLACK; this.X.color = Color.RED; // judge if X.data is what we are looking for this._handleDeleteXWhenXhasTwoBlackChildren(val); } } } else { this._handleDeleteXWhenXhasAtLastOneRedChild(val); } } // 2.1 _handleRotateSisterWithTwoBlackChildren() { this.P.color = Color.BLACK; this.X.color = Color.RED; this.S.color = Color.RED; } _handleDeleteXWhenXhasTwoBlackChildren(val) { if (this.X.data === val) { if (this._hasChild(this.X)) { val = this._replaceWithSuccessorOrPrecursor(val); this._descend(val); this._delete(val); } else { // delete X when it's a leaf this._deleteLeafNode(val, this.P); } } else { this._descend(val); this._delete(val); } } _handleDeleteXWhenXhasAtLastOneRedChild(val) { if (this.X.data === val) { val = this._replaceWithSuccessorOrPrecursor(val); this._descend(val); } else { this._descend(val); } // X is red, enter the phase of judging X's data if (this.X.color === Color.RED) { this._handleDeleteXWhenXhasTwoBlackChildren(val); } else { this._handleRotateWhenXIsBlackAndSisterIsRed(); this._delete(val); } } // 3.1.2 _handleRotateWhenXIsBlackAndSisterIsRed() { let curGP = this._rotate(this.S.data, this.GP); // change color this.S.color = Color.BLACK; this.P.color = Color.RED; // fix pointer of S and GP this.S = this.X.data > this.P.data ? this.P.lchild : this.P.rchild; this.GP = curGP; } _deleteLeafNode(val, parent) { if (parent.rchild.data === val) { parent.rchild = this.NullNode; } else { parent.lchild = this.NullNode; } } _hasChild(node) { return !this._isNilNode(node.lchild) || !this._isNilNode(node.rchild); } _isNilNode(node) { return node === this.NullNode; } /** * replace X with it's successor, * if it has no successor, instead of it's precursor * @param {*} val the delete data * * @return {*} updated delete data */ _replaceWithSuccessorOrPrecursor(val) { let child = this.findSuccessor(this.X); if (!child) { child = this.findPrecursor(this.X); } this.X.data = child.data; return child.data; } /** * descend one floor * * @param {*} val the val of node will be deleted */ _descend(val) { this.GP = this.P; this.P = this.X; if (val < this.X.data) { this.S = this.X.rchild; this.X = this.X.lchild; } else if (val > this.X.data) { this.S = this.X.lchild; this.X = this.X.rchild; } // val === this.X.data when X's successor or precursor // is it's child, in this situation it's wrong to choise // where X to go down by comparing val, cause X.data is equal // with new delete value else { if (val === this.X.lchild) { this.S = this.X.rchild; this.X = this.X.lchild; } else { this.S = this.X.lchild; this.X = this.X.rchild; } } }}module.exports = RedBlackTree;复制代码

转载地址:http://vzxbo.baihongyu.com/

你可能感兴趣的文章
路由配置系统(URLconf)
查看>>
TextBox Template
查看>>
Linux MySQL 储存中文失败简单解决办法
查看>>
求最大值及其下标
查看>>
洛谷——P1330 封锁阳光大学
查看>>
css选择器
查看>>
Kubernetes1.91(K8s)安装部署过程(八)-- kubernetes-dashboard安装
查看>>
个人总结 css 浏览器兼容常见问题总结方法
查看>>
C语言实现面向对象(转)
查看>>
Android dialog在有的手机上宽度不能充满屏幕的问题
查看>>
android通过USB使用真机调试程序
查看>>
对于基本类型的复制以及浅拷贝和深拷贝
查看>>
【错误】混合模式程序集是针对“v2.0.50727”版的运行时生成的,在没有配置其他信息的情况下,无法在 4.0 运行时中加载该程序集。...
查看>>
set
查看>>
markdown
查看>>
不要想当然的认为移动函数是必然存在, 高效且可用的.
查看>>
http- 像Apache一样
查看>>
keil (mdk) 中 在编译时,去掉没有使用的函数,减小代码量
查看>>
使用Redis搭建简单的Java分布式锁
查看>>
腾讯云和阿里云哪个好,对比双方活动看看谁更好
查看>>