数据结构与算法——高级搜索树(伸展树,B-树,红黑树)(C++)

1 伸展树

1.1 伸展树类

二叉搜索树的数据局部性体现在:
(1)刚刚被访问过的节点,极有可能在不久后再次被访问到;
(2)将被访问的下一节点,极有可能就处于之前被访问过的某个节点附近

由于伸展树实现了“即用即前移”的伸展策略,伸展树非常符合于局部性原理。伸展树的伸展策略包括以下6种:zig-zig, zig-zag, zag-zig, zag-zag, zag, zig。

以下是伸展树基于继承BST实现的类

/*
* head file of SPLAY TREE
* author@Ripples
* 20200728
*/
#include "../dsa_BST_20200726/BST.h"
#include <iostream>
template <typename T> class Splay : public BST<T> {
    protected:
        BinNodePosi(T) splay (BinNodePosi(T) v);//将节点伸展到根
    public:
        BinNodePosi(T) & search( const T& e );//查找
        BinNodePosi(T) insert( const T& e );//插入
        bool remove ( const T& e );//删除
};
//查找
template <typename T> BinNodePosi(T) & Splay<T>::search( const T& e ){
    BinNodePosi(T) p = searchIn( this->_root, e, this->_hot = nullptr );
    this->_root = splay ( p ? p : this->_hot );
    return this->_root;
}
//插入
template <typename T> BinNodePosi(T) Splay<T>::insert ( const T& e ){
    if ( !this->_root ) { this->_size ++; return this->_root = new BinNode<T> ( e ); }//处理原树为空的退化情况
    if ( e == search( e )->data ) return this->_root;//已经有这个数的情况
    this->_size++; BinNodePosi(T) t = this->_root;
    if ( this->_root->data < e ){
        t -> parent = this->_root = new BinNode<T> ( e, nullptr, t, t->rc );
        if ( HasRChild ( *t )) { t->rc->parent = this->_root; t->rc = nullptr; }
    }else{
        t->parent = this->_root = new BinNode<T> ( e, nullptr, t->lc, t );
        if ( HasLChild ( *t )) { t->lc->parent = this->_root; t->lc = nullptr; }
    }
    BST<T>::updateHeightAbove ( t );
    return this->_root;
}
//删除
template <typename T> bool Splay<T>::remove( const T& e ){
    if (!this->_root || e != search(e)->data)  return false; 
    auto t = this->_root;
    if (!HasLChild( *(this->_root))) { // 若无左子树,直接删除
        this->_root = this->_root->rc; // 以右子树为树根
        if (this->_root) { this->_root->parent = nullptr; } // 更新右子树父节点为null
    } else if (!HasRChild( *(this->_root))) { // 若无右子树,直接删除
        this->_root = this->_root->lc; // 以左子树为树根
        if (this->_root) { this->_root->parent = nullptr; } // 更新左子树父节点为null
    } else { // 左、右子树同时存在
        // 暂时切除左子树
        auto ltree = this->_root->lc;
        ltree->parent = nullptr;
        this->_root->lc = nullptr;

        // 只保留右子树
        this->_root = this->_root->rc;
        this->_root->parent = nullptr;
        
        // 以原树根为目标,做一次(必定失败的)查找
        search(t->data);

        // 至此,右子树中最小节点必伸展至根,且(因无雷同节点)其左子树必空,于是只需将原左子树接回原位即可
        this->_root->lc = ltree;
        ltree->parent = this->_root;
    }

    // 释放节点,更新规模
    dtl::release(t->data); dtl::release(t); 
    this->_size--;

    // 此后,若树非空,则树根的高度需要更新
    if (this->_root) {
        this->updateHeight(this->_root);
    }

    return true;
}

template <typename NodePosi> inline
void attachAsLChild ( NodePosi p, NodePosi lc ) { p->lc = lc; if(lc) lc->parent = p; }//节点*p与*lc建立父(左)子关系
template <typename NodePosi> inline
void attachAsRChild ( NodePosi p, NodePosi rc ) { p->rc = rc; if(rc) rc->parent = p; }//节点*p与*rc建立父(右)子关系

