文章目录
控制底层红黑树模板参数
如果我们用一棵KV模型的红黑树同时实现map和set,我们就需要控制map和set中的所传入红黑树中的模板参数,其中,封装过程中,为了与红黑树的模板参数区分,我们将红黑树中模板参数由V改成T.
template <class K, class T>
struct RBTree
{
//
};
对于T来说,如果是set容器,那么T实例化出来的便是Key
namespace mySet
{
template<class K>
class set
{
public:
//
private:
RBTree<K, K>> _t;
};
}
如果是map容器,那么T实例化出来的便是pair<K,V>键值对.
namespace myMap
{
template<class K,class V>
class map
{
public:
//
private:
RBTree<K, pair<K, V>> _t;
};
}
T实例化例图如下:
那么,在红黑树中我们可不可以只保留一个模板参数T,根据实参类型转换为所需要的存储类型呢?
不可以,之所以还需要第一个模板参数K,就是因为STL中map所提供的find和erase接口需要key来指定删除或者查找的结点.
模板参数中的仿函数
在插入中,结点中存储的类型T可能为Set容器中的key,也有可能是Map中的pair<K,V>键值对,底层红黑树如何针对不同类型的容器进行比较呢?
我们所需要的是:
如果是set容器,那么T就是类型key,我们需要取数据类型为T的data就行.
如果是map容器,那么T就为键值对pair<K,V>,我们需要取键值对pair中的first与所给值key比较.
所以,综合以上情况,我们需要在上层set,map中添加一个仿函数,根据实参所传的不同类型进而获取不同数据类型来比较两个结点.
例如:
以下为set,map容器分别传入一个仿函数,在比较时会更具容器类型不同进而调用所需要的仿函数.
set仿函数:
namespace mySet
{
template<class K>
class set
{
public:
struct SetKeyOfT
{
const K& operator()(const K& key )
{
return key;
}
};
private:
RBTree<K, K, SetKeyOfT> _t;
};
}
map仿函数:
namespace myMap
{
template<class K,class V>
class map
{
public:
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
private:
RBTree<K, pair<K, V>,MapKeyOfT> _t;
};
}
所以,当容器为set时,红黑树中的仿函数就实例化为set容器中的仿函数,当容器为map时,红黑树中的仿函数就实例化为map中的仿函数.
进而,当我们通过上层容器中的Insert函数调用底层红黑树中的Insert函数时,在比较时就会根据存储类型T,分别调用所需要的仿函数进行结点的比较.
例如:
以下是底层红黑树Insert函数仿函数调用插入新结点部分代码:
bool Insert(const T& data ) //插入函数
{
KeyOfT kot;
if (_root == nullptr) //如果为空树,直接插入结点就可以了.
{
_root = new Node(data);
cout << kot(_root->_data);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur) //寻找插入位置
{
if (kot(data) < kot(cur->_data))
{
parent = cur;
cur = cur->_right;
}
else if ( kot( cur->_data) < kot(data))
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(data); //找到插入位置并插入.
//......
}
map,set中的正向迭代器
map和set中的迭代器实际上就是对指向结点的指针进行封装,正向迭代器中一个内置成员.
其中Ref,Ptr分别代表目标数据类型的引用,指针类型.
template <class T,class Ref,class Ptr>
struct _RBTreeIterator
{
typedef RBTreeNode<T> Node; //重新定义红黑树结点类型为Node
typedef _RBTreeIterator<T, Ref, Ptr> Self; //重新定义迭代器类型为Self
Node* _node;
}
正向迭代器的构造:
_RBTreeIterator(Node* node = nullptr)
:_node(node)
{
}
正向迭代器的解引用运算符重载:
当我们使用正向迭代器的解引用运算符重载时,直接返回目标数据的引用.
Ref operator*()
{
return _node->_data;
}
正向迭代器的->运算符重载
当我们使用正向迭代器的->运算符重载,直接返回目标数据的地址.
Ptr operator->()
{
return &_node->_data;
}
正向迭代器还包括了==,!=运算符重载,我们直接判断两个迭代器中的结点指针是否相同即可.
bool operator!=(const Self& s) const //普通对象和const对象都可以进行调用.
{
return _node != s._node;
}
bool operator==(const Self& s) const
{
return _node == s._node;
}
前置++运算符重载:
前置++运算重载是按照中序遍历进行访问的,主要有两个步骤
1: 如果it所指的结点中右子树存在,则访问右子树的最左结点.
2: 如果it所指的结点中右子树不存在,则寻找孩子不是父亲右的那个祖先,并对其进行访问.
代码如下:
Self& operator++() //前置++,返回的是自己.
{
if (_node->_right) //右子树存在,访问的是右子树的最左节点.
{
Node* left = _node->_right;
while ( left->_left)
{
left = left->_left;
}
_node = left;
}
else //右子树不存在,寻找孩子不是父亲右的那个祖先.
{
Node* parent = _node->_parent;
Node* cur = _node;
while ( parent && cur == parent->_right)
{
cur = cur->_parent;
parent = parent->_parent;
}
_node = parent;
}
return *this; //返回迭代器.
}
前置–运算符重载:
前置–运算重载是按照中序遍历进行访问的,主要有两个步骤:
1: 如果it所指的结点中左子树存在,则访问左子树的最右结点.
2: 如果it所指的结点中左子树不存在,则寻找孩子不是父亲左的那个祖先,并对其进行访问.
当正向迭代器实现后,我们可以在上层对该迭代器进一步封装.可以在底层红黑树的public区域中重新定义迭代器的类型为iterator,并且封装两个函数:
1: begin函数中迭代器指向底层红黑树的最左结点.
2: end函数中原本返回的是最后一个数据的下一个地址,如果有哨兵位则返回哨兵位,为了方便,我们采用的是返回nullptr构造的迭代器.(但是这样在it从end–遍历时会访问空指针造成程序崩溃Bug)
begin函数和end函数代码如下:
iterator begin() //指向的是底层红黑树的最左结点.