目录
一、改造红黑树
1、模板T改造节点
2、提取节点中的key
3、迭代器类
operator++
operator--
4、改造insert
5、红黑树迭代器
6、 普通迭代器构造const迭代器
二、set
三、map
在stl中map和set的结构中,他们都使用一个红黑树进行封装。
由上图可知,set传给红黑树节点的两个模板参数都是key,而map传给的红黑树的第一个模板参数是key、第二个参数是pair,因此我们首先对上一篇文章中的红黑树进行改造。
一、改造红黑树
#pragma onceenum Colour
{RED,BLACK,
};template<class T>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED){}
};template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{typedef RBTreeNode<T> Node;typedef __RBTreeIterator<T, Ref, Ptr> Self;Node* _node;__RBTreeIterator(Node* node):_node(node){}// 1、typedef __RBTreeIterator<T, T&, T*> itertaor; 拷贝构造// 2、 typedef __RBTreeIterator<T, const T&, const T*> const_itertaor;// 支持普通迭代器构造const迭代器的构造函数__RBTreeIterator(const __RBTreeIterator<T, T&, T*>& it):_node(it._node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}Self& operator++(){if (_node->_right){// 1、右不为空,下一个就是右子树的最左节点Node* subLeft = _node->_right;while (subLeft->_left){subLeft = subLeft->_left;}_node = subLeft;}else{// 2、右为空,沿着到根的路径,找孩子是父亲左的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}Self& operator--(){if (_node->_left){// 1、左不为空,找左子树最右节点Node* subRight = _node->_left;while (subRight->_right){subRight = subRight->_right;}_node = subRight;}else{// 2、左为空,孩子是父亲的右的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}
};template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public:~RBTree(){_Destroy(_root);_root = nullptr;}
public:typedef __RBTreeIterator<T, T&, T*> itertaor;typedef __RBTreeIterator<T, const T&, const T*> const_itertaor;itertaor begin(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return itertaor(cur);}itertaor end(){return itertaor(nullptr);}const_itertaor begin() const{Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return const_itertaor(cur);}const_itertaor end() const{return const_itertaor(nullptr);}Node* Find(const K& key){Node* cur = _root;KeyOfT kot;while (cur){if (kot(cur->_data) < key){cur = cur->_right;}else if (kot(cur->_data) > key){cur = cur->_left;}else{return cur;}}return nullptr;}pair<itertaor, bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(itertaor(_root), true);}KeyOfT kot;Node* parent = nullptr;Node* cur = _root;while (cur){if (kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else{return make_pair(itertaor(cur), false);}}cur = new Node(data);Node* newnode = cur;if (kot(parent->_data) > kot(data)){parent->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;// 情况1:u存在且为红,变色处理,并继续往上处理if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 继续往上调整cur = grandfather;parent = cur->_parent;}else // 情况2+3:u不存在/u存在且为黑,旋转+变色{// g// p u// c if (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g// p u// cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;//parent->_col = RED;grandfather->_col = RED;}break;}}else // (grandfather->_right == parent){// g// u p// cNode* uncle = grandfather->_left;// 情况1:u存在且为红,变色处理,并继续往上处理if (uncle && uncle->_col == RED){parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;// 继续往上调整cur = grandfather;parent = cur->_parent;}else // 情况2+3:u不存在/u存在且为黑,旋转+变色{// g// u p// cif (cur == parent->_right){RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}else{// g// u p// cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return make_pair(itertaor(newnode), true);;}bool IsBalance(){if (_root && _root->_col == RED){cout << "根节点颜色是红色" << endl;return false;}int benchmark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)++benchmark;cur = cur->_left;}// 连续红色节点return _Check(_root, 0, benchmark);}int Height(){return _Height(_root);}private:void _Destroy(Node* root){if (root == nullptr){return;}_Destroy(root->_left);_Destroy(root->_right);delete root;}int _Height(Node* root){if (root == NULL)return 0;int leftH = _Height(root->_left);int rightH = _Height(root->_right);return leftH > rightH ? leftH + 1 : rightH + 1;}bool _Check(Node* root, int blackNum, int benchmark){if (root == nullptr){if (benchmark != blackNum){cout << "某条路径黑色节点的数量不相等" << endl;return false;}return true;}if (root->_col == BLACK){++blackNum;}if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << "存在连续的红色节点" << endl;return false;}return _Check(root->_left, blackNum, benchmark)&& _Check(root->_right, blackNum, benchmark);}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* ppnode = parent->_parent;subR->_left = parent;parent->_parent = subR;if (ppnode == nullptr){_root = subR;_root->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* ppnode = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subL;}else{ppnode->_right = subL;}subL->_parent = ppnode;}}private:Node* _root = nullptr;
};
1、模板T
改造节点
我们需要将节点结构体RBTreeNode
中的数据成员_data
的类型改为模板参数T
。这样,每个节点就可以存储不同类型的数据,以适应map的节点存储pair结构,set的节点存储key。
template<class T>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;RBTreeNode(const T& data): _left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED){}
};
2、提取节点中的key
在上述代码中,我们可以看到`RBTree`类的定义如下:
template<class K, class T, class KeyOfT>
class RBTree
{// ...
};
其中,`K`是键的类型,`T`是值的类型,`KeyOfT`是一个辅助类也叫作仿函数,用于从值中提取键。在`set`中,键和值的类型相同,因此`KeyOfT`可以直接使用`K`。而在`map`中,键和值的类型不同,因此我们需要对`KeyOfT`进行修改。
对于`map`类,我们需要将`KeyOfT`修改为适应`pair<const K, V>`类型的辅助类。我们可以定义一个新的辅助类(仿函数)`MapKeyOfT`,其内部使用重载了函数调用运算符 operator()
,这个辅助类的作用是从`pair<const K, V>`类型的值中提取出键`K`。同时,我们将`RBTree`的第三个模板参数修改为`MapKeyOfT`。
template<class K, class V>
class map
{struct MapKeyOfT{const K& operator()(const pair<const K, V>& kv){return kv.first;}};
private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
对于`set`类,我们需要将`KeyOfT`修改为配合map使用的辅助类。我们可以定义一个仿函数`SetKeyOfT`, 通过重载operator()直接返回key。同时,我们将第三个模板参数修改为’SetKeyOfT'。
template<class K>
class set
{struct SetKeyOfT{const K& operator()(const K& key){return key;}};
private:RBTree<K, K, SetKeyOfT> _t;
};
3、迭代器类
template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{typedef RBTreeNode<T> Node;typedef __RBTreeIterator<T, Ref, Ptr> Self;Node* _node;__RBTreeIterator(Node* node):_node(node){}// 1、typedef __RBTreeIterator<T, T&, T*> itertaor; 拷贝构造// 2、 typedef __RBTreeIterator<T, const T&, const T*> const_itertaor;// 支持普通迭代器构造const迭代器的构造函数__RBTreeIterator(const __RBTreeIterator<T, T&, T*>& it):_node(it._node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}Self& operator++(){}Self& operator--(){}
};
这段代码定义了一个模板类__RBTreeIterator
,它有三个模板参数:T
、Ref
和Ptr
。
T
:表示节点中存储的数据类型。Ref
:表示解引用操作符*
返回的引用类型。Ptr
:表示箭头操作符->
返回的指针类型。
这个迭代器类用于遍历红黑树,并提供了对节点数据的访问功能。
在这个迭代器类中,有一个成员变量Node* _node
,它指向红黑树中的一个节点。
- 构造函数
__RBTreeIterator(Node* node)
用于初始化迭代器对象,将_node
指针指向传入的节点。 - 拷贝构造函数
__RBTreeIterator(const __RBTreeIterator<T, T&, T*>& it)
用于从普通迭代器构造const迭代器。它将传入迭代器的_node
指针赋值给当前迭代器的_node
成员变量。 - 重载解引用操作符
*
,使得可以通过迭代器访问节点的数据。它返回的是Ref
类型的引用,可以直接修改节点的数据。 - 重载箭头操作符
->
,使得可以通过迭代器访问节点的数据。它返回的是Ptr
类型的指针,可以通过指针访问节点的数据成员。 - 重载不等于操作符
!=
,用于比较两个迭代器是否不相等。它比较两个迭代器的_node
指针是否相等。 - 重载前缀递增操作符
++
,使得可以将迭代器指向下一个节点。如果当前节点有右子节点,则下一个节点是右子树的最左节点;否则,沿着到根的路径,找到第一个孩子是父节点左孩子的祖先节点。 - 重载前缀递减操作符
--
,使得可以将迭代器指向上一个节点。如果当前节点有左子节点,则上一个节点是左子树的最右节点;否则,沿着到根的路径,找到第一个孩子是父节点右孩子的祖先节点。
operator++
Self& operator++(){if (_node->_right){// 1、右不为空,下一个就是右子树的最左节点Node* subLeft = _node->_right;while (subLeft->_left){subLeft = subLeft->_left;}_node = subLeft;}else{// 2、右为空,沿着到根的路径,找孩子是父亲左的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}
首先,函数检查当前节点的右子节点是否存在。如果存在,说明存在右子树,下一个节点就是右子树中的最左节点。因此,函数将迭代器指向右子树中的最左节点,并结束函数的执行。
如果当前节点的右子节点为空,说明不存在右子树。在这种情况下,函数需要沿着到根的路径向上查找,直到找到一个节点,该节点是其父节点的左子节点。这是因为在红黑树中,右子树为空的节点的下一个节点是其父节点。
函数使用两个指针变量 cur 和 parent 来进行查找。
- 开始时,将 cur 初始化为当前节点,将 parent 初始化为 cur 的父节点。
- 然后,函数进入一个循环,只要 parent 存在且 cur 是 parent 的右子节点,就继续向上查找。
- 在循环中,将 cur 更新为 parent,将 parent 更新为 cur 的父节点。
- 这样,函数就可以找到下一个节点,并将迭代器指向该节点。
最后,函数返回迭代器自身的引用,以支持链式操作。
operator--
Self& operator--(){if (_node->_left){// 1、左不为空,找左子树最右节点Node* subRight = _node->_left;while (subRight->_right){subRight = subRight->_right;}_node = subRight;}else{// 2、左为空,孩子是父亲的右的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}
首先,函数检查当前节点的左子节点是否存在。如果存在,说明存在左子树,前一个节点就是左子树中的最右节点。因此,函数将迭代器指向左子树中的最右节点,并结束函数的执行。
如果当前节点的左子节点为空,说明不存在左子树。在这种情况下,函数需要沿着到根的路径向上查找,直到找到一个节点,该节点是其父节点的右子节点。这是因为在红黑树中,左子树为空的节点的前一个节点是其父节点。
函数使用两个指针变量 cur
和 parent
来进行查找。
- 开始时,将
cur
初始化为当前节点,将parent
初始化为cur
的父节点。 - 然后,函数进入一个循环,只要
parent
存在且cur
是parent
的左子节点,就继续向上查找。 - 在循环中,将
cur
更新为parent
,将parent
更新为cur
的父节点。 - 这样,函数就可以找到前一个节点,并将迭代器指向该节点。
最后,函数返回迭代器自身的引用,以支持链式操作。
4、改造insert
1. 修改函数的参数类型为 `const T& data`,以接受要插入的数据。
返回类型 pair<iterator, bool>
是为了提供更多的信息给调用者,以便在插入操作后进行进一步的处理。
pair<iterator, bool> Insert(const T& data)
-
iterator
:表示插入节点的迭代器。通过返回迭代器,调用者可以方便地访问插入的节点,进行后续的操作,如修改节点的值、删除节点等。 -
bool
:表示插入是否成功的标志。通过返回布尔值,调用者可以知道插入操作是否成功。如果插入成功,布尔值为true
;如果插入失败(节点已存在),布尔值为false
。
2. 将 `pair<K, V>` 替换为 `T`,因为现在的节点类型是 `RBTreeNode<T>`。
cur = new Node(data);
3. 在函数内部,将 `_kv` 替换为 `_data`,以匹配节点的数据成员名称。
if (cur->_data < data){// ...
}
else if (cur->_data > data){// ...
}
else{//查找失败return make_pair(iterator(cur), false);
}
4. 新增的 KeyOfT kot;
是为了创建 KeyOfT
类型的对象 kot
,用于从节点数据类型 T
中提取键值。
KeyOfT kot;
while (cur)
{if (kot(cur->_data) < kot(data)){//..}else if (kot(cur->_data) > kot(data)){//..}else{//..}
}
5. 在修改后的 Insert
函数中,新增的 Node* newnode = cur;
是为了在返回 pair<iterator, bool>
时,将插入的节点的迭代器保存下来。
Node* newnode = cur;
- 在原始的代码中,返回的迭代器是通过
iterator(cur)
创建的,其中cur
是指向新插入节点的指针。然而,在后续的操作中,cur
的值可能会发生变化,因为在红黑树的调整过程中,节点的位置可能会发生改变。 - 为了确保返回的迭代器指向插入的节点,我们将
cur
的值保存在newnode
中。这样,在返回pair<iterator, bool>
时,我们使用iterator(newnode)
创建迭代器,确保迭代器指向插入的节点。 - 因此,
Node* newnode = cur;
的目的是为了保存插入节点的指针,以便在返回迭代器时使用。这样可以确保返回的迭代器指向正确的节点,而不受后续操作的影响。
6. 在函数的返回语句中,使用 `make_pair` 创建一个 `pair` 对象,其中包含插入的迭代器和插入是否成功的标志。
return make_pair(iterator(newnode), true);
5、红黑树迭代器
template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef __RBTreeIterator<T, T&, T*> itertaor;typedef __RBTreeIterator<T, const T&, const T*> const_itertaor;itertaor begin(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return itertaor(cur);}itertaor end(){return itertaor(nullptr);}const_itertaor begin() const{Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return const_itertaor(cur);}const_itertaor end() const{return const_itertaor(nullptr);}
- 首先,我们定义了两个迭代器类型
iterator
和const_iterator
,分别对应可修改和只读的迭代器。这些迭代器是通过__RBTreeIterator
结构体模板实例化得到的。 - 然后,我们定义了
begin()
和end()
函数,分别用于返回红黑树的起始迭代器和结束迭代器。 - 在
begin()
函数中,我们从根节点开始,沿着左子节点的路径向下遍历,直到找到最左边的节点。这个节点就是红黑树中最小的节点,也是起始节点。我们将其作为参数传递给__RBTreeIterator
构造函数,创建一个迭代器对象,并将其返回。 - 在
end()
函数中,我们直接返回一个迭代器对象,其指针成员_node
设置为nullptr
,表示结束位置。 - 对于
const
重载的版本,逻辑与非const
版本相同,只是返回的是const_iterator
类型的迭代器对象。
6、 普通迭代器构造const迭代器
template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{typedef RBTreeNode<T> Node;typedef __RBTreeIterator<T, Ref, Ptr> Self;Node* _node;__RBTreeIterator(Node* node):_node(node){}// 1、typedef __RBTreeIterator<T, T&, T*> itertaor; 拷贝构造// 2、 typedef __RBTreeIterator<T, const T&, const T*> const_itertaor;// 支持普通迭代器构造const迭代器的构造函数__RBTreeIterator(const __RBTreeIterator<T, T&, T*>& it):_node(it._node){}
}
const __RBTreeIterator<T, T&, T*>& it
部分没有显式的强制类型转换。它是一个常量引用,用于接收普通迭代器对象的引用作为参数,如果需要将普通迭代器对象转换为const迭代器对象,编译器会自动进行隐式类型转换。- 因为参数加上了
const
修饰符,这意味着它是一个常量引用。当使用普通迭代器对象调用构造函数时,编译器会进行隐式类型转换,将普通迭代器对象转换为const迭代器对象。
二、set
#pragma once#include "RBTree.h"namespace MySet
{template<class K>class set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K, K, SetKeyOfT>::const_itertaor iterator;typedef typename RBTree<K, K, SetKeyOfT>::const_itertaor const_iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}pair<iterator, bool> insert(const K& key){return _t.Insert(key);}private:RBTree<K, K, SetKeyOfT> _t;};
}
- 在命名空间MySet中,定义了一个内部结构体SetKeyOfT,它是一个仿函数,用于从键中提取键值。在这个仿函数中,重载了函数调用操作符operator(),接受一个键key作为参数,并返回该键本身。
- 接下来是set类的定义。它使用了模板参数K,表示集合中元素的类型。在set类中,使用了RBTree类来实现红黑树的功能。RBTree的模板参数为K、K和SetKeyOfT,表示键的类型、值的类型和键提取仿函数的类型。
- set类中定义了两个迭代器类型:iterator和const_iterator,它们分别是RBTree<K, K, SetKeyOfT>::const_iterator的别名。这两个迭代器类型用于遍历集合中的元素。
- set类还提供了begin和end方法,用于返回集合的起始迭代器和结束迭代器。begin方法返回的是红黑树的起始迭代器,而end方法返回的是红黑树的结束迭代器。
- 此外,set类还提供了insert方法,用于向集合中插入元素。它接受一个键key作为参数,并返回一个pair对象,其中包含一个迭代器和一个布尔值。迭代器指向插入的元素,布尔值表示插入是否成功。
- 最后,set类中有一个私有成员变量_t,它是一个RBTree<K, K, SetKeyOfT>类型的对象,用于存储集合的元素。
三、map
#pragma once#include "RBTree.h"namespace MyMap
{template<class K, class V>class map{struct MapKeyOfT{const K& operator()(const pair<const K, V>& kv){return kv.first;}};public:typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::itertaor iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}V& operator[](const K& key){pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));return ret.first->second;}pair<iterator, bool> insert(const pair<const K, V>& kv){return _t.Insert(kv);}private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;};
- 命名空间
MyMap
中,定义了一个内部结构体MapKeyOfT
,它是一个仿函数,用于从键值对中提取键。在这个仿函数中,重载了函数调用操作符operator()
,接受一个键值对kv
作为参数,并返回该键值对中的键first
。 - 接下来是
map
类的定义。它使用了两个模板参数K
和V
,分别表示键和值的类型。在map
类中,使用了RBTree
类来实现红黑树的功能。RBTree
的模板参数为K
、pair<const K, V>
和MapKeyOfT
,表示键的类型、键值对的类型和键提取仿函数的类型。 map
类中定义了一个迭代器类型iterator
,它是RBTree<K, pair<const K, V>, MapKeyOfT>::iterator
的别名。这个迭代器类型用于遍历映射中的键值对。map
类提供了begin
和end
方法,用于返回映射的起始迭代器和结束迭代器。begin
方法返回的是红黑树的起始迭代器,而end
方法返回的是红黑树的结束迭代器。- 此外,
map
类还重载了下标操作符[]
,使得可以通过键访问对应的值。它接受一个键key
作为参数,并返回对应的值的引用。如果键不存在于映射中,则会自动插入一个新的键值对,并返回对应值的引用。 map
类还提供了insert
方法,用于向映射中插入键值对。它接受一个键值对kv
作为参数,并返回一个pair
对象,其中包含一个迭代器和一个布尔值。迭代器指向插入的键值对,布尔值表示插入是否成功。- 最后,
map
类中有一个私有成员变量_t
,它是一个RBTree<K, pair<const K, V>, MapKeyOfT>
类型的对象,用于存储映射的键值对。