1.set和map是什么
set和map是 C++ STL 提供的容器,用于高效的查找数据,底层采用红黑树实现,其中set是Key模型,map是Key-Value模型
set和map的基本使用较为简单,这里不再叙述,直接进入实现环节
2.set和map的底层实现
2.1 节点的定义
既然set和map的底层采用红黑树,那就少不了我们之前实现过的红黑树,直接搬过来复用
但我们实现的红黑树是Key-Value模型,难道要针对Key模型再copy一份红黑树,将节点里面的值修改成Key吗?
STL 中采用模板的方式,只需要一份红黑树,就能完成Key和Key-Value模型的统一
既然如何,红黑树节点的定义中,数据就不能只是key或pair对象,而应当是泛型,外部传递什么类型,数据就是什么类型
template<typename T>
struct RBTreeNode
{
RBTreeNode(const T& data)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_data(data)
,_col(RED)
{}
RBTreeNode<T>* _left;
RBTreeNode<T>* _right;
RBTreeNode<T>* _parent;
T _data;
Color _col;
};
// set.h
template<typename K>
class set
{
private:
RBTree<K, K> _tree;
};
// map.h
template<typename K, typename V>
class map
{
private:
RBTree<K, std::pair<K, V>> _tree;
};
2.2 set和map的设计
同时,插入数据时,插入的值也应当为修改,此时,数据的比较就有问题了,之前的比较都是针对单一的key或pair进行比较,但红黑树这层并不知道外部传递给我的是一个什么类型的数据,只有set/map层知道传递的数据类型是什么,该如何进行数据的比较?
这里的解决方法是使用仿函数,在set/map层传递一个仿函数给红黑树层,该仿函数的功能用于取出key值,如果是set,那就直接取出key值,如果是map,那就取出pair中的first
// set.h
template<typename K>
class set
{
private:
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
private:
RBTree<K, K, SetKeyOfT> _tree;
};
// map.h
template<typename K, typename V>
class map
{
private:
struct MapKeyOfT
{
const K& operator()(const std::pair<K, V>& kv)
{
return kv.first;
}
};
private:
RBTree<K, std::pair<K, V>, MapKeyOfT> _tree;
};
// RBTree.h
template<typename K, typename T, typename KeyOfT>
class RBTree
{
public:
bool insert(const T& data)
{
if (_root == nullptr)
{
_root = new Node(data);
_root->_col = BLACK;
return true;
}
KeyOfT kot;
Node* cur = _root, * parent = nullptr;
while (cur)
{
if (kot(data) < kot(cur->_data))
{
parent = cur;
cur = cur->_left;
}
else if (kot(data) > kot(cur->_data))
{
parent = cur;
cur = cur->_right;
}
else return false;
}
Node* newnode = new Node(data);
if (kot(data) < kot(parent->_data)) parent->_left = newnode;
else parent->_right = newnode;
newnode->_parent = parent;
// .......
return true;
}
private:
Node* _root = nullptr;
};
至此,set/map的整体框架建立完毕
set/map中,红黑树的定义时,还多传递了一个K类型,这样set有两个K类型,会不会很多余?
虽然插入数据不同的类型要采用不同的比较方式,但双方的查找操作都是根据key进行查找的,因此,多添加的K类型是为了方便map进行find操作的
2.3 迭代器
接下来,便是迭代器的设计了
在vector和list中,迭代器都是对指针的封装,再通过重载*和->运算符,达到屏蔽底层,上层统一遍历的效果
这里我们的设计思想也是一样的
template<typename T, typename Ref, typename Ptr>
struct __RBreeIterator
{
typedef RBTreeNode<T> Node;
typedef __RBreeIterator<T, Ref, Ptr> Self;
__RBreeIterator(Node* node)
:_node(node)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
Node* _node;
};
唯一不同的是对于红黑树,++操作该如何实现?
红黑树的遍历要采用中序遍历才有意义,左子树 根 右子树
遍历到到某一节点时,如果左子树不为空,那么要一直遍历,直到左子树为空
左子树遍历完了,接下来访问根
根访问完了,如果右子树不为空,下一个应该访问右子树的最左节点
如果右子树为空,表明当前子树已经访问完了,此时,如果父亲是祖父的右子树,表明父亲的那颗子树也访问完了,要一直向上返回,直到当前节点是其父亲的左子树
template<typename T, typename Ref, typename Ptr>
struct __RBreeIterator
{
typedef RBTreeNode<T> Node;
typedef __RBreeIterator<T, Ref, Ptr> Self;
// ...
Self& operator++()
{
if (_node->_right != nullptr)
{
_node = _node->_right;
while (_node->_left) _node = _node->_left;
}
else
{
Node* parent = _node->_parent;
while (parent && _node == parent->_right)
{
_node = parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
Node* _node;
};
set和map在 STL 中是双向迭代器,意味着能迭代器不仅支持++操作,也支持–操作,而–操作无非反过来遍历
Self& operator--()
{
if (_node->_left != nullptr)
{
_node = _node->_left;
while (_node->_right) _node = _node->_right;
}
else
{
Node* parent = _node->_parent;
while (parent && _node == parent->_left)
{
_node = parent;
parent = parent->_parent;
}
_node = parent;
}
return *this;
}
规定迭代器的begin为红黑树的最左节点,end为空
template<typename K, typename T, typename KeyOfT>
class RBTree
{
public:
typedef RBTreeNode<T> Node;
typedef __RBreeIterator<T, T&, T*> iterator;
iterator begin()
{
Node* left = _root;
while (left && left->_left) left = left->_left;
return iterator(left);
}
iterator end()
{
return iterator(nullptr);
}
// ...
}
而set/map,无论是插入操作,还是迭代器,无非是对红黑树的接口的封装罢了
// set.h
template<typename K>
class set
{
private:
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
typedef typename RBTree<K, const K, SetKeyOfT>::iterator iterator;
bool insert(const K& key)
{
return _tree.insert(key);
}
iterator begin()
{
return _tree.begin();
}
iterator end()
{
return _tree.end();
}
private:
RBTree<K, const K, SetKeyOfT> _tree;
};
// map.h
template<typename K, typename V>
class map
{
private:
struct MapKeyOfT
{
const K& operator()(const std::pair<K, V>& kv)
{
return kv.first;
}
};
public:
typedef typename RBTree<K, std::pair<const K, V>, MapKeyOfT>::iterator iterator;
bool insert(const std::pair<K, V>& kv)
{
return _tree.insert(kv);
}
iterator begin()
{
return _tree.begin();
}
iterator end()
{
return _tree.end();
}
private:
RBTree<K, std::pair<const K, V>, MapKeyOfT> _tree;
};
2.4 map中operator[]
STL 中map支持使用[]运算符进行value的修改,使用起来相当方便
实际上,其内部设计利用了insert操作,将insert的返回值修改为std::pair<iterator, bool>
使用[]时,会传递一个key值,希望拿到key对应value的引用,这样就能方便修改value
因此,可以在[]内部调用insert,如果key不存在则插入成功,返回新节点的迭代器 + true的pair对象,否则返回值中的pair的second为false,表示插入失败
template<typename K, typename V>
class map
{
// ...
public:
typedef typename RBTree<K, std::pair<const K, V>, MapKeyOfT>::iterator iterator;
V& operator[](const K& key)
{
std::pair<iterator, bool> ret = this->insert(std::make_pair(key, V()));
return ret.first->second;
}
// ...
};
2.5 拷贝构造 + 赋值重载
set和map都只有RBTree一个成员,直接调用红黑树的拷贝构造和赋值重载即可
template<typename K, typename T, typename KeyOfT>
class RBTree
{
public:
typedef RBTreeNode<T> Node;
typedef __RBreeIterator<T, T&, T*> iterator;
typedef __RBreeIterator<T, const T&, const T*> const_iterator;
~RBTree()
{
this->destroy(_root);
_root = nullptr;
}
RBTree() = default;
RBTree(const RBTree<K, T, KeyOfT>& t)
{
_root = this->create(t._root);
_root->_parent = nullptr;
}
RBTree<K, T, KeyOfT>& operator=(RBTree<K, T, KeyOfT> t)
{
std::swap(_root, t._root);
return *this;
}
// ...
private:
void destroy(Node* root)
{
if (root == nullptr) return;
destroy(root->_left);
destroy(root->_right);
delete root;
root = nullptr;
}
Node* create(Node* root)
{
if (root == nullptr) return nullptr;
Node* newnode = new Node(root->_data);
newnode->_col = root->_col;
newnode->_left = create(root->_left);
if (newnode->_left) newnode->_left->_parent = newnode;
newnode->_right = create(root->_right);
if (newnode->_right) newnode->_right->_parent = newnode;
return newnode;
}
private:
Node* _root = nullptr;
}
剩下的还有const迭代器和find的实现了,由于较为简单,不再详讲
**注意:**还有个细节,我们在传递红黑树的模板参数时,将第二个模板参数中的K加上了const
如果是set,那就是const K,如果是map,那就是pair<const K, V>,这是为了防止使用迭代器修改节点中的值,为什么不直接使用const迭代器呢?如果使用const迭代器,那么map中的Key和Value都不能修改,而map是要能支持修改Value的
2.6 测试
void Test_set()
{
set<int> s;
std::vector<int> v = { 1, 3, 2, 8, 11, 9, 4, 5, 7, 10, 6 };
for (auto& e : v) s.insert(e);
std::cout << "正向遍历: ";
for (auto& e : s) std::cout << e << " ";
std::cout << std::endl;
std::cout << "反向遍历: ";
set<int>::iterator it = s.find(11);
while (it != s.end())
{
std::cout << *it << " ";
--it;
}
std::cout << std::endl;
std::cout << "拷贝构造: s2->";
set<int> s2(s);
for (auto& e : s2) std::cout << e << " ";
std::cout << std::endl;
std::cout << "赋值重载: ";
set<int> s3(s);
s3.insert(1);
s3 = s;
std::cout << "s3->";
for (auto& e : s3) std::cout << e << " ";
std::cout << " s->";
for (auto& e : s) std::cout << e << " ";
}
void Test_map()
{
std::vector<std::string> v = { "apple", "tassel", "watermelon", "apple", "banana", "tassel", "orange", "apple" };
map<std::string, int> m;
for (auto& e : v) m[e] ++;
for (auto& it : m)
{
std::cout << it.first << " " << it.second << std::endl;
}
}