您的位置:首页 > 编程语言 > C语言/C++

红黑树(RedBlackTree)实现

2017-04-19 09:17 627 查看
红黑树(Red Black Tree) 是一种自平衡二叉查找树,和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。

但现实中 红黑树 的应用比 AVL
树多, 因为同时插入一个结点,AVL树旋转的时候,红黑树不一定旋转。

我们通过上滤(自底向上调整)算法用c++模板类实现了红黑树的插入操作代码,

但是,更好的实现插入操作的算法是下滤(自顶向下),
这种算法中结点中可以少保存一个parent指针成员, 节约空间。

关于红黑树的删除也有上滤与下滤两种,大家可以在别的资料中看到。

本篇博客没有太多的叙述去讲红黑树插入的几种情况,以及每种情况下分别应该做什么调整使插入新节点后,整棵树依旧满足红黑树性质

.h:
enum color{ Red, Black };

template<class T>
class RedBlackTree
{
public:
RedBlackTree( )
:_root( NULL )
{}

void PrintTree( ) const
{
if ( _root != NULL )
{
_PrintTree( _root );

cout << endl;
}
else
{
cout << "空树!" << endl;
}
}

void Insert( const T& x )
{
_Insert( x, _root );

_root->_color = Black;					//因为情况四最后结束时, grand(cur)可能为_root. 所以我们在这置Black.
}

private:
struct RBTNode
{
T _data;								//parent grand是在类的私有成员,定义在RBTNode里你是不是傻!
RBTNode* _left;
RBTNode* _right;
RBTNode* _parent;
int _color;

RBTNode( const T& x= T( ), RBTNode* left = NULL, RBTNode* right = NULL,
RBTNode* parent = NULL, int color = Red )
:_data( x )
,_left( left )
,_right( right )
,_parent( parent )
,_color( color )
{}
};

//利用三叉链,在这也定义 RBTNode* parent.  否则, parent 与 grand 都不好找。 利用三叉链很容易找到 parent 与 grand( parent->parent )
RBTNode* _root;
RBTNode* parent;
RBTNode* cur;
RBTNode* uncle;
RBTNode* grand;

void _PrintTree( RBTNode* root ) const
{
if ( NULL == root )
{
return;
}

_PrintTree( root->_left );
cout << root->_data << " ";
_PrintTree( root->_right );
}

//旋转这有问题,我们是直接利用了AVL树旋转的模板, 但AVL树中, 是通过传 某节点的左or右孩子 的引用, 因此它可以自己 链接 。  但这里我们传的是具体结点指针,所以不会链接上。

void _RotateWithLeftChild( RBTNode*& k2 )
{
RBTNode* k1 = k2->_left;

k2->_left = k1->_right;
if ( NULL != k1->_right )				//有坑, "k1->_right"必须存在 才能访问 它的_parent
{
k1->_right->_parent = k2;
}

k1->_right = k2;
//注意下面两句顺序不能错, 否则会丢值
k1->_parent = k2->_parent;
k2->_parent = k1;

k2 = k1;
}

void _RotateWithRightChild( RBTNode*& k1 )
{
RBTNode* k2 = k1->_right;

k1->_right = k2->_left;
if ( NULL != k2->_left )
{
k2->_left->_parent = k1;
}

k2->_left = k1;
k2->_parent = k1->_parent;
k1->_parent = k2;

k1 = k2;
}

void _DoubleWithLeftChild ( RBTNode*& k3 )
{
_RotateWithRightChild( k3->_left );
_RotateWithLeftChild( k3 );
}

void _DoubleWithRightChild ( RBTNode*& k1 )
{
_RotateWithLeftChild( k1->_right );
_RotateWithRightChild( k1 );
}

void _Insert( const T& x, RBTNode*& root )	//我们利用自底向上算法进行插入(注意这里 root 为引用)//如果不传引用,在这改了 开辟空间给了root后, 外面root还是 NULL.
{

//第一种情况:空树,插入x后形成新的根
if ( NULL == root )
{
root = new RBTNode( x, NULL, NULL, NULL, Black );
cout << "原树为空树,插入值: " << x << " 成功! 形成新的根!" << endl;

return;
}

//先遍历这棵树,找到结点x要插入的位置,并且开辟一个新的结点保存x

cur = root;

while ( NULL != cur )
{
if ( x < cur->_data )
{
parent = cur;
cur = cur->_left;
}
else if( x > cur->_data )
{
parent = cur;
cur = cur->_right;
}
else
{
cout << "原树中已经存在数据: " << x << " 插入失败!" << endl;
return;
}
}

cur = new RBTNode( x, NULL, NULL, parent, Red );//此时 cur 已经链接到 parent. 但是 parent 并未链接到 cur.

//因为后面要循环处理,即上滤,所以我们在这就需要把 parent 与 cur 链接起来。
//否则,下面处理会很麻烦

if ( x < parent->_data )
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}

//此处开始我们循环的逻辑:

while ( NULL != parent )
{

//第二种情况:插入结点的父节点为黑色,此时不需要做调整 或者 为 调整上来
if ( Black == parent->_color )
{
cout << "插入点: " << x << " 父节点为黑色,插入成功!" << endl;
return;
}

//函数进行到这,可判断插入节点的父亲结点为红色

//先找到插入节点的叔叔
grand = parent->_parent;

if ( parent->_data < grand->_data )
{
uncle = grand->_right;
}
else
{
uncle = grand->_left;
}

//即使是循环再次到这,父亲颜色为红色,肯定未到根节点,所以一定有祖父节点
//需要改,叔叔为黑色也要加进来
//第三种情况:插入节点的父亲为红色(或者 为 调整上来),且插入节点的叔叔不存在或者叔叔颜色为黑色(必须通过 情况4后 叔叔才有可能为黑色, 否则刚插入时, 叔叔一定不存在或为红色)。(此时祖父一定为黑)。	此时可通过旋转对树进行调整(具体单旋还是双旋视情况而定)
if ( (NULL == uncle) || (Black == uncle->_color) )//注意顺序
{
if ( (parent->_data < grand->_data) && (cur->_data < parent->_data) )//此时对祖父进行一次右单旋及变色处理即可使这棵树满足红黑树性质
{
//在外面我们只对颜色进行处理, 其余操作均在旋转函数中完成
grand->_color = Red;
parent->_color = Black;

if ( NULL == grand->_parent )//此时 grand 为根节点, 我们直接传 root 进来
{
_RotateWithLeftChild( root );	//若 cur 有兄弟结点,则在这个函数中进行处理
}
else if ( grand->_data < grand->_parent->_data )//之所以这么做,
{
_RotateWithLeftChild( grand->_parent->_left );//是为了 "链接"  好好想想
}
else if ( grand->_data > grand->_parent->_data )
{
_RotateWithLeftChild( grand->_parent->_right );
}

cout << "对: " << x << " 祖父进行了一次右单旋!插入成功!" << endl;
return;
}
else if ( (parent->_data > grand->_data) && (cur->_data > parent->_data) )//对祖父进行左单旋
{
parent->_color = Black;
grand->_color = Red;

if ( NULL == grand->_parent )//此时 grand 为根节点, 我们直接传 root 进来
{
_RotateWithRightChild( root );	//若 cur 有兄弟结点,则在这个函数中进行处理
}
else if ( grand->_data < grand->_parent->_data )
{
_RotateWithRightChild( grand->_parent->_left );
}
else if ( grand->_data > grand->_parent->_data )
{
_RotateWithRightChild( grand->_parent->_right );
}

cout << "对: " << x << " 祖父进行了一次左单旋!插入成功!" << endl;
return;
}
else if ( (parent->_data < grand->_data) && (cur->_data > parent->_data) )//左右双旋
{
cur->_color = Black;
grand->_color = Red;

if ( NULL == grand->_parent )//此时 grand 为根节点, 我们直接传 root 进来
{
_DoubleWithLeftChild( root );	//对祖父进行 左右双旋 使插入后新树仍然满足红黑树性质
}
else if ( grand->_data < grand->_parent->_data )
{
_DoubleWithLeftChild( grand->_parent->_left );
}
else if ( grand->_data > grand->_parent->_data )
{
_DoubleWithLeftChild( grand->_parent->_right );
}

cout << "对: " << x << " 祖父进行了一次左右双旋!插入成功!" << endl;
return;
}
else if ( (parent->_data > grand->_data) && (cur->_data < parent->_data) )//右左双旋
{
cur->_color = Black;
grand->_color = Red;

if ( NULL == grand->_parent )//此时 grand 为根节点, 我们直接传 root 进来
{
_DoubleWithRightChild( root );	//对祖父进行 右左双旋 使插入后新树仍然满足红黑树性质
}
else if ( grand->_data < grand->_parent->_data )
{
_DoubleWithRightChild( grand->_parent->_left );
}
else if ( grand->_data > grand->_parent->_data )
{
_DoubleWithRightChild( grand->_parent->_right );
}

cout << "对: " << x << " 祖父进行了一次左右双旋!插入成功!" << endl;
return;
}
}

//上滤完,把grand当cur, 当cur父亲为空,则循环结束,循环必须包含情况234. 子问题
//第四种情况:插入节点的父亲结点为红色,叔叔结点存在也为红色.(叔叔结点存在且为黑色在这种情况中出现,此时 parent 一定为祖父节点, 它是调整上来的(上滤),而不可能刚插入 x 时, x 结点叔叔为黑色, 因为它父亲为红色, 若叔叔为黑色, 违反红黑树性质4)  第四种情况需要上滤
//第四种情况上滤后,可能产生几种情况  将gp当 cur,  gp的parent为黑->结束。 为红则gp->_parent为红->-parent一定为黑,则此时gp->_parent的兄弟有可能为红或者黑。  黑的话旋转即可,红的话又要上滤
//第四种情况:插入节点父节点为红色,且叔叔结点也为红色,此时祖父节点一定为黑色。 可通过对叔叔,父亲,祖父结点变色进行处理,  但处理完后,  祖父结点 与 祖父结点的父亲颜色有可能又不满足(两个红色), 此时需要再进行上滤(但叔叔此时有多种可能),进行处理。
if ( Red == uncle->_color )			//其实程序到这,一定为 红父亲,红叔叔,黑祖父。  不进行条件判断也无所谓
{
parent->_color = Black;
uncle->_color = Black;
grand->_color = Red;

cur = grand;					//经过好多循环,这个 grand( cur ) 有可能为_root.   只有 情况四 调整后 需要上滤, 别的情况调整成功后, 直接就结束了。  如果 grand 的 parent 为空。 即 parent 为空,此时就结束循环了。 调整完毕!
parent = cur->_parent;

cout << "对: " << x << " 父亲,叔叔,祖父节点进行变色处理, 并且以祖父作为当前节点进行上滤调整, 使整棵树继续满足红黑树性质!" << endl;
}

}//end while

}//end _Insert( );
};


