STL 簡單紅黑樹的實現
1.紅黑樹簡介
二叉搜尋樹能夠提供對數的元素插入和訪問。二叉搜尋樹的規則是:任何節點的鍵值一定大於其左子樹的每一個節點值,並小於右子樹的每一個節點值。
常見的二叉搜尋樹有AVL-tree、RB-tree(紅黑樹)。紅黑樹具有極佳的增、刪、查效能,故我們選擇紅黑樹作為關聯式容器(associative containers)的底層結構。
紅黑樹是每個節點都帶有顏色屬性的二叉查詢樹,顏色或紅色或黑色。在二叉查詢樹強制一般要求以外,對於任何有效的紅黑樹我們增加了如下的額外要求:
1. 節點是紅色或黑色。;
2. 根節點是黑色;
3. 每個葉節點(NILL節點,空節點)是黑色的;
4. 每個紅色節點的兩個子節點都是黑色(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點);
5. 從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點
由性質4可以推出:一條路徑上不能有兩個毗連的紅色節點。最短的可能路徑都是黑色節點,最長的可能路徑是交替的紅色和黑色節點。又根據性質5,所有最長的路徑都有相同數目的黑色節點,這就表明了沒有路徑能多於任何其他路徑的兩倍長。
以上約束條件強化了紅黑樹的關鍵性質: 從根到葉子的最長路徑不多於最短路徑的兩倍長。所以紅黑樹是大致上是平衡的(不像AVL-tree,要求絕對平衡)。樹的插入、刪除和查詢效率與樹的高度成比例,紅黑樹的高度上限允許在最壞情況下都是高效的,這是紅黑樹相對於其他二叉搜尋樹最大的優勢。
2.紅黑樹在STL中的實現
要學習STL關聯式容器,我們必須實現一顆紅黑樹。本文介紹紅黑樹在STL中的程式碼實現,為今後學習關聯式容器打下基礎。關於紅黑樹的詳細特性(如增加、刪除、旋轉等)不在討論範圍內,請查閱相關資料。
一顆STL紅黑樹的實現需要以下幾個檔案:
1. globalConstruct.h,構造和解構函式檔案,位於cghSTL/allocator/cghAllocator/
2. cghAlloc.h,空間配置器檔案,位於cghSTL/allocator/cghAllocator/
6. test_rb_tree.cpp,紅黑樹的測試,位於
2.1構造與析構
先看第一個,globalConstruct.h建構函式檔案
/*******************************************************************
* Copyright(c) 2016 Chen Gonghao
* All rights reserved.
*
* [email protected]
*
* 功能:全域性構造和析構的實現程式碼
******************************************************************/
#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.
*
* [email protected]
*
* 功能: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 T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef size_t size_type;
typedef ptrdiff_t difference_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.
*
* [email protected]
*
* 檔案內容:紅黑樹的節點
******************************************************************/
#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; // 紅色為0
const __rb_tree_color_type __rb_tree_black = true; // 黑色為1
/* 基層節點 */
struct __rb_tree_node_base{
typedef __rb_tree_color_type color_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.
*
* [email protected]
*
* 檔案內容:紅黑樹的迭代器
******************************************************************/
#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_ptr base_ptr; // 父類節點指標
typedef std::bidirectional_iterator_tag iterator_category;
typedef ptrdiff_t difference_type;
base_ptr node; // 成員變數:指向父類節點的指標,這是聯絡迭代器和節點的紐帶
/* 迭代器的子類實現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 value value_type;
typedef ref reference;
typedef ptr pointer;
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_t size_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.
*
* [email protected]
*
* 檔案內容:紅黑樹
******************************************************************/
#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 typedef
protected:
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_type color_type; // 節點顏色
public:
typedef key key_type; // 鍵
typedef value value_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_t size_type;
typedef ptrdiff_t difference_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指標指向根節點,初始化紅黑樹的根節點指標為null
leftmost() = 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拿到header
link_type x = root(); // x拿到紅黑樹的根節點,紅黑樹的根節點被初始化為null,因此插入第一個值時,x等於null
bool 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(),獲得整顆樹最左側的節點,如果插入節點為整顆樹的最左側節點就進入if
if (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)為v
left(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; // 令新節點的父節點為y
left(z) = 0; // 新節點的左子樹為null
right(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; // 右子節點的左子樹為x
x->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.
*
* [email protected]
*
* 檔案內容:紅黑樹的測試
******************************************************************/
#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函式內)
到此為止,紅黑樹又恢復平衡啦~
以下是我理解插入操作時邊打斷點邊畫的圖,紀念一下:
相關推薦
STL 簡單紅黑樹的實現
1.紅黑樹簡介 二叉搜尋樹能夠提供對數的元素插入和訪問。二叉搜尋樹的規則是:任何節點的鍵值一定大於其左子樹的每一個節點值,並小於右子樹的每一個節點值。 常見的二叉搜尋樹有AVL-tree、RB-tree(紅黑樹)。紅黑樹具有極佳的增、刪、查效能,故我們選擇紅黑樹作為關聯式容
c++ STL 紅黑樹實現
紅黑樹是一種自平衡二叉查詢樹,它的操作有著良好的最壞情況執行時間,並且在實踐中是高效的: 它可以在O(log n)時間內做查詢,插入和刪除,這裡的n是樹中元素的數目。 紅黑樹應用: 1.linux核心中,程序的虛擬地址區間由紅黑樹組織管理 2.nginx中,超時時間由紅黑
ConcurrentHashMap與紅黑樹實現分析Java8
本文學習知識點 1、二叉查詢樹,以及二叉樹查詢帶來的問題。 2、平衡二叉樹及好處。 3、紅黑樹的定義及構造。 4、ConcurrentHashMap中紅黑樹的構造。 在正式分析紅黑樹之前,有必要了解紅黑樹的發展過程,請讀者耐心閱讀。 二叉查詢樹 紅黑樹的起源得從二叉查詢
作業系統簡單問題&&簡單紅黑樹
作業系統問題 1.程序和執行緒的區別:a.粒度性分析;b.排程性分析;c.系統開銷分析:執行緒不擁有系統資源; 2.程序不同與互斥: 互斥:是指某一資源同時只允許一個訪問者訪問,訪問無序; 同步:在互斥基礎上,通過其他機制實現訪問者對資源的有序訪問; 同步體
《演算法》中的紅黑樹實現
有別於上一篇文章介紹的紅黑樹,在《演算法<第四版>》一書中用另一套規則實現了紅黑樹,主要手段是遞迴,實現思路來自於2-3樹,這本書中有詳細的解讀,在這裡我談談自己對它的理解。 首先,在之前文章中介紹的紅黑樹,我們把節點看成紅,黑兩色,而這裡紅節點指的是它指向父親的
Python紅黑樹實現(帶樹的圖形化展示方法)
BLACK = 0 RED = 1 #graphic elements of rbtree for printing VC = '│' HC = '─' SIZE = 3 RIG = '┌' + HC * SIZE LEF = '└' + HC * SIZE SP = ch
紅黑樹的簡單實現
紅黑樹 LINDA 2018/9/25 前言 如果你還是對寫紅黑樹毫無頭緒,可以看一下我的思路,從普通二叉搜尋樹的插入操作是如何一步步“進化”為真正的紅黑樹的插入操作的。 紅黑樹的四個規則: (1) 每個結點要麼是紅的,要麼是黑的; (2) 根結點必須為黑
stl map底層之紅黑樹插入步驟詳解與程式碼實現
本篇文章並沒有詳細的講解紅黑樹各方面的知識,只是以圖形的方式對紅黑樹插入節點需要進行調整的過程進行的解釋。 最近在看stl原始碼剖析,看到map底層紅黑樹的實現。為了加深對於紅黑樹的理解就自己動手寫了紅黑樹插入的實現。關於紅黑樹插入節點後破壞紅黑樹性質的幾種情況,可以在網
紅黑樹C++實現
con colors end ase 復制代碼 設置 typename ucc 技術 1 /* 2 * rbtree.h 3 * 1. 每個節點是紅色或者黑色 4 * 2. 根節點是黑色 5 * 3. 每個葉子節點是黑色(該葉子節點就空的節點)
查找(一)史上最簡單清晰的紅黑樹解說
ont 演示 detail align article 向上 節點 動態插入 列表 查找(一) 我們使用符號表這個詞來描寫敘述一張抽象的表格。我們會將信息(值)存儲在當中,然後依照指定的鍵來搜索並獲取這些信息。鍵和值的詳細意義取決於不同的應用。 符號表中可能會保
數據結構 - 紅黑樹(Red Black Tree)插入詳解與實現(Java)
啟示 dpa con 技術分享 節點數 src 通知 一點 this 最終還是決定把紅黑樹的篇章一分為二,插入操作一篇,刪除操作一篇,因為合在一起寫篇幅實在太長了,寫起來都覺得累,何況是閱讀並理解的讀者。 紅黑樹刪除操作請參考 數據結構 - 紅黑樹(Red
數據結構 - 紅黑樹(Red Black Tree)刪除詳解與實現(Java)
replace ati 轉載 之前 9.png one com 四種 簡單 本篇要講的就是紅黑樹的刪除操作 紅黑樹插入操作請參考 數據結構 - 紅黑樹(Red Black Tree)插入詳解與實現(Java) 紅黑樹的刪除是紅黑樹操作中比較麻煩且比較有意
紅黑樹的實現——c++
實現 define nor 過程 紅黑樹刪除 實現類 節點類 技術 class 紅黑樹介紹參考上一篇。 1. 基本定義 enum RBTColor{RED, BLACK}; template <class T> class RBTNode{ pu
二叉樹與紅黑樹的java實現
二叉樹的java實現 public class BinaryTree { /** * 根節點 */ private static Node root; static class Node { int key; Node l
深入剖析紅黑樹以及JAVA實現
作者:美團技術團隊 連結:https://zhuanlan.zhihu.com/p/24367771 來源:知乎 著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。 紅黑樹是平衡二叉查詢樹的一種。為了深入理解紅黑樹,我們需要從二叉查詢樹開始講起。 BST
.集合Set,HashSet,TreeSet及其底層實現HashMap和紅黑樹;Collection總結
ONE.Set集合 one.Set集合的特點 無序,唯一 TWO.HashSet集合 1.底層資料結構是雜湊表(是一個元素為連結串列的陣列) 2.那麼HashSet如何來實現元素的唯一性的呢? 通過一HashSet新增字串的案例檢視HashSet中add()的原始碼,
紅黑樹下——紅黑樹的實現
1. 實現紅黑樹的基本思想 實際上,紅黑樹是有固定的平衡過程的:遇到什麼樣的節點分佈,我們就對應怎麼去調整。只要按照這些固定的調整規則來操作,就能將一個非平衡的紅黑樹調整成平衡的。 首先,我們需要再來看一下紅黑樹的定義: 根節點是黑色的; 每個葉子節點都是黑色的空節點(NIL),也就是說,葉子
根據紅黑樹的演算法來分析TreeMap的實現
TreeMap的實現是紅黑樹演算法的實現,所以要了解TreeMap就必須對紅黑樹有一定的瞭解。通過這篇博文你可以獲得如下知識點: 1、紅黑樹的基本概念。 &nb
紅黑樹的快速實現
紅黑樹的概述: 紅黑樹本質上是一種二叉查詢樹,但它在二叉查詢樹的基礎上額外添加了一個標記(顏色),同時具有一定的規則。這些規則使紅黑樹保證了一種平衡,插入、刪除、查詢的最壞時間複雜度都為 O(logn)。 紅黑樹的性質: 1、每個節點要麼是紅色,要麼是黑色; 2、根節點
紅黑樹(Red Black Tree)刪除詳解與實現(Java)
本篇要講的就是紅黑樹的刪除操作 紅黑樹的刪除是紅黑樹操作中比較麻煩且比較有意思的一部分。 在此之前,重申一遍紅黑樹的五個定義: 1. 紅黑樹的節點要不是黑色的要不是紅色的 2. 紅黑樹的根節點一定是黑色的 3. 紅黑樹的所有葉子節點都是黑色的(注意:紅黑樹的葉子節點指Nil節點