//伸展算法的实现
template <typename T>
BinNodePosi(T) Splay<T>::splay ( BinNodePosi(T) v ){
    if( !v ) return nullptr; BinNodePosi(T) p; BinNodePosi(T) g;
    while( ( p = v->parent ) && ( g = p->parent )){//祖父与父亲都安在,双层伸展
        BinNodePosi(T) gg = g->parent;
        if( v->data < p->data)
            if( p->data < g->data){//zig-zig 
                attachAsLChild ( g, p->rc ); attachAsLChild( p, v->rc );
                attachAsRChild ( p, g ); attachAsRChild( v, p );
                std::cout << "here0" << p->data << g->data<< std::endl;
            }else{//zig-zag
                std::cout << "here1" <<std::endl;
                attachAsLChild ( p, v->rc ); attachAsRChild( g, v->lc );
                attachAsLChild ( v, g ); attachAsRChild( v, p );
            }
        else 
            if( p->data >= g->data){//zag-zag
                std::cout << "here2" <<std::endl;
                attachAsRChild ( g, p->lc ); attachAsRChild( p, v->lc );
                attachAsLChild ( p, g ); attachAsLChild( v, p );
            }else{//zag-zig
                std::cout << "here3" <<std::endl;
                attachAsRChild ( p, v->lc ); attachAsLChild( g, v->rc );
                attachAsRChild ( v, g ); attachAsLChild( v, p );
        }
        if ( !gg ) v->parent = NULL;
        else
            ( g == gg->lc ) ? attachAsLChild( gg, v ) : attachAsRChild( gg, v );
        BST<T>::updateHeight ( g ); BST<T>::updateHeight( p ); BST<T>::updateHeight( v );
    }
    if ( p = v->parent ){//只有父亲安在
        if (  v->data < p->data) { attachAsLChild( p, v->rc ) ; attachAsRChild( v, p ); }
        else                     { attachAsRChild( p, v->lc ) ; attachAsLChild( v, p ); }
        BST<T>::updateHeight( p ); BST<T>::updateHeight( v );
    }
    v->parent = NULL; return v;
}

1.2 伸展树测试

测试用例及测试结果见下面主程序的注释。

/* 
* test program on SPLAY TREE
* author@Ripples
* 20200728
*/
#include "splay.h"
#include <iostream>
using namespace std;

template<typename T> void returnValue(T& a)
{
    cout << "return_value: " << a << endl;
}



int main(){
    Splay<int> bt_test;

	bt_test.insertAsRoot(7);
	BinNodePosi(int) root = bt_test.root();
	int i;
	const int a[7] = { 0, 6, 5, 4, 3, 2, 1};
	for( i = 1; i < 7; ++i ){
		bt_test.insertAsLC( root, 7-i );
		root = root->lc;
	}

	//测试用例
	/* 								7
							6
						5
					4
				3
			2
		1
	*/
	bt_test.search(1);//先序遍历的搜索结果为1642357,与推算结果符合
	/* 	
			1						
							6			
						4		7
				2			5
			 		3
	*/
	bt_test.insert(0);//先序遍历的搜索结果为01642357,与推算结果符合
	/* 	
	0
			1						
							6			
						4		7
				2			5
			 		3
	*/
	//bt_test.remove(59);//删除
	bt_test.remove(1);//先序遍历的搜索结果为2043657,与推算结果符合
	/* 	
		2
	0				4						
				3			6			
						5		7
							
			 		
	*/
	void (* visit)(int& ) = &returnValue;
	bt_test.traverse(bt_test.root(),visit);
	return 0;
    
}

1.3 伸展树总结

伸展算法是伸展树数据结构的核心,其功能包括降低树高求效率,与数据集中于头部符合局部性。

2 B树

2.1 BTNode结构体

B树的节点与二叉树不同,其主要成员包括父亲节点,孩子向量与关键码向量。

