• 二叉搜索树、AVL平衡二叉搜索树、红黑树、多路查找树


    1.二叉搜索树

    1.1定义

      是一棵二叉树,每个节点一定大于等于其左子树中每一个节点,小于等于其右子树每一个节点

    1.2插入节点

      从根节点开始向下找到合适的位置插入成为叶子结点即可;在向下遍历时,如果要插入的值比节点的值小,则向节点的左子树遍历,大于等于则向右子树遍历,如此循环。

    1.3删除节点

    删除节点x有3种情况:

      1.x是叶子结点,则直接删除;

      2.x只有一棵子树(左子树或者右子树),则直接将x的父结点指向x的孩子,再删除x节点,如果x是根结点,则要更新x的孩子为树根;

      3.x有两棵子树,则要找到x在右子树中的后驱节点y,然后将y的右子树成为y的父结点的左子树,再用y替换掉x。

    后驱节点y:大于x的节点中的最小结点,也就是x的右子树中的最小结点,即x的右子树中最左侧的不拥有左子树的节点;下图是一个实例:

    参考资料:

    https://blog.csdn.net/ivan_zgj/article/details/51502767

    2.AVL平衡二叉搜索树(要平衡先搜索)

    2.1定义

      一棵二叉搜索树,任何节点的左子树高度和右子树高度最多相差1(严格平衡)

      节点58左子树高度3,右子树高度0,相差3,所以图中不是平衡二叉树

      AVL树的高度为 ⌊logn⌋ (n为节点总个数,向下取整符号)

      满二叉树是平衡树吗?看标题,要平衡先搜索,搜索树是考虑了节点值的树

    2.2节点结构

      data、bf(平衡因子=左子树高度-右子树高度)、left指针、right指针

    2.3插入节点

    1)按二叉搜索树的规则找到插入位置;

    2)节点插入后,若节点X的左子树和右子树高度的差变成了2,破坏了平衡,则需要调整,可分为四种情况:

      1.外侧插入:左左——插入点位于X的左儿子的左子树

      2.外侧插入:右右——插入点位于X的右儿子的右子树

      3.内侧插入:左右——插入点位于X的左儿子的右子树

      4.内侧插入:右左——插入点位于X的右儿子的左子树

    3)对于外侧插入,采用单旋转修正:左左(右单旋),右右(左单旋);

    4)单旋转:拿右旋转举例,捏起X节点的左儿子,让X节点自然下滑,并将X节点的左子节点的右子树挂到X节点的左侧,整个过程就像向右边旋转一样;

    5)对于内侧插入,采用双旋转(两次单旋转)修正:左右(左单旋+右单旋),右左(右单旋+左单旋);

    6)旋转操作都是O(1)

    2.4删除节点(不太懂)

    按二叉搜索树的规则找到要删除的节点,然后再找到“最先发生树高变化的节点”,进行旋转调整,删除节点x有3种情况:

    1. x是叶子节点,“最先发生树高变化的节点”为x的父节点;

    2. x只有一棵子树,“最先发生树高变化的节点”为x的父节点;

    3. x有两棵子树,则“最先发生树高变化的节点”为x的后驱的父节点。

    2.5各种操作的时间复杂度

      查找、插入、删除:O(logn)

    3.红黑树

    3.1定义

    是一棵二叉搜索树,确保最长路径不大于两倍的最短路径的长度,不是严格平衡,是近似平衡,满足5个规则:

      1.每个节点要么是红色,要么是黑色

      2.根节点必为黑色

      3.NULL视为黑色

      4.如果某个节点为红色,其儿子节点必须为黑色

      5.任何一个节点至NULL的任何路径,所含黑色节点的数目相同

    一般情况下,规则5导致新插的节点必须为红色,进而根据规则4,需要新插节点的父节点为黑色,如果不为黑色,就需要调整颜色或旋转来修正

    3.2节点结构的数据成员

      color(枚举型)、key、value、left指针、right指针、parent指针(红黑树的各种操作经常要上溯到父节点)

    3.3插入节点

    case 1:如果插入的节点是根节点,直接将该节点标识成黑色即可

    case 2:插入的节点不是根节点,如果插入的节点的父节点是黑色的话,那么不需要调整

    case 3:如果父节点是红色,则需要看叔节点的颜色

      case 3.1:叔节点为也红色,则将父和叔结点变为黑色,将祖父结点变为红色;由于祖父结点的父结点有可能为红,则将祖父结点作为新的判定点向上修正

      case 3.2:叔节点为黑色,则需要旋转并调整颜色来修正

        case 3.2.1:左左,右旋转

        case 3.2.1:右右,左旋转

        case 3.2.1:左右,左旋转+右旋转

        case 3.2.1:右左,右旋转+左旋转

        

             

    参考资料:

    http://blog.csdn.net/hackbuteer1/article/details/7740956

    http://www.cnblogs.com/xuqiang/archive/2011/05/16/2047001.html

    3.4删除节点

      太复杂。。。红黑树删除节点最多只需3次旋转调整

    根据被删除节点x的颜色可分为2种情况:

    1.x为红色,删除x后不会影响红黑树的平衡性,不需要做任何调整;

    2.x为黑色,则x所在的路径上的黑色节点总数减少1,红黑树失去平衡,需要调整。

    参考资料:

    https://www.cnblogs.com/sandy2013/p/3271497.html

    3.5插入、删除节点源码

    // sgi stl _Rb_tree 插入算法 insert_equal() 实现.
    // 策略概述: insert_equal() 在红黑树找到自己的位置,
    // 然后交由 _M_insert() 来处理接下来的工作.
    // _M_insert() 会将节点插入红黑树中, 接着调整红黑树,
    // 维持性质.
    template <class _Key, class _Value, class _KeyOfValue,class _Compare, class _Alloc>
    typename _Rb_tree<_Key, _Value, _KeyOfValue, _Compare, _Alloc>::iterator
    _Rb_tree<_Key, _Value, _KeyOfValue, _Compare, _Alloc>::insert_equal(const _Value& __v)
    {
        // 在红黑树中有头结点和根节点的概念, 头结点位于根节点之上,
        // 头结点只为管理而存在, 根节点是真正存储数据的地方. 头结点和根节点互为父节点,
         // 是一种实现的技巧.
        _Link_type __y = _M_header; // 指向头结点
        _Link_type __x = _M_root(); // _M_header->_M_parent, 即指向根节点
    
        // 寻找插入的位置
        while (__x != 0) {
            __y = __x;
    
            // 小于当前节点要走左边, 大于等于当前节点走右边
            __x = _M_key_compare(_KeyOfValue()(__v), _S_key(__x)) ?
                _S_left(__x) : _S_right(__x);
        }
        // __x 为需要插入的节点的位置, __y 为其父节点
        return _M_insert(__x, __y, __v);
    }
    
    // sgi stl _Rb_tree 插入算法 insert_unique() 实现.
    // 策略概述: insert_unique() 同样也在红黑树中找到自己的位置; 我们知道,
    // 如果小于等于当前节点会往右走, 所以遇到一个相同键值的节点后, 会往右走一步,
    // 接下来一直往左走, 所以下面的实现会对往左走的情况做特殊的处理.
    template <class _Key, class _Value, class _KeyOfValue,class _Compare, class _Alloc>
    pair<typename _Rb_tree<_Key, _Value, _KeyOfValue, _Compare, _Alloc>::iterator,bool>
    _Rb_tree<_Key, _Value, _KeyOfValue, _Compare, _Alloc>::insert_unique(const _Value& __v)
    {
        _Link_type __y = _M_header; // 指向头结点
        _Link_type __x = _M_root(); // 指向根节点, 可能为空
        bool __comp = true;
    
        // 寻找插入的位置
        while (__x != 0) {
            __y = __x;
            __comp = _M_key_compare(_KeyOfValue()(__v), _S_key(__x));//_M_key_compare:v<x返回true
    
            // 小于当前节点要走左边, 大于等于当前节点走右边
            __x = __comp ? _S_left(__x) : _S_right(__x);
        }
        //离开while循环之后,__y所指为插入点的父节点
    
        iterator __j = iterator(__y); // 令迭代器__j指向插入点的父节点__y
    
        // 我认为下面判断树中是否有存在键值的情况有点绕,
        // 它充分利用了二叉搜索树的性质, 如此做很 hack, 但不易理解.
        // 要特别注意往左边插入的情况.
    
        // HACKS:
        // 下面的 if 语句是比 __x 小走左边的情况: 会发现, 如果插入一个已存在的键的话,
        // __y 最终会定位到已存在键的右子树的最左子树.
        // 譬如, 红黑树中已经存在一个键为 100 的节点, 其右孩子节点为 101,
        // 此时如果再插入键为 100 的节点, 因为 100<=100, 所以会往右走到达 101 节点,
        // 有 100<101, 继而往左走, 会一直往左走.大家稍微画一个例子就能理解.
    
        if (__comp)    //如果离开while循环__comp为真,也就是说,最后一次while循环选择的是左边
            
            if (__j == begin())//特殊情况,如果插入点的父节点为最左节点,那么肯定要插入这个新节点.
                return pair<iterator, bool>(_M_insert(__x, __y, __v), true);
            else// 那么 --__j 能定位到此重复的键的节点.
                --__j;
    
        // HACKS: 这里比较的是 __j 和 __v, 如果存在键值, 那么 __j == __v,
        // 会跳过 if 语句. 否则执行插入. 也就是说如果存在重复的键, 那么 __j
        // 的值肯定是等于 __v
        if (_M_key_compare(_S_key(__j._M_node), _KeyOfValue()(__v)))
            return pair<iterator, bool>(_M_insert(__x, __y, __v), true);
    
        // 此时 __y.value = __v, 不允许插入, 返回键值所在位置
        return pair<iterator, bool>(__j, false);
    }
    
    // _M_insert() 是真正执行插入的地方.
    // 策略概述: 插入策略已经在上篇中详述, 可以根据上篇文章的描述,
    // 和下面代码的注释, 加深对红黑树插入算法里理解
    template <class _Key, class _Value, class _KeyOfValue,class _Compare, class _Alloc>
    typename _Rb_tree<_Key, _Value, _KeyOfValue, _Compare, _Alloc>::iterator
    _Rb_tree<_Key, _Value, _KeyOfValue, _Compare, _Alloc>::_M_insert(_Base_ptr __x_, _Base_ptr __y_, const _Value& __v)
    {
        _Link_type __x = (_Link_type)__x_; // 新节点插入的位置.
        // 关于 __x 的疑问:
        // 1. 它被放到下面的, 第一个 if 语句中, 我觉得是没有必要的,
        // 因为从调用 _M_insert() 的函数来看, __x 总是为空.
        // 2. 既然 __x 是新节点插入的位置, 那么为什么不直接在 __x 上创建节点,
        // 还要在下面通过比较来决定新节点是左孩子还是右孩子;
        // 不如直接用指针的指针或者指针的引用来完成, 省去了下面的判断.
    
        _Link_type __y = (_Link_type)__y_; // 新节点的父节点
        _Link_type __z; // 新节点的位置
    
        if (__y == _M_header || __x != 0 ||
            _M_key_compare(_KeyOfValue()(__v), _S_key(__y))) {
            // 新节点应该为左孩子
            __z = _M_create_node(__v);
            _S_left(__y) = __z;               // also makes _M_leftmost() = __z
                                              //    when __y == _M_header
            if (__y == _M_header) {
                _M_root() = __z;
                _M_rightmost() = __z;
            }
            else if (__y == _M_leftmost())
                _M_leftmost() = __z;   // maintain _M_leftmost() pointing to min node
        }
        // 新节点应该为右孩子
        else {
            __z = _M_create_node(__v);
            _S_right(__y) = __z;
            if (__y == _M_rightmost())
                _M_rightmost() = __z;  // maintain _M_rightmost() pointing to max node
        }
        _S_parent(__z) = __y;
        _S_left(__z) = 0;
        _S_right(__z) = 0;
    
        // 重新调整
        _Rb_tree_rebalance(__z, _M_header->_M_parent);
    
        // 更新红黑树节点数
        ++_M_node_count;
    
        // 返回迭代器类型
        return iterator(__z);
    }
    
    // 插入新节点后, 可能会破坏红黑树性质, _Rb_tree_rebalance() 负责维持性质.
    // 其中:
    // __x 新插入的节点
    // __root 根节点
    // 策略概述: 红黑树插入重新调整的策略已经在上篇中讲述,
    // 可以结合上篇文章和这里的代码注释,
    // 理解红黑树的插入算法.
    inline void
    _Rb_tree_rebalance(_Rb_tree_node_base* __x, _Rb_tree_node_base*& __root)
    {
        // 将新插入的节点染成红色
        __x->_M_color = _S_rb_tree_red;
    
        while (__x != __root && __x->_M_parent->_M_color == _S_rb_tree_red) {
            // __x 的父节点也是红色的情况. 提示: 如果是黑色节点, 不会破坏红黑树性质.
    
            if (__x->_M_parent == __x->_M_parent->_M_parent->_M_left) {
                // 叔父节点
                _Rb_tree_node_base* __y = __x->_M_parent->_M_parent->_M_right;
    
                if (__y && __y->_M_color == _S_rb_tree_red) {
                    // 第 1 种情况, N,P,U 都红(G 肯定黑).
                    // 策略: G->红, N,P->黑. 此时, G 红, 如果 G 的父亲也是红, 性质又被破坏了,
                    // HACK: 可以将 GPUN 看成一个新的红色 N 节点, 如此递归调整下去;
                    // 特俗的, 如果碰巧将根节点染成了红色, 可以在算法的最后强制 root->红.
                    __x->_M_parent->_M_color = _S_rb_tree_black;
                    __y->_M_color = _S_rb_tree_black;
                    __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
                    __x = __x->_M_parent->_M_parent;
                }
                else {
    
                    if (__x == __x->_M_parent->_M_right) {
                        // 第 2 种情况, P 为红, N 为 P 右孩子, U 为黑或缺少.
                        // 策略: 旋转变换, 从而进入下一种情况:
                        __x = __x->_M_parent;
                        _Rb_tree_rotate_left(__x, __root);
                    }
                    // 第 3 种情况, 可能由第二种变化而来, 但不是一定: P 为红, N 为红.
                    // 策略: 旋转, 交换 P,G 颜色, 调整后, 因为 P 为黑色, 所以不怕
                    // P 的父节点是红色的情况. over
                    __x->_M_parent->_M_color = _S_rb_tree_black;
                    __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
                    _Rb_tree_rotate_right(__x->_M_parent->_M_parent, __root);
                }
            }
            else { // 下面的代码是镜像得出的, 脑补吧.
                _Rb_tree_node_base* __y = __x->_M_parent->_M_parent->_M_left;
                if (__y && __y->_M_color == _S_rb_tree_red) {
                    __x->_M_parent->_M_color = _S_rb_tree_black;
                    __y->_M_color = _S_rb_tree_black;
                    __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
                    __x = __x->_M_parent->_M_parent;
                }
                else {
                    if (__x == __x->_M_parent->_M_left) {
                        __x = __x->_M_parent;
                        _Rb_tree_rotate_right(__x, __root);
                    }
                    __x->_M_parent->_M_color = _S_rb_tree_black;
                    __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
                    _Rb_tree_rotate_left(__x->_M_parent->_M_parent, __root);
                }
            }
        }
        __root->_M_color = _S_rb_tree_black;
    }
    
    // 删除算法, 直接调用底层的删除实现 _Rb_tree_rebalance_for_erase().
    template <class _Key, class _Value, class _KeyOfValue,class _Compare, class _Alloc>
    inline void _Rb_tree<_Key, _Value, _KeyOfValue, _Compare, _Alloc>::erase(iterator __position)
    {
        _Link_type __y =
            (_Link_type)_Rb_tree_rebalance_for_erase(__position._M_node,
                _M_header->_M_parent,
                _M_header->_M_left,
                _M_header->_M_right);
        destroy_node(__y);
        --_M_node_count;
    }
    
    // 删除节点底层实现, 删除可能会破坏红黑树性质,
    // _Rb_tree_rebalance()
    // 负责维持性质. 其中:
    // __z 需要删除的节点
    // __root 根节点
    // __leftmost 红黑树内部数据, 即最左子树
    // __rightmost 红黑树内部数据, 即最右子树
    // 策略概述: _Rb_tree_rebalance_for_erase() 会根据
    // 删除节点的位置在红黑树中找到顶替删除节点的节点,
    // 即无非是删除节点左子树的最大节点或右子树中的最小节点,
    // 此处用的是有一种策略. 接着, 会调整红黑树以维持性质.
    // 调整的算法已经在上篇文章中详述, 可以根据上篇文章的描述
    // 和此篇的代码注释, 加深对红黑树删除算法的理解.
    inline _Rb_tree_node_base* _Rb_tree_rebalance_for_erase(
        _Rb_tree_node_base* __z,
        _Rb_tree_node_base*& __root,
        _Rb_tree_node_base*& __leftmost,
        _Rb_tree_node_base*& __rightmost)
    {
        // __z 是要删除的节点
    
        // __y 最终会指向要删除的节点
        _Rb_tree_node_base* __y = __z;
        // N 节点
        _Rb_tree_node_base* __x = 0;
        // 记录 N 节点的父节点
        _Rb_tree_node_base* __x_parent = 0;
    
        // 只有一个孩子或者没有孩子的情况
        if (__y->_M_left == 0)     // __z has at most one non-null child. y == z.
            __x = __y->_M_right;     // __x might be null.
        else
            if (__y->_M_right == 0)  // __z has exactly one non-null child. y == z.
                __x = __y->_M_left;    // __x is not null.
    
              // 有两个非空孩子
            else {                   // __z has two non-null children.  Set __y to
                __y = __y->_M_right;   //   __z's successor.  __x might be null.
    
                // __y 取右孩子中的最小节点, __x 记录他的右孩子(可能存在右孩子)
                while (__y->_M_left != 0)
                    __y = __y->_M_left;
                __x = __y->_M_right;
            }
    
        // __y != __z 说明有两个非空孩子的情况,
        // 此时的删除策略就和文中提到的普通二叉搜索树删除策略一样:
        // __y 记录了 __z 右子树中最小的节点
        // __x 记录了 __y 的右孩子
        // 用 __y 顶替 __z 的位置, __x 顶替 __y 的位置, 最后用 __y 指向 __z,
        // 从而 __y 指向了要删除的节点
        if (__y != __z) {          // relink y in place of z.  y is z's successor
    
          // 将 __z 的记录转移至 __y 节点
            __z->_M_left->_M_parent = __y;
            __y->_M_left = __z->_M_left;
    
            // 如果 __y 不是 __z 的右孩子, __z->_M_right 有左孩子
            if (__y != __z->_M_right) {
    
                __x_parent = __y->_M_parent;
    
                // 如果 __y 有右孩子 __x, 必须有那个 __x 替换 __y 的位置
                if (__x)
                    // 替换 __y 的位置
                    __x->_M_parent = __y->_M_parent;
    
                __y->_M_parent->_M_left = __x;      // __y must be a child of _M_left
                __y->_M_right = __z->_M_right;
                __z->_M_right->_M_parent = __y;
            }
            // __y == __z->_M_right
            else
                __x_parent = __y;
    
            // 如果 __z 是根节点
            if (__root == __z)
                __root = __y;
    
            // __z 是左孩子
            else if (__z->_M_parent->_M_left == __z)
                __z->_M_parent->_M_left = __y;
    
            // __z 是右孩子
            else
                __z->_M_parent->_M_right = __y;
    
            __y->_M_parent = __z->_M_parent;
            // 交换需要删除节点 __z 和 替换节点 __y 的颜色
            __STD::swap(__y->_M_color, __z->_M_color);
            __y = __z;
            // __y now points to node to be actually deleted
        }
        // __y != __z 说明至多一个孩子
        else {                        // __y == __z
            __x_parent = __y->_M_parent;
            if (__x) __x->_M_parent = __y->_M_parent;
    
            // 将 __z 的父亲指向 __x
            if (__root == __z)
                __root = __x;
            else
                if (__z->_M_parent->_M_left == __z)
                    __z->_M_parent->_M_left = __x;
                else
                    __z->_M_parent->_M_right = __x;
    
            // __leftmost 和 __rightmost 是红黑树的内部数据, 因为 __z 可能是
            // __leftmost 或者 __rightmost, 因此需要更新.
            if (__leftmost == __z)
                if (__z->_M_right == 0)        // __z->_M_left must be null also
                  // __z 左右孩子都为空, 没有孩子
                    __leftmost = __z->_M_parent;
            // makes __leftmost == _M_header if __z == __root
                else
                    __leftmost = _Rb_tree_node_base::_S_minimum(__x);
    
            if (__rightmost == __z)
                if (__z->_M_left == 0)         // __z->_M_right must be null also
                    __rightmost = __z->_M_parent;
            // makes __rightmost == _M_header if __z == __root
                else                      // __x == __z->_M_left
                    __rightmost = _Rb_tree_node_base::_S_maximum(__x);
    
            // __y 同样已经指向要删除的节点
        }
    
        // __y 指向要删除的节点
        // __x 即为 N 节点
        // __x_parent 指向 __x 的父亲, 即 N 节点的父亲
        if (__y->_M_color != _S_rb_tree_red) {
            // __y 的颜色为黑色的时候, 会破坏红黑树性质
    
            while (__x != __root && (__x == 0 || __x->_M_color == _S_rb_tree_black))
                // __x 不为红色, 即为空或者为黑. 提示: 如果 __x 是红色, 直接将 __x 替换成黑色
    
                if (__x == __x_parent->_M_left) { // 如果 __x 是左孩子
    
                    _Rb_tree_node_base* __w = __x_parent->_M_right; // 兄弟节点
    
                    if (__w->_M_color == _S_rb_tree_red) {
                        //第 2 情况, S 红, 根据红黑树性质P,SL,SR 一定黑.
                        // 策略: 旋转, 交换 P,S 颜色.
    
                        __w->_M_color = _S_rb_tree_black;
                        __x_parent->_M_color = _S_rb_tree_red; // 交换颜色
                        _Rb_tree_rotate_left(__x_parent, __root); // 旋转
                        __w = __x_parent->_M_right; // 调整关系
                    }
    
                    if ((__w->_M_left == 0 ||
                        __w->_M_left->_M_color == _S_rb_tree_black) &&
                        (__w->_M_right == 0 ||
                            __w->_M_right->_M_color == _S_rb_tree_black)) {
                        // 提示: 这是 第 1 情况和第 2.1 情况的合并, 因为处理的过程是一样的.
                        // 但他们的情况还是要分门别类的. 已经在文章中详细支出,
                        // 似乎大多数的博文中没有提到这一点.
    
                        // 第 1 情况, N,P,S,SR,SL 都黑.
                        // 策略: S->红. 通过 PN,PS 的黑色节点数量相同了, 但会比其他路径多一个,
                        // 解决的方法是在 P 上从情况 0 开始继续调整.
                        // 为什么要这样呢? HACKS: 因为既然 PN,PS
                        // 路径上的黑节点数量相同而且比其他路径会少一个黑节点,
                        // 那何不将其整体看成了一个 N 节点! 这是递归原理.
    
                        // 第 2.1 情况, S,SL,SR 都黑.
                        // 策略: P->黑. S->红, 因为通过 N 的路径多了一个黑节点,
                        // 通过 S 的黑节点个数不变, 所以维持了性质 5. over
    
                        // 可能大家会有疑问, 不对啊, 2.1 的情况,
                        // 策略是交换父节点和兄弟节点的颜色, 此时怎么没有对父节点的颜色赋值呢?
                        // HACKS: 这就是合并情况的好处, 因为就算此时父节点是红色,
                        // 而且也将兄弟节点颜色改为红色, 你也可以将 PS,PN 看成一个红色的 N 节点,
                        // 这样在下一个循环当中, 这个 N 节点也会变成黑色. 因为此函数最后有一句话:
                        // if (__x) __x->_M_color = _S_rb_tree_black;
                        // 合并情况, 节省代码量
    
                        // 当然是可以分开写的
    
                        // 兄弟节点染成黑色
                        __w->_M_color = _S_rb_tree_red;
    
                        // 调整关系
                        __x = __x_parent;
                        __x_parent = __x_parent->_M_parent;
                    }
                    else {
                        if (__w->_M_right == 0 ||
                            __w->_M_right->_M_color == _S_rb_tree_black) {
                            // 第 2.2.1 情况, S,SR 黑, SL 红.
                            // 策略: 旋转, 变换 SL,S 颜色.
    
                            if (__w->_M_left) __w->_M_left->_M_color = _S_rb_tree_black;
                            __w->_M_color = _S_rb_tree_red;
                            _Rb_tree_rotate_right(__w, __root);
    
                            // 调整关系
                            __w = __x_parent->_M_right;
                        }
    
                        // 第 2.2.2 情况, S 黑, SR 红.
                        // 策略: 旋转, 交换 S,P 颜色, SR->黑色, 重新获得平衡.
                        __w->_M_color = __x_parent->_M_color;
                        __x_parent->_M_color = _S_rb_tree_black;
                        if (__w->_M_right) __w->_M_right->_M_color = _S_rb_tree_black;
                        _Rb_tree_rotate_left(__x_parent, __root);
                        break;
                    }                        // 下面的代码是镜像得出的, 脑补吧.
                }
                else {                  // same as above, with _M_right <-> _M_left.
                    _Rb_tree_node_base* __w = __x_parent->_M_left;
                    if (__w->_M_color == _S_rb_tree_red) {
                        __w->_M_color = _S_rb_tree_black;
                        __x_parent->_M_color = _S_rb_tree_red;
                        _Rb_tree_rotate_right(__x_parent, __root);
                        __w = __x_parent->_M_left;
                    }
                    if ((__w->_M_right == 0 ||
                        __w->_M_right->_M_color == _S_rb_tree_black) &&
                        (__w->_M_left == 0 ||
                            __w->_M_left->_M_color == _S_rb_tree_black)) {
                        __w->_M_color = _S_rb_tree_red;
                        __x = __x_parent;
                        __x_parent = __x_parent->_M_parent;
                    }
                    else {
                        if (__w->_M_left == 0 ||
                            __w->_M_left->_M_color == _S_rb_tree_black) {
                            if (__w->_M_right) __w->_M_right->_M_color = _S_rb_tree_black;
                            __w->_M_color = _S_rb_tree_red;
                            _Rb_tree_rotate_left(__w, __root);
                            __w = __x_parent->_M_left;
                        }
                        __w->_M_color = __x_parent->_M_color;
                        __x_parent->_M_color = _S_rb_tree_black;
                        if (__w->_M_left) __w->_M_left->_M_color = _S_rb_tree_black;
                        _Rb_tree_rotate_right(__x_parent, __root);
                        break;
                    }
                }
            if (__x) __x->_M_color = _S_rb_tree_black;
        }
        return __y;
    }

    3.6各种操作的时间复杂度

      查找、插入、删除:O(logn)

      插入和删除都要先查找,所以是O(logn)

    3.7与AVL平衡二叉搜索树的比较

    从操作复杂度看:AVL树和红黑树查找、插入、删除都是O(logn)

    进一步考虑:

    1)查找:红黑树牺牲了严格平衡,但是保证了最长路径不大于最短路径的两倍,所以查找某个节点也可以保证O(logn);但是对于大规模数据的查找,AVL树的严格平衡显然使得查询效率更高(查找上:AVL树胜)

    2)插入:如果是插入一个节点引起的不平衡,AVL树和红黑树都是最多通过2次旋转来修正,两者均是O(1)。如果新节点的父结点为黑色,那么插入一个红点将不会影响红黑树的平衡。但红黑树这种黑父的情况比较常见,从而使红黑树需要旋转的概率0比AVL树小

    3)删除:如果删除某个节点引起了不平衡,AVL树需要维护被删除节点到根节点的整条路径上所有节点的平衡性,需要O(logn);而红黑树最多只需3次旋转,只需要O(1)

    所以说,在大规模数据插入或者删除时,AVL树的效率不如红黑树(插入和删除上:红黑树胜)

    4.多路查找树

    1)多路查找树的每一个节点的儿子可以多于两个,且每一个节点可以存储多个元素

    2)常见的多路查找树有:2-3树、2-3-4树、B树、B+树

    3)为什么需要多路查找树:

    • 当要处理的数据非常多,多到内存已经装不下,就要把数据存储在磁盘上,从磁盘中把数据读入内存进行处理
    • 磁盘I/O操作的基本单位为块,从磁盘上读取数据时,会把包含数据的整个块读入内存,读取磁盘数据的过程:磁头移动到包含所请求数据的磁盘位置上方,然后磁盘旋转至相应位置,磁头将下方的整个块传读入内存:

    磁盘数据访问时间= 寻道时间 + 转动延迟 + 数据传送时间

    • 如果用平衡树(AVL,RB)作为在磁盘上存储数据的结构,那么平衡树的节点可能不均匀地分布在磁盘的不同块上,这样访问磁盘数据的时间就会显著增加:

    • 而B树(一种平衡的多路查找树)有一个重要的特点是每个节点的大小与磁盘块的大小相等,同时每个节点可以存储多个元素,元素的数量可以根据磁盘块的大小而定,这样每次加载一个块就能完整覆盖一个节点以便选择下一层子树,大大加快磁盘数据的访问速度

    参考资料:

    https://blog.csdn.net/bit_clearoff/article/details/53197436

    5.2-3树

    1)2-3树有2节点和3节点

    2)2节点包含一个元素和两个儿子(要么没有儿子,要么就有两个儿子),每个节点一定大于等于其左子树中每一个节点,小于等于其右子树每一个节点

    3)3节点包含一小一大两个元素三个儿子(要么没有儿子,要么就有三个儿子),左子树包含小于较小元素的元素,右子树包含大于较大元素的元素,中间子树包含介于两元素之间的元素

    4)所有叶子节点在同一层

    6.2-3-4树

    1)2-3-4树在2-3-4树的基础上多了一种4节点

    2)4节点包含小中大三个元素四个儿子(要么没有儿子,要么就有四个儿子),子树元素大小排列规则类似于3节点

    3)所有叶子节点在同一层

    7.B树

    1)B树是一种平衡的多路查找树,2-3树和2-3-4树都是B树的特例

    2)节点的最大孩子数称为B树的阶

    3)m阶B树具有以下性质:

    • 根节点如果不是叶子节点,则至少有两个儿子
    • 每个分支节点都有k-1元素和k个儿子,元素以升序排列,⌈ m/2 ⌉ <= k <= m
    • 所有的叶子节点都在同一层
    • key [i]和key [i+ 1 ]之间的儿子节点的元素介于key [i]、key [i+ 1 ]之间

    4)B树的查找过程是一个查找节点和遍历节点上的所有元素的交叉过程

    8.B+树

    1)B树的查找过程是一个交叉过程,为了加快B树的遍历效率,产生了B树的变形:B+树

    2)n阶B+树具有以下性质:

    • 根节点如果不是叶子节点,则至少有两个儿子
    • 每个分支节点都有k元素和k个儿子,⌈ m / 2 ⌉ <= k <= m
    • 所有的叶子节点都在同一层
    • 分支节点仅仅具有索引功能,它存储的元素不包含实际的值,而是其子树最大的(或最小的)关键字,仅仅是用来定位,用来索引,所有的值均存在叶子节点中
    • 同一层的叶子节点构成一个升序链表,每个叶子节点里的元素也从小到大排列

    3)B+树的查找过程:

    4)B+树特别适合带有范围的查找,比如查找18~22岁学生,可以从根节点出发找到第一个18岁的学生,然后在叶子层按顺序结合链表同层查找即可

    5)为什么说B+树比B树更适合数据库索引:

    • 基于范围的查询是非常频繁的,B+树的区间访问能力强,不用回溯到分支节点,直接在叶子节点这一层完成操作
    • B+树磁盘读写的代价更低:B+树的节点少了很多指向儿子的指针,因此其节点能存放更多的关键字,那么一次性读入内存的的关键字也就越多,相对IO读写次数就降低了,效率提高

    参考资料:

    https://www.cnblogs.com/vincently/p/4526560.html

  • 相关阅读:
    php基础
    MYSQL 常用函数
    MYSQL 练习题
    MYSQL 查询
    MYSQL:增删改
    隐藏导航
    分层导航
    图片轮播!
    你帅不帅?
    PHP 流程
  • 原文地址:https://www.cnblogs.com/Joezzz/p/9610861.html
Copyright © 2020-2023  润新知