Tree--RedBlackTree详解(2 - 3 - 4Tree)(红黑树)

#topics h2 { background: #2B6695; color: #FFFFFF; font-family: "微软雅黑", "宋体", "黑体", Arial; font-size: 15px; font-weight: bold; height: 24px; line-height: 23px; margin: 12px 0 !important; padding: 5px 0 5px 10px }
#topics h1 :not(.postTitle) { background: #ff005e; color: #FFFFFF; font-family: "微软雅黑", "宋体", "黑体", Arial; font-size: 25px; font-weight: bold; height: 36px; line-height: 31px; margin: 18px 0 !important; padding: 8px 0 5px 5px }

  前言

  最近看到好多红黑树的东西,英文好的童鞋可以直接点击http://www.cs.princeton.edu/~rs/talks/LLRB/RedBlack.pdf这里查看我之前学习的材料,对理解下面讲的东西肯定也有点帮助(但是不完全一样),英文一般的同学就直接看我的文采飞扬把哈哈。还有大家可以去coursera上学习一些国外比较好的资料。感觉比国内一些学习网站做的好很多。

  前面一篇随笔写的binarysearchtree(http://www.cnblogs.com/robsann/p/7567596.html)说了有一个缺点就是不平衡,意思就是插入已经排好序的对象的时候会变成一条链表,链表当然比二叉树要慢很多啦,随机插入的话二叉树的各种方法需要的时间和LgN的成正比。所以红黑树其实是在解决二叉树不平衡的问题的。

  度娘(这里稍微看一下)

  红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:

  性质1. 节点是红色或黑色。

  性质2. 根节点是黑色。

  性质3 每个叶节点(NIL节点,空节点)是黑色的。

  性质4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)

  性质5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

  这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。

 正文 

  2 - 3 - 4 Tree

  2 - 3 - 4 Tree 我觉得算是一种模型,一种树模型保证了树是平衡的,所谓平衡就是树不会一个枝头长得很高,另外一个枝头长得很矮,那保证平衡有什么用?平衡的情况下,所有操作需要的时间都是和LgN成正比的,你说腻害不腻害。

  2 - 3 - 4 树,允许一个节点是 2-nodes 或者是 3 nodes 或者是 4 nodes, 具体的意思就是说

   这是一个2-nodes, 有2个触手,能够指向不同的2个子元素,左边的子元素小于A,右边的子元素大于A

  3-nodes, 3 分叉,最左边的子元素小于C,中间的子元素between c和e, 最右边的子元素大于E

    4-nodes, 3分叉。同上

  看看2-3-4tree是如何保证平衡的

  假设有 10 7 6 3 8 11 15 要插入 2 -3 4 树中

  

  

  

  

  

  

  这里插入还有另外一种选择叫Bottom-up solution,之下而上的一种解法(可以忽略)。就是先找到这个节点会被插入的位置,如果插入后变成了5-nodes,就把其中一个节点往父节点抛出,让父节点和其中的一个子节点结合。

  删除同理,为了要保证树的平衡,当删除的时候,如果被删除的节点只有一个元素的话,必须要把父亲元素拉下来形成一个3-nodes然后删除后变成2-nodes(后面还会说)

  左倾红黑树(LeftLeaningRedBlackTree)

  红黑树和2-3-4树的关系,红黑树是2-3-4树的一种实现方式。2-3-4树只是一个模型。

  先介绍一下节点对象Node,看代码,之后会用到这个对象

    private class Node {
        private K k; // 这个节点的key 
        private V v; //节点的value
        private Node left, right;  //左右节点
        private int size;  //节点为根的树的大小
        private boolean color; //节点的颜色,分黑和红

        Node(K k, V v, int size, boolean color) {
            this.k = k;
            this.v = v;
            this.size = size;
            this.color = color;
        }

        @Override
        public String toString() {
            if (color) return "red " + v;
            else return "black " + v;
        }

    }