#include "../dsa_vector_200622/dsa_vector.h"
#define BTNodePosi(T) BTNode<T>*

template <typename T> struct BTNode {
//成员
    BTNodePosi(T) parent;//父亲节点
    dtl::Vector<T> key;//关键码向量
    dtl::Vector<BTNodePosi(T)> child;//孩子向量
//构造函数
    BTNode() { parent = nullptr; child.insert ( 0, nullptr );}
    BTNode( T e, BTNodePosi(T) lc = nullptr, BTNodePosi(T) rc = nullptr){
        parent = nullptr;
        key.insert( 0, e );//一个关键码
        child.insert( 0, lc );child.insert( 1, rc );//两个孩子
        if ( lc ) lc->parent = this; if( rc ) rc->parent = this;
    }
};

2.2 BTree类

BTree类实现查找、插入与删除,以及与之匹配的上溢与下溢处理。

#include "btnode.h"
#include "../dsa_bintree_20200720/release.h"

template <typename T> class BTree{
protected:
    int _size;//关键码总数
    int _order;//B树的阶数
    BTNodePosi(T) _root;
    BTNodePosi(T) _hot;//BTree::search()最后访问的非空的节点位置
    void solveOverflow( BTNodePosi(T) );//因插入而上溢后的分裂处理
    void solveUnderflow( BTNodePosi(T) );//因删除而下溢后的合并处理
public:
    BTree ( int order = 3 ) : _order ( order ), _size( 0 ) { _root = new BTNode<T>(); }
    ~BTree() { if( _root ) dtl::release ( _root );}
    int const order() { return _order; }
    int const size() { return _size; }
    BTNodePosi(T) & root() { return _root; }
    bool empty() const{ return !_root; }
    BTNodePosi(T) search( const T& e );
    bool insert( const T& e );
    bool remove( const T& e );
};//BTree

//查找
template <typename T> BTNodePosi(T) BTree<T>::search ( const T& e ){
    BTNodePosi(T) v = _root; _hot = nullptr;
    while( v ){
        Rank r = v->key.search( e );
        if( ( 0 <= r ) && ( e == v->key[r] )) return v;
        _hot = v; v = v->child[ r + 1 ];
    }
    return nullptr;
}
//插入
template <typename T> bool BTree<T>::insert( const T& e ){
    BTNodePosi(T) v = search( e ); if( v ) return false;
    Rank r = _hot->key.search( e );
    _hot->key.insert( r + 1, e );
    _hot->child.insert( r + 2, nullptr );
    _size ++;
    solveOverflow( _hot );
    return true;
}

//上溢处理
template <typename T> void BTree<T>::solveOverflow( BTNodePosi(T) v ){
    if ( _order >= v->child.size() ) return;//递归基
    Rank s = _order / 2;
    BTNodePosi(T) u = new BTNode<T>();
    for( Rank j = 0; j < _order - s - 1; ++j ){
        u->child.insert( j, v->child.remove( s + 1 ));
        u->key.insert( j, v->key.remove( s + 1 ));
    }
    u->child[ _order - s - 1 ] = v->child.remove( s + 1 );
    if ( u->child[0] )
        for ( Rank j = 0; j < _order - s; ++j )
            u->child[j]->parent = u;
    BTNodePosi(T) p = v->parent;
    if( !p ) { _root = p = new BTNode<T>(); p->child[0] = v; v->parent = p; }
    Rank r = 1 + p->key.search( v->key[0] );
    p->key.insert( r, v->key.remove( s ));
    p->child.insert( r + 1, u ); u->parent = p;
    solveOverflow( p );
}

