zoukankan      html  css  js  c++  java
  • 数据结构之红黑树

    红黑树是平衡二叉查找树的一种。为了深入理解红黑树,我们需要从二叉查找树开始讲起。

    BST

    二叉查找树(Binary Search Tree,简称BST)是一棵二叉树,它的左子节点的值比父节点的值要小,右节点的值要比父节点的值大。它的高度决定了它的查找效率。

    在理想的情况下,二叉查找树增删查改的时间复杂度为O(logN)(其中N为节点数),最坏的情况下为O(N)。当它的高度为logN+1时,我们就说二叉查找树是平衡的。

    BST的查找操作

    T  key = a search key
    Node root = point to the root of a BST
    
    while(true){
        if(root==null){
            break;
        }
        if(root.value.equals(key)){
            return root;
        }
        else if(key.compareTo(root.value)<0){
            root = root.left;
        }
        else{
            root = root.right;
        }
    }
    return null;

    从程序中可以看出,当BST查找的时候,先与当前节点进行比较:

    • 如果相等的话就返回当前节点;
    • 如果少于当前节点则继续查找当前节点的左节点;
    • 如果大于当前节点则继续查找当前节点的右节点。

    直到当前节点指针为空或者查找到对应的节点,程序查找结束。

    BST的插入操作

    Node node = create a new node with specify value
    Node root = point the root node of a BST
    Node parent = null;
    
    //find the parent node to append the new node
    while(true){
       if(root==null)break;
       parent = root;
       if(node.value.compareTo(root.value)<=0){
          root = root.left;  
       }else{
          root = root.right;
       } 
    }
    if(parent!=null){
       if(node.value.compareTo(parent.value)<=0){//append to left
          parent.left = node;
       }else{//append to right
          parent.right = node;
       }
    }

    插入操作先通过循环查找到待插入的节点的父节点,和查找父节点的逻辑一样,都是比大小,小的往左,大的往右。找到父节点后,对比父节点,小的就插入到父节点的左节点,大就插入到父节点的右节点上。

    BST的删除操作

    删除操作的步骤如下:

    1. 查找到要删除的节点。
    2. 如果待删除的节点是叶子节点,则直接删除。
    3. 如果待删除的节点不是叶子节点,则先找到待删除节点的中序遍历的后继节点,用该后继节点的值替换待删除的节点的值,然后删除后继节点。

    BST存在的问题

    BST存在的主要问题是,数在插入的时候会导致树倾斜,不同的插入顺序会导致树的高度不一样,而树的高度直接的影响了树的查找效率。理想的高度是logN,最坏的情况是所有的节点都在一条斜线上,这样的树的高度为N。

    RBTree

    基于BST存在的问题,一种新的树——平衡二叉查找树(Balanced BST)产生了。平衡树在插入和删除的时候,会通过旋转操作将高度保持在logN。其中两款具有代表性的平衡树分别为AVL树和红黑树。AVL树由于实现比较复杂,而且插入和删除性能差,在实际环境下的应用不如红黑树。

    红黑树(Red-Black Tree,以下简称RBTree)的实际应用非常广泛,比如Linux内核中的完全公平调度器、高精度计时器、ext3文件系统等等,各种语言的函数库如Java的TreeMap和TreeSet,C++ STL的map、multimap、multiset等。

    RBTree也是函数式语言中最常用的持久数据结构之一,在计算几何中也有重要作用。值得一提的是,Java 8中HashMap的实现也因为用RBTree取代链表,性能有所提升。

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

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

    性质2. 根节点是黑色。

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

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

     

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

    是性质3导致路径上不能有两个连续的红色节点确保了这个结果。最短的可能路径都是黑色节点,最长的可能路径有交替的红色和黑色节点。因为根据性质4所有最长的路径都有相同数目的黑色节点,这就表明了没有路径能多于任何其他路径的两倍长。

    数据结构表示如下:

    class  Node<T>{
       public  T value;
       public   Node<T> parent;
       public   boolean isRed;
       public   Node<T> left;
       public   Node<T> right;
    }

    RBTree在理论上还是一棵BST树,但是它在对BST的插入和删除操作时会维持树的平衡,即保证树的高度在[logN,logN+1](理论上,极端的情况下可以出现RBTree的高度达到2*logN,但实际上很难遇到)。这样RBTree的查找时间复杂度始终保持在O(logN)从而接近于理想的BST。RBTree的删除和插入操作的时间复杂度也是O(logN)。RBTree的查找操作就是BST的查找操作。

    RBTree的旋转操作

    旋转操作(Rotate)的目的是使节点颜色符合定义,让RBTree的高度达到平衡。
    Rotate分为left-rotate(左旋)和right-rotate(右旋),区分左旋和右旋的方法是:待旋转的节点从左边上升到父节点就是右旋,待旋转的节点从右边上升到父节点就是左旋。

    RBTree的查找操作

    RBTree的查找操作和BST的查找操作是一样的。请参考BST的查找操作代码。

    public boolean contains(E element){
        return _contains(root, element);
    }
    //通过比较大小值来递归查询子节点。
    private boolean _contains(Node node,E element){
        if(node == null) return false;
        
        if(element.compareTo(node.e) == 0){
            return true;
        }else if(element.compareTo(node.e) > 0){
            return _contains(node.right, element);
        }else{
            return _contains(node.left, element);
        }
        
        return false;
    }

    RBTree的插入操作

    RBTree的插入与BST的插入方式是一致的,只不过是在插入过后,可能会导致树的不平衡,这时就需要对树进行旋转操作和颜色修复(在这里简称插入修复),使得它符合RBTree的定义。

    新插入的节点是红色的,插入修复操作如果遇到父节点的颜色为黑则修复操作结束。也就是说,只有在父节点为红色节点的时候是需要插入修复操作的。

    插入修复操作分为以下的三种情况,而且新插入的节点的父节点都是 红色的:

    1. 叔叔节点也为红色。
    2. 叔叔节点为空,且祖父节点、父节点和新节点处于一条斜线上。
    3. 叔叔节点为空,且祖父节点、父节点和新节点不处于一条斜线上。

      

    插入操作-case 1

    case 1的操作是将父节点和叔叔节点与祖父节点的颜色互换,这样就符合了RBTRee的定义。即维持了高度的平衡,修复后颜色也符合RBTree定义的第三条和第四条。下图中,操作完成后A节点变成了新的节点。如果A节点的父节点不是黑色的话,则继续做修复操作。

    插入操作-case 2

    case 2的操作是将B节点进行右旋操作,并且和父节点A互换颜色。通过该修复操作RBTRee的高度和颜色都符合红黑树的定义。如果B和C节点都是右节点的话,只要将操作变成左旋就可以了。

    插入操作-case 3

    case 3的操作是将C节点进行左旋,这样就从case 3转换成case 2了,然后针对case 2进行操作处理就行了。case 2操作做了一个右旋操作和颜色互换来达到目的。如果树的结构是下图的镜像结构,则只需要将对应的左旋变成右旋,右旋变成左旋即可。

    插入操作的总结

    参考:https://zhuanlan.zhihu.com/p/24367771

  • 相关阅读:
    数据结构(一)线性表单链表试题
    虚拟研讨会:如何设计好的RESTful API?
    如何生成RestFul Api文档
    webstorm 10 设置文件的默认编码
    HAML学习
    Nodejs初阶之express
    RESTful API 简书
    解读Nodejs多核处理模块cluster
    Nginx做NodeJS应用负载均衡配置实例
    拿nodejs快速搭建简单Oauth认证和restful API server攻略
  • 原文地址:https://www.cnblogs.com/xiaosisong/p/12293930.html
Copyright © 2011-2022 走看看