Node(节点对象)

  左倾红黑树和红黑树的区别如下

  这是一般的红黑树,3-node 的时候红节点可以左倾或者右倾

  左边是2-3-4树的模型,右边是红黑树的实现

  

  这是左倾红黑树, 3-nodes的时候红色的节点必须在左边。

  

  用常量来表示红黑

  

  红黑树的修正

  首先左倾红黑树的性质必须要得到保证(就是上图中说的Require that 3 -node be left leaning),但是很多时候可能因为插入或者删除的操作破坏了这个性质,所以我们必须要修正。

  这里介绍3个修正的方法

  第一个方法的使用场景是这样子的,(为了不破坏平衡,每次插入的都是红节点),下图插入的节点在右边,破坏了左倾的性质,所以必须rotateLeft。rotateLeft是指把红色节点左移

    

  方法如下

    //右树是红link的时候,turn this red link to left
    private Node rotateLeft(Node  h) {
        assert(isRed(h.right));

        Node  x = h.right;  //change the pointers
        h.right = x.left;
        x.left = h;

        x.color = h.color; //change the colors
        h.color = RED;

        x.size = h.size; //change the sizes
        h.size = size(h.left) + size(h.right) + 1;
        return x;
    }

rotateLeft(仔细看一边)

  

  另外一种情况是最新插入的节点在最左边,把中间节点rotateRight,重新平衡

    

  方法如下

    //左树是红link的时候,turn this red link to left
    private Node rotateRight(Node  h) {
        assert(isRed(h.left));

        Node x = h.left;
        h.left = x.right;
        x.right = h;

        x.color = h.color;
        h.color = RED;

        x.size = h.size; //size is the same
        h.size = size(h.left) + size(h.right) + 1;

        return x;
    }

rotateRight

  这张图是破坏了性质之后,修正的办法

  

  还有一个方法是flipColors(),代码如下,就是可能插入的时候需要满足当前节点不是4-nodes,可能就会使用这个方法

  

  有了上面这些辅助的方法后就可以开始下面的学习了

  

  左倾红黑树的put  

  在2-3-4tree中的介绍中也知道了,put的时候,当前节点如果是4-nodes的话就没有位置留给需要插入的对象了。

  所以我们在put的时候,一定要保证当前的节点(currentNode)以后用cn来表示。cn必须不是4-nodes, 如果是4-nodes的话就用flipColor把4-node变成3个2-node

  假设我们要插入10  7  6  3 这4个对象, 大片动态图,燃烧的经费。

  

  附上代码

    public void put(K k, V v) {
        root = put(root, k, v);
        root.color = BLACK;
    }

    private Node put(Node cn, K k, V v) {
        if (cn == null) return new Node(k, v, 1, RED);
        if(isRed(cn.left) && isRed(cn.right)) split4Node(cn);//是4节点的话 就split

        int cmp = k.compareTo(cn.k);
        if (cmp > 0) cn.right = put(cn.right, k, v); // k > node.k go right
        else if (cmp < 0) cn.left = put(cn.left, k, v);
        else cn.v = v; //hit

        //following code is to fix the tree on the way up
        if (isRed(cn.right) && !isRed(cn.left)) cn = rotateLeft(cn); // right leaning 3nodes的时候   需要变成 left leaning
        if (isRed(cn.left) && isRed(cn.left.left)) cn = rotateRight(cn);  //变成了一个4节点

        cn.size = size(cn.left) + size(cn.right) + 1;
        return cn;
    }

put

  

  左倾红黑树的get

  树的get方法其实很简单,就是判断key是不是相等,如果相等就return 这个值。

  

    public V get(K k) {
        return get(root, k);
    }

    //cn means currentNode
    private V get(Node cn, K k) {
        if (cn == null) return null; // not find the key

        int cmp = k.compareTo(cn.k);
        if(cmp > 0) return get(cn.right, k);
        else if (cmp < 0) return get(cn.left, k);
        else return cn.v; // hit
    }