//删除
template <typename T> bool BTree<T>::remove ( const T& e ) { //从BTree树中删除关键码e
   BTNodePosi(T) v = search ( e ); if ( !v ) return false; //确认目标关键码存在
   Rank r = v->key.search ( e ); //确定目标关键码在节点v中的秩(由上,肯定合法)
   if ( v->child[0] ) { //若v非叶子,则e的后继必属于某叶节点
      BTNodePosi(T) u = v->child[r+1]; //在右子树中一直向左,即可
      while ( u->child[0] ) u = u->child[0]; //找出e的后继
      v->key[r] = u->key[0]; v = u; r = 0; //并与之交换位置
   } //至此,v必然位于最底层,且其中第r个关键码就是待删除者
   v->key.remove ( r ); v->child.remove ( r + 1 ); _size--; //删除e,以及其下两个外部节点之一
   solveUnderflow ( v ); //如有必要,需做旋转或合并
   return true;
}

//节点下溢算法
template <typename T> //关键码删除后若节点下溢,则做节点旋转或合并处理
void BTree<T>::solveUnderflow ( BTNodePosi(T) v ) {
   if ( ( _order + 1 ) / 2 <= v->child.size() ) return; //递归基:当前节点并未下溢
   BTNodePosi(T) p = v->parent;
   if ( !p ) { //递归基:已到根节点,没有孩子的下限
      if ( !v->key.size() && v->child[0] ) {
         //但倘若作为树根的v已不含关键码,却有(唯一的)非空孩子,则
         _root = v->child[0]; _root->parent = NULL; //这个节点可被跳过
         v->child[0] = NULL; dtl::release ( v ); //并因不再有用而被销毁
      } //整树高度降低一层
      return;
   }
   Rank r = 0; while ( p->child[r] != v ) r++;
   //确定v是p的第r个孩子——此时v可能不含关键码,故不能通过关键码查找
   //另外,在实现了孩子指针的判等器之后,也可直接调用Vector::find()定位

// 情况1:向左兄弟借关键码
   if ( 0 < r ) { //若v不是p的第一个孩子,则
      BTNodePosi(T) ls = p->child[r - 1]; //左兄弟必存在
      if ( ( _order + 1 ) / 2 < ls->child.size() ) { //若该兄弟足够“胖”,则
         v->key.insert ( 0, p->key[r - 1] ); //p借出一个关键码给v(作为最小关键码)
         p->key[r - 1] = ls->key.remove ( ls->key.size() - 1 ); //ls的最大关键码转入p
         v->child.insert ( 0, ls->child.remove ( ls->child.size() - 1 ) );
         //同时ls的最右侧孩子过继给v
         if ( v->child[0] ) v->child[0]->parent = v; //作为v的最左侧孩子
         return; //至此,通过右旋已完成当前层(以及所有层)的下溢处理
      }
   } //至此,左兄弟要么为空,要么太“瘦”
// 情况2:向右兄弟借关键码
   if ( p->child.size() - 1 > r ) { //若v不是p的最后一个孩子,则
      BTNodePosi(T) rs = p->child[r + 1]; //右兄弟必存在
      if ( ( _order + 1 ) / 2 < rs->child.size() ) { //若该兄弟足够“胖”,则
         v->key.insert ( v->key.size(), p->key[r] ); //p借出一个关键码给v(作为最大关键码)
         p->key[r] = rs->key.remove ( 0 ); //ls的最小关键码转入p
         v->child.insert ( v->child.size(), rs->child.remove ( 0 ) );
         //同时rs的最左侧孩子过继给v
         if ( v->child[v->child.size() - 1] ) //作为v的最右侧孩子
            v->child[v->child.size() - 1]->parent = v;
         return; //至此,通过左旋已完成当前层(以及所有层)的下溢处理
      }
   } //至此,右兄弟要么为空,要么太“瘦”
// 情况3:左、右兄弟要么为空(但不可能同时),要么都太“瘦”——合并
   if ( 0 < r ) { //与左兄弟合并
      BTNodePosi(T) ls = p->child[r - 1]; //左兄弟必存在
      ls->key.insert ( ls->key.size(), p->key.remove ( r - 1 ) ); p->child.remove ( r );
      //p的第r - 1个关键码转入ls,v不再是p的第r个孩子
      ls->child.insert ( ls->child.size(), v->child.remove ( 0 ) );
      if ( ls->child[ls->child.size() - 1] ) //v的最左侧孩子过继给ls做最右侧孩子
         ls->child[ls->child.size() - 1]->parent = ls;
      while ( !v->key.empty() ) { //v剩余的关键码和孩子,依次转入ls
         ls->key.insert ( ls->key.size(), v->key.remove ( 0 ) );
         ls->child.insert ( ls->child.size(), v->child.remove ( 0 ) );
         if ( ls->child[ls->child.size() - 1] ) ls->child[ls->child.size() - 1]->parent = ls;
      }
      dtl::release ( v ); //释放v
   } else { //与右兄弟合并
      BTNodePosi(T) rs = p->child[r + 1]; //右兄度必存在
      rs->key.insert ( 0, p->key.remove ( r ) ); p->child.remove ( r );
      //p的第r个关键码转入rs,v不再是p的第r个孩子
      rs->child.insert ( 0, v->child.remove ( v->child.size() - 1 ) );
      if ( rs->child[0] ) rs->child[0]->parent = rs; //v的最左侧孩子过继给ls做最右侧孩子
      while ( !v->key.empty() ) { //v剩余的关键码和孩子,依次转入rs
         rs->key.insert ( 0, v->key.remove ( v->key.size() - 1 ) );
         rs->child.insert ( 0, v->child.remove ( v->child.size() - 1 ) );
         if ( rs->child[0] ) rs->child[0]->parent = rs;
      }
      dtl::release ( v ); //释放v
   }
   solveUnderflow ( p ); //上升一层,如有必要则继续分裂——至多递归O(logn)层
   return;
}

