STL 简单红黑树的实现

来源:互联网 发布:数控切割机编程实例 编辑:程序博客网 时间:2024/04/29 00:45

1.红黑树简介

二叉搜索树能够提供对数的元素插入和访问。二叉搜索树的规则是:任何节点的键值一定大于其左子树的每一个节点值,并小于右子树的每一个节点值。

常见的二叉搜索树有AVL-tree、RB-tree(红黑树)。红黑树具有极佳的增、删、查性能,故我们选择红黑树作为关联式容器(associative containers)的底层结构。

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


1.      节点是红色或黑色。;

2.      根节点是黑色;

3.      每个叶节点(NILL节点,空节点)是黑色的;

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

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


由性质4可以推出:一条路径上不能有两个毗连的红色节点。最短的可能路径都是黑色节点,最长的可能路径是交替的红色和黑色节点。又根据性质5,所有最长的路径都有相同数目的黑色节点,这就表明了没有路径能多于任何其他路径的两倍长。


以上约束条件强化了红黑树的关键性质: 从根到叶子的最长路径不多于最短路径的两倍长。所以红黑树是大致上是平衡的(不像AVL-tree,要求绝对平衡)。树的插入、删除和查找效率与树的高度成比例,红黑树的高度上限允许在最坏情况下都是高效的,这是红黑树相对于其他二叉搜索树最大的优势。


2.红黑树在STL中的实现

         要学习STL关联式容器,我们必须实现一颗红黑树。本文介绍红黑树在STL中的代码实现,为今后学习关联式容器打下基础。关于红黑树的详细特性(如增加、删除、旋转等)不在讨论范围内,请查阅相关资料。

我用VS2013写的程序(github),红黑树版本的代码位于cghSTL/version/cghSTL-0.4.0.rar

一颗STL红黑树的实现需要以下几个文件:

1.      globalConstruct.h,构造和析构函数文件,位于cghSTL/allocator/cghAllocator/

2.       cghAlloc.h,空间配置器文件,位于cghSTL/allocator/cghAllocator/

3.      rb_tree_node.h,红黑树节点,位于cghSTL/associative containers/RB-tree/

4.       rb_tree_iterator.h,红黑树迭代器,位于cghSTL/associative containers/RB-tree/

5.      rb_tree.h,红黑树的实现,位于cghSTL/associative containers/RB-tree/

6.      test_rb_tree.cpp,红黑树的测试,位于cghSTL/test/


 

2.1构造与析构

先看第一个,globalConstruct.h构造函数文件

/********************************************************************  Copyright(c) 2016 Chen Gonghao*  All rights reserved.**  chengonghao@yeah.net**  功能:全局构造和析构的实现代码******************************************************************/#include "stdafx.h"#include <new.h>#include <type_traits>#ifndef _CGH_GLOBAL_CONSTRUCT_#define _CGH_GLOBAL_CONSTRUCT_namespace CGH{#pragma region 统一的构造析构函数template<class T1, class  T2>inline void construct(T1* p, const T2& value){new (p)T1(value);}template<class T>inline void destroy(T* pointer){pointer->~T();}template<class ForwardIterator>inline void destroy(ForwardIterator first, ForwardIterator last){// 本来在这里要使用特性萃取机(traits编程技巧)判断元素是否为non-trivial// non-trivial的元素可以直接释放内存// trivial的元素要做调用析构函数,然后释放内存for (; first < last; ++first)destroy(&*first);}#pragma endregion }#endif

按照STL的接口规范,正确的顺序是先分配内存然后构造元素。构造函数的实现采用placement new的方式;为了简化起见,我直接调用析构函数来销毁元素,而在考虑效率的情况下一般会先判断元素是否为non-trivial类型。

关于 trivial 和 non-trivial 的含义,参见:stack overflow

 

2.2空间配置器

cghAlloc.h是空间配置器文件,空间配置器负责内存的申请和回收。