get

  左倾红黑树的删除

  删除可以说是最难的了把,基本的思想就是,cn节点(当前节点)不会是2-node,(如果root节点是2-nodes,我们需要把root节点变成红节点)

  保证其中一个子节点不是2节点(这个保证需要看删除的节点位于当前节点的哪里,比如删除的节点比cn节点小,所以接下来我们会往left走,所以要保证left节点不是2-node)。因为2节点如果删除了的话就不会平衡。所以必须要把红节点从root一步一步carry下去。

  先实现一个deleteMin方法,我们要把红节点带向左边。想一想,红节点带向左边后,如果左边有节点删除了可能没办法保持平衡,红节点可以变成黑节点,代替刚才被删除的节点。通过这样子可以保证左边的树是一定会平衡的。

  deleteMin的几种情况

  1. , 现在可以直接删除掉。也不会影响平衡。删除了后3节点变成了2节点。

  

  2., 需要继续向左走,但是左子节点是2-node,必须要想办法变成不是2-node。所以需要把父亲节点和兄弟节点和cn节点。整合在一起变成4-node

  

  3. , 需要继续向左走,但是左子节点是2-node,必须要想办法变成不是2-node。发现兄弟节点是不是2-nodes。所以把兄弟节点借一个node过来

  

  

  2和3这2种情况总结在一起就是moveRedLeft的代码

  

    public void deleteMin() {
        //保证了root节点不是2nodes
        if (!isRed(root.left) && !isRed(root.right))
            root.color = RED;

        root = deleteMin(root);
        root.color = BLACK;
    }

    public Node deleteMin(Node cn) {
        if (cn.left == null) return null;

        if (!isRed(cn.left) && !isRed(cn.left.left))  //判断左边子节点是不是2node,是的话就需要把Red带下去
            cn = moveRedLeft(cn);

        cn.left = deleteMin(cn.left);

        return fixup(cn);
    }

    private Node fixup(Node h) {

        if (isRed(h.right) && !isRed(h.left)) h = rotateLeft(h); //右倾
        if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h);
        h.size = size(h.left) + size(h.right) + 1; //right the size
        return h;
    }

deleteMin

  随意的delete方法。按照下面的图说一下,基本的思想。

  首先删除D。从H开始,H不是2-node(右边有一个红节点),D小于H,左往H的左边找

  找到了D。D不是2-node且是红节点。找到了D,处理办法是找到右树中最小的值,发现是E, 把最小的值赋值给当前的node

  所以我们要往右边走。但是发现右边节点F是2-node。所以我们要把红色的连接往右边带。

  flipColor(D), B D 和 F就变成了一个4-node(可以自己做一下图看看是不是这样子的)。 这时候红链接也往右边带了。

  现在到了F 节点。发现F 的左节点是 2-node。把红色的链接往左边带。

  flipColor(F),这个时候F 和 E 和 G ,变成了一个4-node。 顺理成章删除左边的节点。没有影响平衡。

  接着原路返回,修复节点。

  

  

  

    public void delete(K k) {
        if (k == null) throw new IllegalArgumentException("argument to delete() is null");
        if (!contains(k)) return;

        if (!isRed(root.left) && !isRed(root.right))
            root.color = RED;
        root = delete(root, k);
        if (root != null)
            root.color = BLACK;
    }

    public boolean contains(K k) {
        return get(k) != null;
    }

    private Node delete(Node cn, K k) {

        if (cn == null) return null;

        int cmp = k.compareTo(cn.k);

        if (cmp < 0) { // k < node.k go left
            if (!isRed(cn.left) && !isRed(cn.left.left)) //保证了下一个左元素不是2nodes
                cn = moveRedLeft(cn);
            cn.left = delete(cn.left, k);
        } else if (cmp > 0) { // k > node.k go right
            if (isRed(cn.left) && !isRed(cn.right)) //如果是3节点的话需要 rotate 把red转到右边
                cn = rotateRight(cn);
            if (!isRed(cn.right) && !isRed(cn.right.left)) //保证下一个右节点不是2nodes
                cn = moveRedRight(cn);
            cn.right = delete(cn.right, k);
        } else { //hit

            if (isRed(cn.left) && !isRed(cn.right))
                cn = rotateRight(cn);

            if (k.compareTo(cn.k) == 0 && (cn.right == null)) //find null just return null
                return null;

            if (!isRed(cn.right) && !isRed(cn.right.left)) //保证下一个右节点不是2nodes
                cn = moveRedRight(cn);

            if (k.compareTo(cn.k) == 0) {
                 Node x = min(cn.right);
                 cn.k = x.k;
                 cn.v = x.v;
                 cn.right = deleteMin(cn.right);
            } else cn.right = delete(cn.right, k);
        }
        return fixup(cn);
    }