2.3 测试

测试的程序如下所示,从注释中可以清楚看出在插入过程中的上溢处理,以及删除过程中的下溢处理。

#include "btree.h"
#include <iostream>

using namespace std;

int main(){
    BTree<int> bt;
    bt.insert( 1 );
    /*
    *     1
    */
    bt.insert( 2 );
    /*
    *     1 2
    */
    bt.insert( 3 );
    /*
    *       2
    *   1       3
    */
    bt.insert( 4 );
    cout << bt.root() ->child[1]->key[0] << endl;//3
    /*
    *       2
    *   1      3 4
    */

    bt.insert( 5 );
    cout << bt.root() ->child[1]->key[0] << endl;//3
    /*
    *       2 4
    *   1    3    5
    */    
    bt.insert( 6 );
    cout << bt.root() ->child[2]->key[0] << endl;//5
    /*
    *       2 4
    *   1    3    5 6
    */   
    cout << "-----------"<< endl;
    bt.remove( 3 );
    cout << bt.root() ->child[1]->key[0] << endl;//4
    cout << bt.root()->child[2]->key[0] << endl;//6
     /*
    *       2 5
    *   1    4    6
    */   
}


2.4 B-树总结

B-树是一个强大的数据结构,可以大大提高操作效率。其中伴随插入的节点上溢处理与伴随删除的节点下溢处理是重点。

3 红黑树

3.1 红黑树简介

由红黑节点组成的二叉搜索树若满足以下条件,即为红黑树:
(1)树根始终为黑色;
(2)外部节点均为黑色;
(3)其余节点若为红色,则其孩子节点必为黑色;
(4)从任一外部节点到根节点的沿途,黑节点的数目相等。
红黑树与4阶B-树之间有着密切的联系,经过适当转换之后,二者相互等价!

3.2 红黑树类的实现

红黑树继承了BST类,在此基础上改写了插入与删除操作。高度更新也发生了改变,变为了黑高度的更新。此外,伴随着插入的双红修正与伴随着删除的双黑修正是类实现过程的重点。

