c++标准库中STL的关联容器(set、multiset、map、multimap)都是用的红黑树作为底层代码,而且红黑树在快速查找的结构里面用的比较多,想比于平衡二叉树,红黑树没有那么严格的限制条件(平衡二叉树要求结点的深度之差不超过1,而红黑树则没有这个限制),虽然红黑树是一种弱化的AVL树,但在数据查找效率方面依然不必AVL差,而且由于AVL的平衡条件要求比较严格,所以,当插入一个数据时,极有可能就需要经过旋转操作来使其重新达到平衡,所以平衡二叉树运用的范围更加广泛,但由于其增加了颜色标识,所以操作起来也更加复杂。
一、红黑树的性质
一般的,红黑树,满足以下性质,即只有满足以下全部性质的树,我们才称之为红黑树:
- 每个节点要么是红的,要么是黑的。
- 根节点是黑的。
- 每个叶节点(叶节点即指树尾端NIL指针或NULL节点)是黑的。
- 如果一个节点是红的,那么它的两个儿子都是黑的。
- 对于任一节点而言,其到叶节点树尾端NIL指针的每一条路径都包含相同数目的黑节点。
如下图,就是一个典型的红黑树:
二、红黑树和AVL树的比较
红黑树:红黑树并不追求“完全平衡”——它只要求部分地达到平衡要求,降低了对旋转的要求,从而提高了性能。红黑树能够以O(log2n)的时间复杂度进行搜索、插入和删除操作,而且任何不平衡都会在三次旋转之内解决,红黑树的算法时间和AVL相同,但统计性能要比AVL好。
AVL树:平衡二叉树或为空树,或为如下性质的二叉排序树:
(1)左右子树深度之差的绝对值不超过1;
(2)左右子树仍然为平衡二叉树.
因为平衡二叉树的所有节点有平衡限制,所以,查找速度比较快,不会因为深度过深而影响搜索时间。
三、红黑树的插入、平衡及旋转代码展示
由于删除操作的思路还没理清,所以暂时先没写,等到时理解后在重新编写完善该文章。把自己的理解结合STL源码,编写代码代码如下:
#include <iostream>
using namespace std;
enum {BLACK=0,RED}; //表示红黑树的两种颜色
//红黑树的结点数据结构
struct rb_tree_node
{
public:
char data;
rb_tree_node* parents;
rb_tree_node* left;
rb_tree_node* right;
int color;
};
//操作红黑树的类
class My_rb_tree
{
public:
typedef rb_tree_node* rb_pointer;
private:
rb_pointer header; //一个头,与根节点互为父结点
public:
My_rb_tree() //构造函数
{
_init();
}
//插入结点,insert_equal
rb_pointer insert_equal(const char &data)
{
rb_pointer x=_root(); //得到根节点
rb_pointer tem=header;
while(x!=nullptr)
{
tem=x;
x=(x->data)>data?x->left:x->right;
}
_insert(tem,data);
return x;
}
void print()
{
cout<<_left_most()->data<<endl;
cout<<_right_most()->data<<endl;
}
private:
void _init() //初始化
{
header=_get_node(); //为头结点分配一个内存
header->data='#';
header->color=RED; //color为空,与root区别
header->left=header;
header->right=header; //左右孩子为空
header->parents=nullptr; //指向根节点,开始为空
}
rb_pointer _get_node()
{
return (rb_pointer)malloc(sizeof(rb_tree_node)); //为结点配置一个内存
}
//获得根节点
rb_pointer& _root() const
{
return (rb_pointer&)header->parents;
}
rb_pointer& _left_most()const
{
return (rb_pointer&)header->left;
}
rb_pointer& _right_most()const
{
return (rb_pointer&)header->right;
}
rb_pointer _min(rb_pointer x)
{
while(x->left!=nullptr)
x=x->left;
return x;
}
rb_pointer _max(rb_pointer x)
{
while(x->right!=nullptr)
x=x->right;
return x;
}
rb_pointer _insert(rb_pointer parents,const char& data)
{
rb_pointer new_node=nullptr;
if(parents==header) //当前结点是header结点,则,插入的是root结点
{
new_node=_get_node();
//设置数据
new_node->data=data;
new_node->left=nullptr;
new_node->right=nullptr;
new_node->parents=parents;
new_node->color=RED;
header->parents=new_node;
_left_most()=new_node;
_right_most()=new_node;
_root()=new_node;
}
else //当前结点不是header结点
{
if(data<parents->data) //值小于当前结点
{
new_node=_get_node();
//设置数据
new_node->data=data;
new_node->left=nullptr;
new_node->right=nullptr;
new_node->parents=parents;
new_node->color=RED;
parents->left=new_node;
if(header->left==parents) //当前结点是最小值
_left_most()=new_node; //修改当前最小的值,_left_most()返回的必须是引用,不然不能修改该值,以下同理
}
else //值大于当前结点
{
new_node=_get_node();
//设置数据
new_node->data=data;
new_node->left=nullptr;
new_node->right=nullptr;
new_node->parents=parents;
new_node->color=RED;
parents->right=new_node;
if(header->right==parents)
_right_most()=new_node; //修改当前最大的值
}
}
//调整红黑特性
_rb_tree_rebalance(new_node,_root());
return new_node;
}
void _rb_tree_rebalance(rb_pointer new_node,rb_pointer& root)
{
while(new_node!=root&&new_node->parents->color==RED) //当前节点不为根节点,且父结点为红(只有父结点为红才需要调整),父结点是祖父结点的左孩子
{
if(new_node->parents==new_node->parents->parents->left) //父结点是祖父结点的左孩子,右孩子类似
{
rb_pointer uncle=new_node->parents->parents->right;
if(uncle!=nullptr&&uncle->color==RED) //case1:父结点是红色的,伯父结点存在且为红
{
uncle->parents->color=RED;
new_node->parents->color=BLACK;
uncle->color=BLACK;
new_node=uncle->parents;
}
else //case2:父结点是红色的,而伯父结点不存在或伯父结点为黑
{
if(new_node==new_node->parents->right) //如果插入的点是父结点的右孩子时,要进行双旋转,先进行左旋转
{
new_node=new_node->parents;
_rotate_left(new_node,root); ///以插入点的父结点为基准,左旋转
}
new_node->parents->color=BLACK; //改变基准点的父结点的颜色
new_node->parents->parents->color=RED;
_rotate_right(new_node->parents->parents,root); //如果插入的是父结点的左孩子,则只要进行右旋转就行了
}
}
else 父结点是祖父结点的右孩子
{
rb_pointer uncle=new_node->parents->parents->left;
if(uncle!=nullptr&&uncle->color==RED) //case3:父结点是红色的,伯父结点存在且为红
{
uncle->parents->color=RED;
new_node->parents->color=BLACK;
uncle->color=BLACK;
new_node=uncle->parents;
}
else //case4:父结点是红色的,而伯父结点不存在或伯父结点为黑
{
if(new_node==new_node->parents->left) //如果插入的点是父结点的右孩子时,要进行双旋转,先进行左旋转
{
new_node=new_node->parents;
_rotate_right(new_node,root); ///以插入点的父结点为基准,左旋转
}
new_node->parents->color=BLACK; //改变基准点的父结点的颜色
new_node->parents->parents->color=RED;
_rotate_left(new_node->parents->parents,root); //如果插入的是父结点的左孩子,则只要进行右旋转就行了
}
}
}
root->color=BLACK; //设置根节点为黑,最终会到达根节点,然后直接改根节点的颜色就可以了
}
void _rotate_left(rb_pointer x,rb_pointer& root)
{
rb_pointer y=x->right;
x->right=y->left;
if(y->left!=nullptr)// 当前的x结点有左孩子,则应设置该左孩子的父结点
y->left->parents=x;
y->parents=x->parents;
if(x==_root())
_root()=y;
else if(x==x->parents->left) //x是父结点的左孩子
{
x->parents->left=y;
}
else if(x==x->parents->right) //x是父结点的右孩子
{
x->parents->right=y;
}
x->parents=y;
y->left=x;
}
void _rotate_right(rb_pointer x,rb_pointer& root)
{
rb_pointer y=x->left;
x->left=y->right;
if(y->right!=nullptr)
{
y->right->parents=x;
}
y->parents=x->parents;
if(x==_root())
{
_root()=y;
}
else if(x==x->parents->left)
x->parents->left=y;
else if(x==x->parents->left)
x->parents->right=y;
x->parents=y;
y->right=x;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
My_rb_tree rb;
rb.insert_equal('1'); //这里只是字符,可以改为int型更好
rb.insert_equal('5');
rb.insert_equal('6');
rb.insert_equal('2');
rb.print(); //打印最小值和最大值
return 0;
}
输出结果如下:
测试成功!