delete

 总结

  具体的实现可以参考一下https://github.com/Cheemion/algorithms/blob/master/src/com/algorithms/tree/LeftLeaningRedBlackTree.java

  可能有地方说的不清楚哈。见谅,可以留言我有不清楚的地方

  

时间: 2024-10-05 05:04:44

Tree--RedBlackTree详解(2 - 3 - 4Tree)(红黑树)的相关文章

linux下tree命令详解

1.description方法是NSObject自带的方法,包括类方法和对象方法 + (NSString *)description; // 默认返回 类名 - (NSString *)description; // 默认返回 <类名:内存地址> 2.默认情况下利用NSLog和%@输出对象的时返回的就是类名和内存地址 3.修改NSLog和%@的默认输出:重写类对象或者实例对象的description方法即可.因为NSLog函数进行打印的时候会自动调用description方法 /*******

Ext.Net学习笔记22:Ext.Net Tree 用法详解

Ext.Net学习笔记22:Ext.Net Tree 用法详解 上面的图片是一个简单的树,使用Ext.Net来创建这样的树结构非常简单,代码如下: <ext:TreePanel runat="server"> <Root> <ext:Node Text="根节点" Expanded="true"> <Children> <ext:Node Text="节点1" Expand

CentOS tree命令详解

inux下tree命令详解---linux以树状图逐级列出目录的内容命令 #############################################################################命令格式 tree <选项或者是参数> <分区或者是目录> #############################################################################(1) tree 最长使用的参数或者是选项 

Merkle Tree算法详解

转载自:http://blog.csdn.net/yuanrxdu/article/details/22474697Merkle Tree是Dynamo中用来同步数据一致性的算法,Merkle Tree是基于数据HASH构建的一个树.它具有以下几个特点: 1.数据结构是一个树,可以是二叉树,也可以是多叉树(本BLOG以二叉树来分析) 2.Merkle Tree的叶子节点的value是数据集合的单元数据或者单元数据HASH. 3.Merke Tree非叶子节点value是其所有子节点value的H

k-d tree算法详解

k-d树(k-dimensional树的简称),是一种分割k维数据空间的数据结构.主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索). 1.应用背景 SIFT算法中做特征点匹配的时候就会利用到k-d树.而特征点匹配实际上就是一个通过距离函数在高维矢量之间进行相似性检索的问题.针对如何快速而准确地找到查询点的近邻,现在提出了很多高维空间索引结构和近似查询的算法,k-d树就是其中一种. 索引结构中相似性查询有两种基本的方式:一种是范围查询(range searches),另一种是K近邻查询

HDU 5379 Mahjong tree (详解,构造+思维)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5379 题面: Mahjong tree Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total Submission(s): 1148    Accepted Submission(s): 351 Problem Description Little sun is an art

STL RB Tree(红黑树)分析

当我2014年上半年看内核代码的时候,进程调度用的就是RB  Tree,而现在分析STL源码的时候发现Set和Map也使用了这个数据结构,说明了RBTree的使用时如此的广泛,所以我花了两天时间看了这,分三部分来说明,首先我要说明下红黑树的基本概念,然后说明下STL中的RB Tree的迭代器,最后说下STL中RB Tree容器的实现. 一.红黑树的基本概念 红黑树是平衡二叉搜索树的一种(平衡二叉搜索树中又有AVL Tree),满足二叉搜索树的条件外,还应买足下面的4个条件 1) 每个节点不是红色

数据结构之红黑树

红黑树(Red Black Tree) 是一种自平衡二叉查找树 红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能.它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目.(度娘)C++ stl里面的set,map底层就是用红黑树实现的.红黑树具体的插入删除原理请参考<<算法导论>> 维基上面也讲得不错.反正插入过程就是要解决&q

菜鸟nginx源码剖析数据结构篇(四)红黑树ngx_rbtree_t

Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:October 27h, 2014 1.ngx_rbtree优势和特点 ngx_rbtree是一种使用红黑树实现的关联容器,关于红黑树的特性,在<手把手实现红黑树>已经详细介绍,这里就只探讨ngx_rbtree与众不同的地方:ngx_rbtree红黑树容器中的元素都是有序的,支持快速索引,插入,删除操作,也支持范围查询,遍历操作,应