#include "../dsa_BST_20200726/BST.h"
#define IsBlack( p ) ( ! (p) || ( RB_BLACK == (p)->color ))
#define IsRed(p) ( ! IsBlack(p) )
#define BlackHeightUpdated(x) (( stature( (x).lc ) == stature( (x).rc )) && ( (x).height == ( IsRed( &x ) ? stature( (x).lc ) : stature((x).lc) + 1)))
template <typename T> class RedBlack : public BST<T> { //RedBlack树模板类
protected:
   void solveDoubleRed ( BinNodePosi(T) x ); //双红修正
   void solveDoubleBlack ( BinNodePosi(T) x ); //双黑修正
   int updateHeight ( BinNodePosi(T) x ); //更新节点x的高度
public:
   BinNodePosi(T) insert ( const T& e ); //插入(重写)
   bool remove ( const T& e ); //删除(重写)
// BST::search()等其余接口可直接沿用
};

template <typename T> int RedBlack<T>::updateHeight ( BinNodePosi(T) x ) { //更新节点高度
   x->height = max ( stature ( x->lc ), stature ( x->rc ) ); //孩子一般黑高度相等,除非出现双黑
   /*DSA*/// 红黑树中各节点左、右孩子的黑高度通常相等
   /*DSA*/// 这里之所以取更大值,是便于在删除节点后的平衡调整过程中,正确更新被删除节点父亲的黑高度
   /*DSA*/// 否则,rotateAt()会根据被删除节点的替代者(高度小一)设置父节点的黑高度
   return IsBlack ( x ) ? x->height++ : x->height; //若当前节点为黑,则计入黑深度
} //因统一定义stature(NULL) = -1,故height比黑高度少一,好在不致影响到各种算法中的比较判断

template <typename T> BinNodePosi(T) RedBlack<T>::insert ( const T& e ) { //将e插入红黑树
   BinNodePosi(T) & x = search ( e ); if ( x ) return x; //确认目标不存在(留意对_hot的设置)
   x = new BinNode<T> ( e, this->_hot, NULL, NULL, -1 ); this->_size++; //创建红节点x:以_hot为父,黑高度-1
   solveDoubleRed ( x ); return x ? x : this->_hot->parent; //经双红修正后,即可返回
} //无论e是否存在于原树中,返回时总有x->data == e

/******************************************************************************************
 * RedBlack双红调整算法:解决节点x与其父均为红色的问题。分为两大类情况:
 *    RR-1:2次颜色翻转,2次黑高度更新,1~2次旋转,不再递归
 *    RR-2:3次颜色翻转,3次黑高度更新,0次旋转,需要递归
 ******************************************************************************************/
template <typename T> void RedBlack<T>::solveDoubleRed ( BinNodePosi(T) x ) { //x当前必为红
   if ( IsRoot ( *x ) ) //若已(递归)转至树根,则将其转黑,整树黑高度也随之递增
      {  this->_root->color = RB_BLACK; this->_root->height++; return;  } //否则,x的父亲p必存在
   BinNodePosi(T) p = x->parent; if ( IsBlack ( p ) ) return; //若p为黑,则可终止调整。否则
   BinNodePosi(T) g = p->parent; //既然p为红,则x的祖父必存在,且必为黑色
   BinNodePosi(T) u = uncle ( x ); //以下,视x叔父u的颜色分别处理
   if ( IsBlack ( u ) ) { //u为黑色(含NULL)时 //*DSA*/printf("  case RR-1:\n");
      if ( IsLChild ( *x ) == IsLChild ( *p ) ) //若x与p同侧(即zIg-zIg或zAg-zAg),则
         p->color = RB_BLACK; //p由红转黑,x保持红
      else //若x与p异侧(即zIg-zAg或zAg-zIg),则
         x->color = RB_BLACK; //x由红转黑,p保持红
      g->color = RB_RED; //g必定由黑转红
/ 以上虽保证总共两次染色,但因增加了判断而得不偿失
/ 在旋转后将根置黑、孩子置红,虽需三次染色但效率更高
      BinNodePosi(T) gg = g->parent; //曾祖父(great-grand parent)
      BinNodePosi(T) r = FromParentTo ( *g ) = rotateAt ( x ); //调整后的子树根节点
      r->parent = gg; //与原曾祖父联接
   } else { //若u为红色 //*DSA*/printf("  case RR-2:\n");
      p->color = RB_BLACK; p->height++; //p由红转黑
      u->color = RB_BLACK; u->height++; //u由红转黑
      if ( !IsRoot ( *g ) ) g->color = RB_RED; //g若非根,则转红
      solveDoubleRed ( g ); //继续调整g(类似于尾递归,可优化为迭代形式)
   }
}