/********************************************************************  Copyright(c) 2016 Chen Gonghao*  All rights reserved.**  chengonghao@yeah.net**  功能:cghAllocator空间配置器的实现代码******************************************************************/#ifndef _CGH_ALLOC_#define _CGH_ALLOC_#include <new>#include <cstddef>#include <cstdlib>#include <climits>#include <iostream>namespace CGH{#pragma region 内存分配和释放函数、元素的构造和析构函数// 内存分配template<class T>inline T* _allocate(ptrdiff_t size, T*){set_new_handler(0);T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));if (tmp == 0){std::cerr << "out of memory" << std::endl;exit(1);}return tmp;}// 内存释放template<class T>inline void _deallocate(T* buffer){::operator delete(buffer);}// 元素构造template<class T1, class  T2>inline void _construct(T1* p, const T2& value){new(p)T1(value);}// 元素析构template<class T>inline void _destroy(T* ptr){ptr->~T();}#pragma endregion#pragma region cghAllocator空间配置器的实现template<class T>class cghAllocator{public:typedef Tvalue_type;typedef T*pointer;typedef const T*const_pointer;typedef T&reference;typedef const T&const_reference;typedef size_tsize_type;typedef ptrdiff_tdifference_type;template<class U>struct rebind{typedef cghAllocator<U> other;};static pointer allocate(size_type n, const void* hint = 0){return _allocate((difference_type)n, (pointer)0);}static void deallocate(pointer p, size_type n){_deallocate(p);}static void deallocate(void* p){_deallocate(p);}void construct(pointer p, const T& value){_construct(p, value);}void destroy(pointer p){_destroy(p);}pointer address(reference x){return (pointer)&x;}const_pointer const_address(const_reference x){return (const_pointer)&x;}size_type max_size() const{return size_type(UINT_MAX / sizeof(T));}};#pragma endregion#pragma region 封装STL标准的空间配置器接口template<class T, class Alloc = cghAllocator<T>>class simple_alloc{public:static T* allocate(size_t n){return 0 == n ? 0 : (T*)Alloc::allocate(n*sizeof(T));}static T* allocate(void){return (T*)Alloc::allocate(sizeof(T));}static void deallocate(T* p, size_t n){if (0 != n)Alloc::deallocate(p, n*sizeof(T));}static void deallocate(void* p){Alloc::deallocate(p);}};#pragma endregion}#endif


classcghAllocator是空间配置器类的定义,主要的四个函数的意义如下:allocate函数分配内存,deallocate函数释放内存,construct构造元素,destroy析构元素。这四个函数最终都是通过调用_allocate、_deallocate、_construct、_destroy这四个内联函数实现功能。

我们自己写的空间配置器必须封装一层STL的标准接口,

template<classT,class Alloc = cghAllocator<T>>classsimple_alloc


构造与析构函数、空间配置器是STL中最最基本,最最底层的部件,把底层搭建好之后我们就可以着手设计红黑树了。