.cpp:
//前序 可以用于结点检查: 你想,当前节点(根)都不正确,就不用往下(左右)遍历了
//先求一条路径黑节点数量保存下,和别的路径比较下就行了(如果判断一棵树是不是红黑树,是否满足那个每条结点黑色路径是否相等性质)

//AVL			与			红黑树 比较:

//AVL旋转次数太多了,同时插入一个结点,AVL树旋转的时候,红黑树不一定旋转。 实际应用中红黑树多一些。

//中序 有序输出(如是数字时,有序输出)

//单参数的构造函数支持隐式类型转换:

//AA a1(2);
//AA(3);生命周期,这一行,结束就析构。   生成一个匿名对象
//AA a2 = 10;构造AA(10)通过它拷贝构造出a2->编译器优化后->它等价于 ==  AA a2(10);

//加explict 后 就不能这样了???   AA a2 = 10;???

//set  1.判断一个对象在不在一个集合里(如判断一个单词是否正确,所有单词都在里面,你不正确自然找不到)	2.排序(因为红黑也是搜索树啊)(有缺陷,因为只存一个相同元素)	3.去重合的元素
#include <iostream>
#include <windows.h>
using namespace std;

#include "RedBlackTree.h"

void test( );

int main( )
{
test( );

system( "pause" );
return 0;
}

void test( )
{
int a[] = {16, 3, 7, 11, 9, 26, 18, 14, 15};

RedBlackTree<int> t1;

for ( size_t i = 0; i < sizeof(a)/ sizeof(a[0]); ++i )
{
t1.Insert( a[i] );
}

//因为程序用到上滤的思想 使 插入新节点后,整棵树依旧 满足 红黑树性质, 故 插入 节点后,如 要 通过 上滤 步骤使新树满足红黑树性质, 则 打印 信息  可能出错(这里的打印信息指的是每插入一个节点后, 输入的信息).
t1.PrintTree( );
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息