template <typename T> bool RedBlack<T>::remove ( const T& e ) { //从红黑树中删除关键码e
   BinNodePosi(T) & x = search ( e ); if ( !x ) return false; //确认目标存在(留意_hot的设置)
   BinNodePosi(T) r = removeAt ( x, this->_hot ); if ( ! ( --this->_size ) ) return true; //实施删除
// assert: _hot某一孩子刚被删除,且被r所指节点(可能是NULL)接替。以下检查是否失衡,并做必要调整
   if ( ! this->_hot ) //若刚被删除的是根节点,则将其置黑,并更新黑高度
      { this->_root->color = RB_BLACK; updateHeight ( this->_root ); return true; }
// assert: 以下,原x(现r)必非根,_hot必非空
   if ( BlackHeightUpdated ( *this->_hot ) ) return true; //若所有祖先的黑深度依然平衡,则无需调整
   if ( IsRed ( r ) ) //否则,若r为红,则只需令其转黑
      { r->color = RB_BLACK; r->height++; return true; }
// assert: 以下,原x(现r)均为黑色
   //*DSA*/printBinTree(_hot, 0, 0);
   solveDoubleBlack ( r ); return true; //经双黑调整后返回
} //若目标节点存在且被删除,返回true;否则返回false


/******************************************************************************************
 * RedBlack双黑调整算法:解决节点x与被其替代的节点均为黑色的问题
 * 分为三大类共四种情况:
 *    BB-1 :2次颜色翻转,2次黑高度更新,1~2次旋转,不再递归
 *    BB-2R:2次颜色翻转,2次黑高度更新,0次旋转,不再递归
 *    BB-2B:1次颜色翻转,1次黑高度更新,0次旋转,需要递归
 *    BB-3 :2次颜色翻转,2次黑高度更新,1次旋转,转为BB-1或BB2R
 ******************************************************************************************/