2.3 红黑树节点的实现

         我们把红黑树的节点为双层结构,有基层节点(__rb_tree_node_base正规节点(__rb_tree_node,正规节点由基层节点继承而来。

基层节点和正规节点分工明确:基层节点包含父指针、左指针、右指针、节点颜色这四个变量,保存着节点的状态信息;正规节点不保存状态信息,仅包含节点值的信息。

布尔值定义节点颜色,红色为false,黑色为true。

         基层节点和正规节点的关系如下图所示。


节点代码的注释已经写得十分详细了,有疑问的地方我都给出了说明。

rb_tree_node.h

/********************************************************************  Copyright(c) 2016 Chen Gonghao*  All rights reserved.**  chengonghao@yeah.net**  文件内容:红黑树的节点******************************************************************/#ifndef _CGH_RB_TREE_NODE_#define _CGH_RB_TREE_NODE_namespace CGH{typedef bool __rb_tree_color_type; // 用布尔类型定义红黑树的颜色const __rb_tree_color_type __rb_tree_red = false; // 红色为0const __rb_tree_color_type __rb_tree_black = true; // 黑色为1/* 基层节点 */struct __rb_tree_node_base{typedef __rb_tree_color_typecolor_type; // 节点颜色typedef __rb_tree_node_base*base_ptr; // 节点指针color_type color; // 节点颜色base_ptr parent; // 父节点指针base_ptr left; // 左子节点指针base_ptr right; // 右子节点指针/*给定某个节点位置,找到最左边的节点如果给定根节点的位置,可以找到整颗红黑树最左边的节点(也就是找到了最小值节点)返回节点位置*/static base_ptr minimum(base_ptr x){while (x->left != 0){x = x->left; // 一直向左走,就能找到最小节点}return x;}/*给定某个节点位置,找到最右边的节点如果给定根节点的位置,可以找到整颗红黑树最右边的节点(也就是找到了最大值节点)返回节点位置*/static base_ptr maximum(base_ptr x){while (x->right != 0){x = x->right; // 一直向左右走,就能找到最大节点}return x;}};/* 正规节点 */template<class value>struct __rb_tree_node :public __rb_tree_node_base{/* 子类继承了父类的成员:color、parent、left、right,value_field用来表示节点的值域 */typedef __rb_tree_node<value>* link_type;value value_field; // 节点值域};}#endif


 

2.4 红黑树迭代器的实现

         与节点一样,迭代器也是双层结构。分别为基层迭代器(rb_tree_base_iterator正规迭代器(__rb_tree_iterator。正规迭代器由基层迭代器继承而来。

         两者分工明确:基层迭代器保存唯一的成员变量:树节点,该变量是联系迭代器和树节点的纽带;正规迭代器不包含任何成员变量,只实现迭代器的各种操作。

注意,红黑树的迭代器提供的是bidirectional access,支持双向访问,不支持随机访问。


         下图总结了迭代器与红黑树节点间的关系:


迭代器代码的注释已经写得十分详细了,有疑问的地方我都给出了说明,童鞋们可以通过注释来理解迭代器的工作原理。        

rb_tree_iterator.h

/********************************************************************  Copyright(c) 2016 Chen Gonghao*  All rights reserved.**  chengonghao@yeah.net**  文件内容:红黑树的迭代器******************************************************************/#ifndef _CGH_RB_TREE_ITERATOR_#define _CGH_RB_TREE_ITERATOR_#include "rb_tree_node.h"#include <memory> // 使用ptrdiff_t需包含此头文件namespace CGH{/* 基层迭代器 */struct rb_tree_base_iterator{typedef __rb_tree_node_base::base_ptrbase_ptr; // 父类节点指针typedef std::bidirectional_iterator_tagiterator_category;typedef ptrdiff_tdifference_type;base_ptrnode; // 成员变量:指向父类节点的指针,这是联系迭代器和节点的纽带/* 迭代器的子类实现operator++时调用本函数 */void increment(){if (node->right != 0) // 如果有右子节点{node = node->right; // 就向右走while (node->left != 0) // 然后一直往左子树走到底{node = node->left;}}else // 没有右子节点{base_ptr y = node->parent; // 找出父节点while (node == y->right) // 如果现行节点本身是个右子节点{node = y; // 就一直上溯,直到“不为右子节点”为止y = y->parent;}if (node->right != y) // 若此时的右子节点不等于此时的父节点,此时的父节点即为解答{  // 否则此时的node为解答node = y;}}}/* 迭代器的子类实现operator--时调用本函数 */void decrement(){if (node->color == __rb_tree_red&&node->parent->parent == node){// 如果是红节点,且祖父节点等于自己,那么右节点即为解答// 该情况发生于node为header时,注意,header的右子节点(即mostright),指向整棵树的max节点node = node->right;}else if (node->left != 0) // 如果有左子节点,当y{base_ptr y = node->left; // 令y指向左子节点while (y->right != 0) // 当y有右子节点时{y = y->right; // 一直往右子节点走到底}node = y; // 最后即为答案}else // 即非根节点,也没有左子节点{base_ptr y = node->parent; // 找出父节点while (node == y->left) // 当现行节点为左子节点{node = y; // 一直交替往上走,直到现行节点y = y->parent; // 不为左子节点}node = y; // 此时父节点即为答案}}};/* 正规迭代器 */template<class value,class ref,class ptr>struct __rb_tree_iterator :public rb_tree_base_iterator{#pragma region typedef/* 注意,正规迭代器没有成员变量,只继承了基层迭代器的node变量基层迭代器的node变量是红黑树节点与迭代器连接的纽带*/typedef valuevalue_type;typedef refreference;typedef ptrpointer;typedef __rb_tree_iterator<value, value&, value*>iterator;typedef __rb_tree_iterator<value, ref, ptr>self;typedef __rb_tree_node<value>*link_type;typedef size_tsize_type;#pragma endregion#pragma region 构造函数__rb_tree_iterator(){} // default构造函数__rb_tree_iterator(link_type x){ node = x; } // 普通构造函数__rb_tree_iterator(const iterator& it){ node = it.node; } // copy构造函数#pragma endregion#pragma region 迭代器的基本操作/* 解除引用,返回节点值 */reference operator*()const{ return link_type(node)->value_field; }/* 解除引用,返回节点值 */pointer operator->()const{ return *(operator*()); }/* 返回迭代器指向的节点的颜色 */__rb_tree_color_type color(){ return node->color == __rb_tree_red ? 0 : 1; }/* 迭代器步进 */self& operator++()const{ increment(); return *this; }/* 迭代器步进 */self& operator++(int){self tmp = *this;increment();return tmp;}/* 迭代器步退 */self& operator--()const{ decrement(); return *this; }/* 迭代器步退 */self& operator--(int){self tmp = *this;decrement();return tmp;}/* 比较两迭代器是否指向同一个节点 */bool operator==(const self& x)const{ return x.node == node; }/* 比较两迭代器是否指向同一个节点 */bool operator!=(const self& x)const{ return x.node != node; }#pragma endregion};}#endif


2.5 红黑树的实现

         有了红黑树的节点和迭代器,接下来着手设计红黑树。

         红黑树的内部结构我用region分为了以下部分:

1.      一堆typedef;

2.      红黑树的成员变量:节点数目(node_count)、迭代器(iterator)等;

3.      树的初始化,节点的构造、析构、复制;

4.      受保护的辅助函数,作为内部工具;

5.      红黑树的操作;


注意,我们在红黑树中使用了一个名为header的节点,header节点作为哨兵,本身不是红黑树的一部分。

header节点的作用有三个:

1.      标识根节点的位置;

2.      标识红黑树最左边节点的位置

3.      标识红黑树最右边节点的位置

红黑树实现代码的注释已经写得十分详细了,有疑问的地方我都给出了说明,童鞋们可以参考红黑树的内部结构来总体把握红黑树的框架,通过注释来理解红黑树的工作原理。

rb_tree.h:

/********************************************************************  Copyright(c) 2016 Chen Gonghao*  All rights reserved.**  chengonghao@yeah.net**  文件内容:红黑树******************************************************************/#ifndef _CGH_RB_TREE_#define _CGH_RB_TREE_#include "globalConstruct.h" // 全局构造析构函数 #include "cghAlloc.h" // 空间配置器#include "rb_tree_node.h" // 红黑树节点#include "rb_tree_iterator.h" // 红黑树迭代器#include <memory> // 使用ptrdiff_t需包含此头文件namespace CGH{template<class key, class value, class keyOfValue, class compare, class Alloc = cghAllocator<key>>class cgh_rb_tree{#pragma region typedefprotected:typedef void*void_pointer; // 空指针typedef __rb_tree_node_base*base_ptr; // 基层节点指针typedef __rb_tree_node<value>rb_tree_node; // 正规节点指针typedef simple_alloc<rb_tree_node, Alloc>rb_tree_node_allocator; // 节点空间配置器typedef __rb_tree_color_typecolor_type; // 节点颜色public:typedef keykey_type; // 键typedef valuevalue_type; //值typedef value_type*pointer; // 值指针typedef const value_type*const_pointer; // const值指针typedef value_type&reference;  // 值引用typedef const value_type&const_reference; // const值引用typedef rb_tree_node*link_type; // 节点指针typedef size_tsize_type;typedef ptrdiff_tdifference_type;#pragma endregion#pragma region 红黑树的成员变量protected:size_type node_count; // 红黑树的节点数目link_type header; // 哨兵节点,其parent指针指向根节点compare key_compare; // 比较值大小的函数public:typedef __rb_tree_iterator<value_type, reference, pointer> iterator; // 定义红黑树的迭代器#pragma endregion#pragma region 树的初始化,节点的构造、析构、复制protected:/* 调用空间配置器申请一个节点 */link_type get_node(){ return rb_tree_node_allocator::allocate(); }/* 调用空间配置器释还一个节点 */void put_node(link_type p){ rb_tree_node_allocator::deallocate(p); }/* 申请并初始化节点 */link_type create_node(const value_type& x){// x是节点的值link_type tmp = get_node(); // 申请一个节点construct(&tmp->value_field, x); // 调用全局构造函数初始化节点return tmp;}/* 克隆节点 */link_type clone_node(link_type x){link_type tmp = create_node(x->value_field); // 申请并初始化节点tmp->color = x->color;tmp->left = 0;tmp->right = 0;return tmp;}/* 释还节点 */void destroy_node(link_type p){destroy(&p->value_field); // 调用全局析构函数销毁节点值put_node(p); // 释还内存}private:/* 初始化红黑树 */void init(){header = get_node(); // 初始化header节点,header节点作为整颗红黑树的哨兵,header的parent指针指向根节点,header的类型是__rb_tree_node*color(header) = __rb_tree_red; // 设置header节点为红色root() = 0; // root()获得红黑树的根节点,header的parent指针指向根节点,初始化红黑树的根节点指针为nullleftmost() = header; // 设置header节点的左子树指向自己rightmost() = header; // 设置header节点的右子树指向自己}public:/* 构造函数 */cgh_rb_tree(const compare& cmp = compare()) :node_count(0), key_compare(cmp){ init(); }/* 析构函数 */~cgh_rb_tree(){//clear();put_node(header);}#pragma endregion#pragma region protected:辅助函数protected:/* 获得根节点(header是哨兵,其parent执行根节点) */link_type& root()const{ return (link_type&)header->parent; }/* 获得整棵树最左边的节点 */link_type& leftmost()const{ return (link_type&)header->left; }/* 获得整棵树最右边的节点 */link_type& rightmost()const{ return (link_type&)header->right; }/* 返回节点的左子节点 */static link_type& left(link_type x){ return (link_type&)(x->left); }/* 返回节点的右子节点 */static link_type& right(link_type x){ return (link_type&)(x->right); }/* 返回节点的父节点 */static link_type& parent(link_type x){ return (link_type&)(x->parent); }/* 返回节点的value */static reference value(link_type x){ return (x->value_field); }/* 返回节点的颜色 */static color_type& color(link_type x){ return (color_type)(x->color); }/* 返回节点的value */static const key& key(base_ptr x){ return keyOfValue()(value(link_type(x))); }/* 返回最小值节点 */static link_type minimum(link_type x){return (link_type)__rb_tree_node_base::minimum(x); // 调用基层节点的最小节点函数}/* 返回最大值节点 */static link_type maximum(link_type x){return (link_type)__rb_tree_node_base::maximum(x); // 调用基层节点的最大节点函数}#pragma endregion#pragma region 提供给用户的工具函数public:/* 获得根节点的值(header是哨兵,其parent执行根节点); return (link_type&)header->parent->; */value_type root_value(){ return value((link_type)header->parent); }/* 返回比较大小的函数 */compare key_comp()const{ return key_compare; }/* 返回一个迭代器,指向红黑树最左边的节点 */iterator begin(){ return leftmost(); }/* 返回一个迭代器,指向红黑树最右边的节点 */iterator end(){ return header; }/* 判断红黑树是否为空 */bool empty()const{ return node_count == 0; }/* 返回红黑树大小(节点数目) */size_type size() const{ return node_count; }/* 红黑树最大节点数 */size_type max_size()const{ return size_type(-1); }#pragma endregion#pragma region 红黑树操作public:/*插入新值,节点键值不能重复,如果重复则插入无效返回值是pair,pair的第一个元素是rb_tree迭代器,指向新节点第二个元素表示插入成功与否*/std::pair<iterator, bool> insert_unique(const value_type& v){link_type y = header; // link_type的类型是__rb_tree_node*,header是哨兵,令y拿到headerlink_type x = root(); // x拿到红黑树的根节点,红黑树的根节点被初始化为null,因此插入第一个值时,x等于nullbool comp = true; // 比较大小的布尔值while (x != 0) // x节点不为null,说明我们找到插入新节点的位置,于是执行while循环内的语句,不停向下遍历{y = x; // y保存着x节点的父节点// 如果待插入的值小于节点x的值,comp为true,否则comp为false。key_compare是比较大小的函数(由模板指定)comp = key_compare(keyOfValue()(v), key(x)); // 如果comp为true,说明待插入值小于节点x的值,我们沿左走,令x为x的左子树// 如果comp为false,说明待插入值大于节点x的值,我们沿右走,令x为x的右子树x = comp ? left(x) : right(x);}iterator j = iterator(y); // 令j指向插入点的父节点if (comp) // 如果插入的值比父节点的值小(意味着我们要插入到父节点的左边),进入if{// begin()调用leftmost(),获得整颗树最左侧的节点,如果插入节点为整颗树的最左侧节点就进入ifif (begin() == j) {return std::pair<iterator, bool>(__insert(x, y, v), true); // x是插入点、y为插入点的父节点,v为插入的值}else{j--;}}// 新值不与既有节点值重复,可以插入if (key_compare(key(j.node), keyOfValue()(v))){return std::pair<iterator, bool>(__insert(x, y, v), true);}return std::pair<iterator, bool>(j, false); // 如果到了这里,说明新值一定与树中键值重复,不能插入}/*插入新值,节点的键值允许重复返回红黑树的迭代器,该迭代器指向新节点*/iterator insert_equal(const value_type& v){link_type y = header;link_type x = root(); // x指向根节点while (x != 0) // 从根节点开始向下寻找合适的插入点{y = x;// 遇大往右,遇小往左x = key_compare(keyOfValue()(v), key(x)) ? left(x) : right(x);}return __insert(x, y, v); // x为待插入点,y为插入点的父节点,v为插入的值}/* 寻找红黑树中是否有键值为k的节点 */iterator find(const value_type& k){link_type y = header; // 令y等于哨兵节点(哨兵节点不是树的一部分,但是其parent指向根节点)link_type x = root(); // 拿到根节点while (x != 0){// key_compare是比较大小的函数if (!key_compare(key(x), k)) // x值大于k{y = x; x = left(x); // 遇到大值就向左走}else // x值与于k{x = right(x); // 遇到小值往左走}}iterator j = iterator(y);return (j == end() || key_compare(k, key(j.node))) ? end() : j;}private:/*插入操作x_:待插入节点y_:插入节点的父节点v:插入的值*/iterator __insert(base_ptr x_, base_ptr y_, const value_type& v){// base_ptr实为__rb_tree_node_base*,link_type实为__rb_tree_node*// 我们把__rb_tree_node_base*向下强转为__rb_tree_node*// __rb_tree_node结构体比__rb_tree_node_base结构体多了value_field,value_field用于保存值link_type x = (link_type)x_; // x指向插入点link_type y = (link_type)y_; // y指向插入点的父节点link_type z;// 1.y == header:插入点的父节点为header(注意header是哨兵,header不属于树的一部分,但是header的parent指向根节点)。y == header说明插入点为根节点// 2.x == 0:说明插入点在叶子节点下方(叶子节点的左子树和右子树均为null),也就是在叶子节点下挂新的节点;x != 0说明插入点在树的内部某个节点上// 3.key_compare(keyOfValue()(v), key(y)):带插入节点的值要比父节点的值小(意味着我们要插入到父节点的左子树上)if (y == header || x != 0 || key_compare(keyOfValue()(v), key(y))){z = create_node(v); // 创建新节点,令新节点的值(value_field)为vleft(y) = z; // 令父节点的左子树为z,我们成功的把新节点加入到树中了if (y == header) // y == header:插入点的父节点为header,说明根节点还没有被初始化,进入if就是要初始化根节点{root() = z; // z成了根节点rightmost() = z; // 令z为整棵树最右边的节点}else if (y == leftmost()) // 如果父节点是整颗树最左边的节点{leftmost() = z; // 我们把新节点作为整棵树最左边的节点}}else{z = create_node(v); // 创建新节点right(y) = z; // 插入节点到父节点的右边if (y == rightmost()) // 如果父节点是整颗树最右边的节点{rightmost() = z; // 我们把新节点作为整棵树最右边的节点}}parent(z) = y; // 令新节点的父节点为yleft(z) = 0; // 新节点的左子树为nullright(z) = 0; // 新节点的右子树为null__rb_tree_rebalance(z, header->parent); // header是哨兵,一旦建立就不改变,header->parent执行树的根节点++node_count; // 增加节点数return iterator(z);}/*重新平衡红黑树(改变颜色和旋转树形)x是新增节点root为根节点*/inline void __rb_tree_rebalance(__rb_tree_node_base* x, __rb_tree_node_base*& root){x->color = __rb_tree_red; // 新插入的节点是红色的// 如果新增节点(x)不为根节点,且新增节点的父节点为红色,那麻烦就大了,要进入while一顿折腾while (x != root && x->parent->color == __rb_tree_red){if (x->parent == x->parent->parent->left) // 如果父节点是祖父节点的左子节点{__rb_tree_node_base* y = x->parent->parent->right; // 令y为伯父节点if (y && y->color == __rb_tree_red) // 如果伯父节点存在且为红{x->parent->color = __rb_tree_black;y->color = __rb_tree_black;x->parent->parent->color = __rb_tree_red;x = x->parent->parent;}else // 如果伯父节点不存在,或者伯父节点为黑{if (x == x->parent->right) // 如果新增节点为父节点的右子节点(为父节点的右子节点,这说明插入节点的方式是内插){x = x->parent; // 父节点为旋转支点// 整理一下从while开始的条件判断分支,我们可以得出做左旋转的条件:// 1.新增节点不是根节点// 2.新增节点的父节点是红色// 3.父节点是祖父节点的左子节点// 4.伯父节点不存在,或者伯父节点为黑// 5.新增节点为父节点的右子节点__rb_tree_rotate_left(x, root); // 做左旋转}x->parent->color = __rb_tree_black; // 修改颜色x->parent->parent->color = __rb_tree_red; // 修改颜色__rb_tree_rotate_right(x->parent->parent, root); // 左旋完了接着右旋}}else // 如果父节点是祖父节点的右子节点{__rb_tree_node_base* y = x->parent->parent->left; // 令y为伯父节点if (y && y->color == __rb_tree_red) // 如果伯父节点存在且为红{x->parent->color = __rb_tree_black;y->color = __rb_tree_black;x->parent->parent->color = __rb_tree_red;x = x->parent->parent;}else // 如果伯父节点不存在,或者伯父节点为黑{// 如果新增节点为父节点的左子节点(为父节点的左子节点,这说明插入节点的方式是内插)if (x == x->parent->left){x = x->parent; // 父节点为旋转支点// 整理一下从while开始的条件判断分支,我们可以得出做右旋转的条件:// 1.新增节点不是根节点// 2.新增节点的父节点是红色// 3.父节点是祖父节点的右子节点// 4.伯父节点不存在,或者伯父节点为黑// 5.新增节点为父节点的左子节点__rb_tree_rotate_right(x, root); // 做右旋转}x->parent->color = __rb_tree_black; // 修改颜色x->parent->parent->color = __rb_tree_red; // 修改颜色__rb_tree_rotate_left(x->parent->parent, root); // 右旋完了接着左旋}}}root->color = __rb_tree_black; // 树的根节点永远是黑}/*左旋转新节点比为红节点,如果插入节点的父节点也为共色,就违反了红黑树规则,此时必须做旋转x:左旋转的支点root:红黑树的根*/inline void __rb_tree_rotate_left(__rb_tree_node_base* x, __rb_tree_node_base*& root){__rb_tree_node_base* y = x->right; // y为旋转点的右子节点x->right = y->left; // 旋转点为父,旋转点的右子节点为子,完成父子对换if (y->left != 0){y->left->parent = x;}y->parent = x->parent;if (x == root){root = y; // 令y为根节点}else if (x == x->parent->left){x->parent->left = y; // 令旋转点的父节点的左子节点为y}else{x->parent->right = y; // 令旋转点的父节点的右子节点为y}y->left = x; // 右子节点的左子树为xx->parent = y; // 右子节点为旋转点的父节点}/*右旋转新节点比为红节点,如果插入节点的父节点也为共色,就违反了红黑树规则,此时必须做旋转x:右旋转的支点root:红黑树的根*/inline void __rb_tree_rotate_right(__rb_tree_node_base* x, __rb_tree_node_base*& root){__rb_tree_node_base* y = x->left; // 令y为旋转点的左子节点x->left = y->right; // 令y的右子节点为旋转点的左子节点if (y->right != 0){y->right->parent = x; // 设定父节点}y->parent = x->parent;// 令y完全顶替x的地位(必须将对其父节点的关系完全接收过来)if (x == root){root = y; // x为根节点}else if (x == x->parent->right){x->parent->right = y; // x为其父节点的右子节点}else{x->parent->left = y; // x为其父节点的左子节点}y->right = x;x->parent = y;}#pragma endregion};}#endif


 

3.测试

      3.1 测试用例

         测试代码,test_rb_tree.cpp:

/********************************************************************  Copyright(c) 2016 Chen Gonghao*  All rights reserved.**  chengonghao@yeah.net**  文件内容:红黑树的测试******************************************************************/#include "stdafx.h"#include "rb_tree.h"using namespace::std;int _tmain(int argc, _TCHAR* argv[]){using namespace::CGH;cgh_rb_tree<int, int, identity<int>, less<int>> test;test.insert_unique(10);test.insert_unique(7);test.insert_unique(8);test.insert_unique(15);test.insert_unique(5);test.insert_unique(6);test.insert_unique(11);test.insert_unique(13);test.insert_unique(12);cgh_rb_tree<int, int, identity<int>, less<int>>::iterator it = test.begin();int i = 1;for (; it != test.end(); it++){string color = it.color() == true ? "黑" : "红";std::cout << *it << " ( " << color.c_str() << " )" << endl << endl;}std::cout << "树的根节点:" << test.root_value()<< endl << endl;std::cout << "树的大小:" << test.size() << endl << endl;system("pause");return 0;}


 

测试图:


根据测试图可以画出红黑树:


         header是哨兵节点,有三个作用:

1.      标识根节点的位置;

2.      标识红黑树最左边节点的位置

3.      标识红黑树最右边节点的位置

 

3.2 分析一个节点的插入

         我们举例分析8插入红黑树的过程。

1.      插入8之前,红黑树的样子:


2.      8小于10,大于7,应该插在7的右子树上


这明显违反了红黑树的规定:红的下面不能挂红的,节点8的伯父节点(10的右子节点)为黑色(空节点为黑色),我们以7为支点,对7、8两节点做左旋转。为方便起见,令7为x,8为y。

3.      左旋第一步(左旋操作均在__rb_tree_rotate_left函数内)


4.      左旋第二步(左旋操作均在__rb_tree_rotate_left函数内)


5.      左旋第三步(左旋操作均在__rb_tree_rotate_left函数内)


6.      左旋第四步(左旋操作均在__rb_tree_rotate_left函数内)


7.      左旋第五步(左旋操作均在__rb_tree_rotate_left函数内)


左旋结束,我们先改变节点颜色(对应代码在__rb_tree_rebalance函数中):


根节点不能为红色,我们还需要做右旋,把节点8作为根节点。

8.      右旋第一步(右旋操作均在__rb_tree_rotate_right函数内),令根节点为x,节点8为y


9.      右旋第二步(右旋操作均在__rb_tree_rotate_right函数内)


10.  右旋第三步(右旋操作均在__rb_tree_rotate_right函数内)


11.  右旋第四步(右旋操作均在__rb_tree_rotate_right函数内)


12.  右旋第五步(右旋操作均在__rb_tree_rotate_right函数内)


 到此为止,红黑树又恢复平衡啦~


以下是我理解插入操作时边打断点边画的图,纪念一下:


5.      rb_tree.h,红黑树的实现,位于cghSTL/associativecontainers/RB-tree/
2 0