C++之STLmap和set的模拟实现
Posted 小赵小赵福星高照~
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++之STLmap和set的模拟实现相关的知识,希望对你有一定的参考价值。
模拟实现map和set
文章目录
我们首先来看一下源码当中的set和map的结构:
template <class Key,class Compare = less<Key>, class Alloc = alloc>
class set
public:
typedef Key key_type;
typedef Key value_type;
typedef rb_tree<key_type,value_type,identity<value_type>,key_compare,Alloc> rep_type;
rep_type t; //red-black tree representing set
;
template <class Key,class T, class Compare = less<Key>, class Alloc = alloc>
class map
public:
typedef Key key_type;
typedef pair<const Key,T> value_type;
typedef rb_tree<key_type,value_type,
select1st<value_type>,key_compare,Alloc> rep_type;
rep_type t; //red-black tree representing mapI
;
typedef Key value_type;
typedef rb_tree<key_type,value_type,identity<value_type>,key_compare,Alloc> rep_type
//set的红黑树模板参数
typedef pair<const Key,T> value_type;
typedef rb_tree<key_type,value_type,select1st<value_type>,key_compare,Alloc> rep_type;
//map的红黑树模板参数
可以看到set当中的红黑树key_type,value_type这两个模板参数,key_type是Key,而且value_type也被typedef成Key,两个Key是什么意思呢?而map当中的红黑树key_type,value_type这两个模板参数,key_type是Key,而且value_type被typedef成pair<const Key,T>
源码中红黑树的结构
template <class Key,class Value, class KeyOfValue,class Compare,class Alloc = allpc>
class rb_tree
protected:
typedef _rb_tree_node<Value> rb_tree_node;
typedef rb_tree_node* link_type;
protected:
link_type header;
;
template <class Value>
struct _rb_tree_node : public _rb_tree_node_base
typedef _rb_tree_node<Value>* link_type;
Value value_field;
;
可以看到红黑树的模板参数中的第二个参数是Value,通过这个参数就可以实现上层set给红黑树传Key,Value就是Key,上层map给红黑树pair<const K,V>,Value就是pair<const K,V>,红黑树中的数据值Value value_field就能够灵活变换。
template<class K>
class set
public:
//...
private:
RBTree<K,K> _t;
;
template<class K,class V>
class map
public:
//...
private:
RBTree<K,pair<K,V>> _t;
;
我们发现set底层的红黑树模板参数第一个和第二个一样,那么能不能不要第一个模板参数呢?
不能,对于set省略掉没有问题,因为set传入红黑树的参数一和二是相同的,但是map就不行,因为map如果只传入第二个参数pair,map提供的接口是有些需要Key值的,比如find和erase。
我们将底层红黑树的模板参数写成:
template<class K,class T>
class RBTree;
T来接受map和set传进来了第二个参数,他可能是key也可能是pair
相应的红黑树节点的数据类型我们也需要改成T:
enum Colour
BLACK,
RED
;
template<classT>
struct RBTreeNode
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
Colour _col;//颜色
T _data;//键值对
//构造函数
RBTreeNode(const T& data)
:_left(nullptr),
:_right(nullptr),
:_parent(nullptr),
:col(RED),
:_data(data)
;
T传key是key,传pair是pair
模板参数中的仿函数
我们前面可以看到红黑树源码当中还有一个模板参数仿函数,这个参数是用来比较键值用的,因为第二个参数是T,set传入的是key,map传入的是pair,在比较键值的时候,是set还可以直接比较,当时map时,pair是不支持比较的,所以上层容器需要给底层红黑树提供一个仿函数,用来重载()获取key值:
template<class K, class V>
class map
//仿函数
struct MapKeyOfT
const K& operator()(const pair<K, V>& kv) //返回键值对当中的键值Key
return kv.first;
;
public:
private:
RBTree<K, pair<K, V>, MapKeyOfT> _t;
;
set也需要写仿函数,传进去,因为底层红黑树没办法知道传进去的是set还是map,所以当需要键值进行比较大小时,红黑树都会根据仿函数来进行比较
set的仿函数:
template<class K>
class set
//仿函数
struct SetKeyOfT
const K& operator()(const K& key) //返回键值Key
return key;
;
public:
private:
RBTree<K, pair<K, V>, SetKeyOfT> _t;
;
增加了一个参数仿函数后,红黑树的参数也发生了变化:
template<class K,class T,class KeyOfT>
class RBTree;
相应的之前实现的红黑树的一些有比较键值的地方就需要改变,比如查找函数和插入函数
红黑树的迭代器的模拟实现
红黑树的迭代器的实现其实就是对节点的指针进行了封装
迭代器的基本结构
template<class T,class Ref,class Ptr>
struct RBTreeIterator
typedef RBTreeNode<T> Node;
typedef RBTreeIterator<T,Ref,Ptr> Self;//自身类型的typedef
Node* _node;
//构造函数
RBTreeIterator(Node* node = nullptr)
:_node(node)
;
红黑树的迭代器如何实现*呢?因为我们对节点的指针进行了封装,只需要返回节点的数据的引用即可
Ref operator*()
return _node->_data;
红黑树的迭代器如何实现->呢?因为我们对节点的指针进行了封装,只需要返回节点的数据的指针即可
Ptr operator->()
return &_node->_data;
相应的!=和==也可以写出来,直接判断两个迭代器所封装的节点是不是同一个即可:
bool operator!=(const Self& s) const
return _node!=s._node;//指向节点的指针不相同
bool operator==(const Self& s) const
return _node==s._node;指向节点的指针相同
那么红黑树的迭代器如何实现++呢?
一个节点进行++时,应该根据红黑树中序遍历序列找到当前节点的下一个节点,因为中序遍历是先访问左子树再访问根节点,最后访问右子树。
思路如下:
1、如果it指向节点的右子树不为空,下一个就++到右子树中序第一个节点,也就是右子树的最左节点。
2、如果it指向节点右子树为空,下一个++要访问的节点是,沿着it指向节点到根节点的路径中,遍历寻找孩子是父亲左的那个父亲节点
Self& operator++()
if(_node->_right)
//右子树中序第一个节点,也就是右子树的最左节点
Node* subLeft = _node->_right;
while(subLeft->_left)
subLeft = subLeft->_left;
_node = subLeft;
else
//右为空,当前子树已经访问完了,要去找祖先访问,沿着到根节点的路径往上走,
//找孩子是父亲左的那个父亲节点
Node* cur = _node;
Node* parent = cur->_parent;
while(parent && parent->_right == cur)
cur = parent;
parent = parent->_parent;
_node = parent;
return *this;
那么红黑树的迭代器如何实现–呢?和++的思路反一下就好了:
1、如果当前节点的左子树不为空,则–操作后要找到其左子树当中的最右节点
2、如果当前节点的左子树为空,则–操作后应该在该节点到根节点的路径中找到孩子在父亲右的祖先
Self operator--()
if (_node->_left)
//左子树中序第一个节点,也就是左子树的最右节点
//寻找该结点左子树当中的最右结点
Node* right = _node->_left;
while (right->_right)
right = right->_right;
_node = right;
else //结点的左子树为空
//寻找孩子在父亲右的祖先
Node* cur = _node;
Node* parent = cur->_parent;
while (parent&&cur == parent->_left)
cur = parent;
parent = parent->_parent;
_node = parent;
return *this;
最后我们需要在红黑树里面实现begin和end:
typedef RBTreeIterator<T,T&,T*> iterator;
typedef RBTreeIterator<T,const T&,const T*> const_iterator;
iterator begin()
Node* left = _root;
while(left && left->_left)
left = left->_left;
return iterator(left);//返回最左节点的迭代器
iterator end()
return iterator(nullptr);
我们实现的迭代器和STL当中的是有差别的,我们对end()–理应返回最后一个节点的迭代器,但是上面的实现无法实现此操作,在STL库当中实现红黑树时,他在根节点增加了头节点,它的左指针指向红黑树的最左节点,右指针指向红黑树当中的最右节点,父亲指针指向红黑树的根节点。通过一些逻辑的控制就可以实现上面的操作
红黑树的迭代器的整体代码
template<class T,class Ref,class Ptr>
struct RBTreeIterator
typedef RBTreeNode<T> Node;
typedef RBTreeIterator<T,Ref,Ptr> 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
return _node!=s._node;
bool operator==(const Self& s) const
return _node==s._node;
Self& operator++()
if(_node->_right)
//右子树中序第一个节点,也就是右子树的最左节点
Node* subLeft = _node->_right;
while(subLeft->_left)
subLeft = subLeft->_left;
_node = subLeft;
else
//右为空,当前子树已经访问完了,要去找祖先访问,沿着到根节点的路径往上走,
//找孩子是父亲左的那个父亲节点
Node* cur = _node;
Node* parent = cur->_parent;
while(parent && parent->_right == cur)
cur = parent;
parent = parent->_parent;
_node = parent;
return *this;
Self operator--()
if (_node->_left)
//左子树中序第一个节点,也就是左子树的最右节点
//寻找该结点左子树当中的最右结点
Node* right = _node->_left;
while (right->_right)
right = right->_right;
_node = right;
else //结点的左子树为空
//寻找孩子在父亲右的祖先
Node* cur = _node;
Node* parent = cur->_parent;
while (parent&&cur == parent->_left)
cur = parent;
parent = parent->_parent;
_node = parent;
return *this;
#pragma once
enum Colour
BLACK,
RED
;
template<class T>
struct RBTreeNode
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
Colour _col;
T _data;
RBTreeNode(const T& data)
:_left(nullptr),
:_right(nullptr),
:_parent(nullptr),
:col(RED),
:_data(data)
;
template<class T,class Ref,class Ptr>
struct RBTreeIterator
typedef RBTreeNode<T> Node;
typedef RBTreeIterator<T,Ref,Ptr> 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
return _node!=s._node;
bool operator==(const Self& s) const
return _node==s._node;
Self& operator++()
if(_node->_right)
//右子树中序第一个节点,也就是右子树的最左节点
Node* subLeft = _node->_right;
while(subLeft->_left)
subLeft = subLeft->_left;
_node = subLeft;
else
//右为空,当前子树已经访问完了,要去找祖先访问,沿着到根节点的路径往上走,
//找孩子是父亲左的那个父亲节点
Node* cur = _node;
Node* parent = cur->_parent;
while(parent && parent->_right == cur)
cur = parent;
parent = parent->_parent;
_node = parent;
return *this;
Self& operator--()
//跟++基本是反过来
return *this;
template<class K,class T,class KeyOfT>
class RBTree
typedef RBTreeNode<T> Node;
public:
typedef RBTreeIterator<T,T&,T*> iterator;
typedef RBTreeIterator<T,const T&,const T*> const_iterator;
iterator begin()
Node* left = _root;
while(left && left->_left)
left = left->_left;
return iterator(left);
iterator end()
return iterator(nullptr);
RBTree()
:_root(nullptr)
//查找函数
iterator Find(const K& key)
KeyOfT kot;
Node* cur = _root;
while (cur)
if (key < kot(cur->_data)) //key值小于该结点的值
cur = cur->_left; //在该结点的左子树当中查找
else if (key > kot(cur->_data)) //key值大于该结点的值
cur = cur->_right; //在该结点的右子树当中查找
else //找到了目标结点
return iterator(cur); //返回该结点
return end(); //查找失败
//插入
pair<iterator,bool> Insert手撕STLmap和set