template <typename T> void RedBlack<T>::solveDoubleBlack ( BinNodePosi(T) r ) {
   BinNodePosi(T) p = r ? r->parent : this->_hot; if ( !p ) return; //r的父亲
   BinNodePosi(T) s = ( r == p->lc ) ? p->rc : p->lc; //r的兄弟
   if ( IsBlack ( s ) ) { //兄弟s为黑
      BinNodePosi(T) t = NULL; //s的红孩子(若左、右孩子皆红,左者优先;皆黑时为NULL)
      if ( IsRed ( s->rc ) ) t = s->rc; //右子
      if ( IsRed ( s->lc ) ) t = s->lc; //左子
      if ( t ) { //黑s有红孩子:BB-1
         //*DSA*/printf("  case BB-1: Child ("); print(s->lc); printf(") of BLACK sibling ("); print(s); printf(") is RED\n");
         RBColor oldColor = p->color; //备份原子树根节点p颜色,并对t及其父亲、祖父
      // 以下,通过旋转重平衡,并将新子树的左、右孩子染黑
         BinNodePosi(T) b = FromParentTo ( *p ) = rotateAt ( t ); //旋转
         if ( HasLChild ( *b ) ) { b->lc->color = RB_BLACK; updateHeight ( b->lc ); } //左子
         if ( HasRChild ( *b ) ) { b->rc->color = RB_BLACK; updateHeight ( b->rc ); } //右子
         b->color = oldColor; updateHeight ( b ); //新子树根节点继承原根节点的颜色
         //*DSA*/printBinTree(b, 0, 0);
      } else { //黑s无红孩子
         s->color = RB_RED; s->height--; //s转红
         if ( IsRed ( p ) ) { //BB-2R
            //*DSA*/printf("  case BB-2R: Both children ("); print(s->lc); printf(") and ("); print(s->rc); printf(") of BLACK sibling ("); print(s); printf(") are BLACK, and parent ("); print(p); printf(") is RED\n"); //s孩子均黑,p红
            p->color = RB_BLACK; //p转黑,但黑高度不变
            //*DSA*/printBinTree(p, 0, 0);
         } else { //BB-2B
            //*DSA*/printf("  case BB-2R: Both children ("); print(s->lc); printf(") and ("); print(s->rc); printf(") of BLACK sibling ("); print(s); printf(") are BLACK, and parent ("); print(p); printf(") is BLACK\n"); //s孩子均黑,p黑
            p->height--; //p保持黑,但黑高度下降
            //*DSA*/printBinTree(p, 0, 0);
            solveDoubleBlack ( p ); //递归上溯
         }
      }
   } else { //兄弟s为红:BB-3
      //*DSA*/printf("  case BB-3: sibling ("); print(s); printf(" is RED\n"); //s红(双子俱黑)
      s->color = RB_BLACK; p->color = RB_RED; //s转黑,p转红
      BinNodePosi(T) t = IsLChild ( *s ) ? s->lc : s->rc; //取t与其父s同侧
      this->_hot = p; FromParentTo ( *p ) = rotateAt ( t ); //对t及其父亲、祖父做平衡调整
      //*DSA*/printBinTree<T>(s, 0, 0);
      solveDoubleBlack ( r ); //继续修正r处双黑——此时的p已转红,故后续只能是BB-1或BB-2R
   }
}

3.3 红黑树测试

红黑树的测试程序如下所示,可以清楚看到插入过程中的调整变换。

/*
 * this program aims to test REDBLACK Tree
 * author@Ripples
 * 20200730
 */
#include "rbtree.h"
#include <iostream>

using namespace std;

int main(){
    RedBlack<int> rb;
    rb.insert( 10 );
    cout << rb.root()->color << endl;//1,树根自动转为黑
    rb.insert( 5 );
    rb.insert( 12 );
    cout << rb.root()->lc->color << endl;//0
    cout << rb.root()->rc->color << endl;//0
    /* 
    *        10(1)
    *   5(0)        12(0)
    */
    //RR-2 
    rb.insert( 8 );
    cout << rb.root()->color << endl;//1,树根自动转为黑
    cout << rb.root()->lc->color << endl;//1
    cout << rb.root()->rc->color << endl;//1
    /* 
    *        10(1)
    *   5(1)        12(1)
    *     8(0)
    */ 
    //RR-1
    rb.insert( 6 );
    // cout << rb.root()->lc->data << endl;//6
    // cout << rb.root()->rc->data << endl;//12
    // cout << rb.root()->data << endl;//10
    // cout << rb.root()->lc->lc->data << endl;//5
    // cout << rb.root()->lc->rc->data << endl;//8
    /* 
    *        10(1)
    *    6(1)          12(1)
    * 5(0)    8(0)
    */ 
//    rb.remove( 5 );
//    cout << "exit" <<endl;
    rb.remove( 12 );
    cout << "---------------- " << endl;
    cout << rb.root()->rc->data << endl;//5
    cout << rb.root()->data << endl;//10
    /* 
    *           6(1)
    *    5(1)          10(1)
    *               8(0)
    */ 
   rb.remove(10 );
   cout << rb.root()->rc->data << endl;
    /* 
    *           6(1)
    *    5(1)          8(1)
    *               
    */ 
   rb.remove( 5 );
   cout << rb.root()->rc->color <<endl;
       /* 
    *           6(1)
    *                   8(0)
    *               
    */ 
}

3.4 红黑树总结

红黑树的双红修正与双黑修正的算法设计值得学习,分析问题也应如此逻辑缜密,思维清晰,皮实。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值