C++ STL 容器设计实现 -- map/set
6、map/multimap/set/multiset
6.1、rb_tree
map 和 multimap 以及 set 和 multiset 底层数据结构都是红黑树,如果对红黑树数据结构感兴趣,可以参考关于红黑树的分享
这里简单介绍一下 map 和 multimap 底层红黑树的布局。rb_tree 有一个 rb_tree_impl 类型的数据成员,rb_tree_impl 实现红黑树,rb_tree 是对 rb_tree_impl 的封装。
header 是实现的技巧,header.parent 保存根结点 root,header.left 保存最左元素,是中序遍历的首元素,header.right 保存最右元素,是中序遍历的最末元素,root->parent 指向的是 header。
向 rbtree 插入元素时,需要确定插入的位置,保持二叉搜索树的性质。rbtree 提供两个接口:_M_get_insert_unique_pos() 和 _M_get_insert_equal_pos() 函数。前者适用于插入后所有元素必须唯一,否则插入失败;后置没有这个限制,是普通插入情况。
_M_get_insert_equal_pos() 函数定义如下,比较直观。在 rbtree 从根节点开始搜索,如果当前结点大于 k,在其左子树继续搜索,否则在其右子树搜索,直到 x 为空,k 插入 y 的左子树或者右子树。
/// stl_tree.h
template<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
pair<typename _Rb_tree<_Key, _Val, _KeyOfValue,
_Compare, _Alloc>::_Base_ptr,
typename _Rb_tree<_Key, _Val, _KeyOfValue,
_Compare, _Alloc>::_Base_ptr>
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
_M_get_insert_equal_pos(const key_type& __k)
{
typedef pair<_Base_ptr, _Base_ptr> _Res;
_Link_type __x = _M_begin();
_Base_ptr __y = _M_end();
while (__x != 0)
{
__y = __x;
__x = _M_impl._M_key_compare(__k, _S_key(__x)) ?
_S_left(__x) : _S_right(__x);
} // x == null,退出循环
return _Res(__x, __y);
}
_M_get_insert_unique_pos() 函数就复杂一点,因为要确保插入 k 之后,rbtree 没有重复的节点。首先和 _M_get_insert_equal_pos() 函数相同,也是找到执行插入的结点 y。然后检查,rbtree 中是否已经存在某个节点,其值和 k 相同。
- 如果 k 插入到 y 的左子树,k < y 已经符合,还需要核查 k 是否大于 y 中序遍历前一个结点的值;
- 如果 k 插入到 y 的右子树,需要确认 y < k;
/// stl_tree.h
template<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
pair<typename _Rb_tree<_Key, _Val, _KeyOfValue,
_Compare, _Alloc>::_Base_ptr,
typename _Rb_tree<_Key, _Val, _KeyOfValue,
_Compare, _Alloc>::_Base_ptr>
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
_M_get_insert_unique_pos(const key_type& __k)
{
typedef pair<_Base_ptr, _Base_ptr> _Res;
_Link_type __x = _M_begin();
_Base_ptr __y = _M_end();
bool __comp = true;
while (__x != 0)
{
__y = __x;
__comp = _M_impl._M_key_compare(__k, _S_key(__x)); // k < x/y --> true
__x = __comp ? _S_left(__x) : _S_right(__x);
}
iterator __j = iterator(__y);
if (__comp) // k < y,插入后 y->left = x
{
if (__j == begin())
return _Res(__x, __y);
else
--__j; // 找到 y 中序遍历的前节点
}
if (_M_impl._M_key_compare(_S_key(__j._M_node), __k)) // j < k
return _Res(__x, __y);
return _Res(__j._M_node, 0);
}
为了方便处理插入,rbtree 定义了 _Audo_node 类,使用 RAII,管理 node 的申请于释放。
/// stl_tree.h
// An RAII _Node handle
struct _Auto_node
{
template<typename... _Args>
_Auto_node(_Rb_tree& __t, _Args&&... __args)
: _M_t(__t),
_M_node(__t._M_create_node(std::forward<_Args>(__args)...))
{ }
~_Auto_node()
{
if (_M_node)
_M_t._M_drop_node(_M_node);
}
_Auto_node(_Auto_node&& __n)
: _M_t(__n._M_t), _M_node(__n._M_node)
{ __n._M_node = nullptr; }
const _Key&
_M_key() const
{ return _S_key(_M_node); }
iterator
_M_insert(pair<_Base_ptr, _Base_ptr> __p)
{
auto __it = _M_t._M_insert_node(__p.first, __p.second, _M_node);
_M_node = nullptr;
return __it;
}
iterator
_M_insert_equal_lower()
{
auto __it = _M_t._M_insert_equal_lower_node(_M_node);
_M_node = nullptr;
return __it;
}
_Rb_tree& _M_t;
_Link_type _M_node;
};
6.2、map
map 就是对 rbtree 的封装,只有一个 rbtree 类型的数据成员。rbtree 节点数据域 vaule_type 是 std::pair 类型,first 和 second 分别存放 key 和 value。
/// stl_map.h
template <typename _Key, typename _Tp, typename _Compare = std::less<_Key>,
typename _Alloc = std::allocator<std::pair<const _Key, _Tp> > >
class map
{
public:
typedef _Key key_type;
typedef _Tp mapped_type;
typedef std::pair<const _Key, _Tp> value_type;
typedef _Compare key_compare;
typedef _Alloc allocator_type;
public:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
class value_compare
: public std::binary_function<value_type, value_type, bool>
{
friend class map<_Key, _Tp, _Compare, _Alloc>;
protected:
_Compare comp;
value_compare(_Compare __c)
: comp(__c) { }
public:
bool operator()(const value_type& __x, const value_type& __y) const
{ return comp(__x.first, __y.first); }
};
#pragma GCC diagnostic pop
private:
/// This turns a red-black tree into a [multi]map.
typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
rebind<value_type>::other _Pair_alloc_type;
typedef _Rb_tree<key_type, value_type, _Select1st<value_type>,
key_compare, _Pair_alloc_type> _Rep_type;
/// The actual tree structure.
_Rep_type _M_t; // 红黑树
typedef __gnu_cxx::__alloc_traits<_Pair_alloc_type> _Alloc_traits;
#if __cplusplus >= 201703L
template<typename _Up, typename _Vp = remove_reference_t<_Up>>
static constexpr bool __usable_key
= __or_v<is_same<const _Vp, const _Key>,
__and_<is_scalar<_Vp>, is_scalar<_Key>>>;
#endif
public:
// many of these are specified differently in ISO, but the following are
// "functionally equivalent"
typedef typename _Alloc_traits::pointer pointer;
typedef typename _Alloc_traits::const_pointer const_pointer;
typedef typename _Alloc_traits::reference reference;
typedef typename _Alloc_traits::const_reference const_reference;
typedef typename _Rep_type::iterator iterator;
typedef typename _Rep_type::const_iterator const_iterator;
typedef typename _Rep_type::size_type size_type;
typedef typename _Rep_type::difference_type difference_type;
typedef typename _Rep_type::reverse_iterator reverse_iterator;
typedef typename _Rep_type::const_reverse_iterator const_reverse_iterator;
#if __cplusplus > 201402L
using node_type = typename _Rep_type::node_type;
using insert_return_type = typename _Rep_type::insert_return_type;
#endif
/* ... */
};
6.2.1、operator[]
operator[] 用于访问 map 元素,如果元素不存在,则执行插入操作。
/// stl_map.h
mapped_type&
operator[](const key_type& __k)
{
// concept requirements
__glibcxx_function_requires(_DefaultConstructibleConcept<mapped_type>)
iterator __i = lower_bound(__k);
// __i->first is greater than or equivalent to __k.
if (__i == end() || key_comp()(__k, (*__i).first))
#if __cplusplus >= 201103L
__i = _M_t._M_emplace_hint_unique(__i, std::piecewise_construct,
std::tuple<const key_type&>(__k),
std::tuple<>());
#else
__i = insert(__i, value_type(__k, mapped_type()));
#endif
return (*__i).second;
}
_M_emplace_hint_unique() 函数首先调用 _M_get_insert_hint_unique_pos() 查找插入位置,如果可以插入,则执行插入操作。
/// stl_tree.h
template<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
template<typename... _Args>
auto
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
_M_emplace_hint_unique(const_iterator __pos, _Args&&... __args)
-> iterator
{
_Auto_node __z(*this, std::forward<_Args>(__args)...);
auto __res = _M_get_insert_hint_unique_pos(__pos, __z._M_key());
if (__res.second)
return __z._M_insert(__res);
return iterator(__res.first);
}
_M_get_insert_hint_unique_pos() 函数会先处理 easy-case
- 可以插入在最右节点右子树,插入
- 可以插入在最左节点左子树,插入
- k < pos && --pos < k,插入
- pos < k && ++pos > k,插入
- 其他情况,调用 _M_get_insert_unique_pos() 函数搜索插入位置。
上面 -- 表示中序遍历前一个节点,++ 表示中序遍历后一个节点。
/// stl_tree.h
template<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
pair<typename _Rb_tree<_Key, _Val, _KeyOfValue,
_Compare, _Alloc>::_Base_ptr,
typename _Rb_tree<_Key, _Val, _KeyOfValue,
_Compare, _Alloc>::_Base_ptr>
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
_M_get_insert_hint_unique_pos(const_iterator __position,
const key_type& __k)
{
iterator __pos = __position._M_const_cast();
typedef pair<_Base_ptr, _Base_ptr> _Res;
// end()
if (__pos._M_node == _M_end())
{
if (size() > 0
&& _M_impl._M_key_compare(_S_key(_M_rightmost()), __k))
return _Res(0, _M_rightmost()); // 在最右节点右子树插入
else
return _M_get_insert_unique_pos(__k); // 检索
}
else if (_M_impl._M_key_compare(__k, _S_key(__pos._M_node)))
{
// First, try before...
iterator __before = __pos;
if (__pos._M_node == _M_leftmost()) // begin()
return _Res(_M_leftmost(), _M_leftmost()); // 在最左节点左子树插入
else if (_M_impl._M_key_compare(_S_key((--__before)._M_node), __k))
{ // k < pos && --pos < k
if (_S_right(__before._M_node) == 0)
return _Res(0, __before._M_node);
else
return _Res(__pos._M_node, __pos._M_node);
}
else
return _M_get_insert_unique_pos(__k);
}
else if (_M_impl._M_key_compare(_S_key(__pos._M_node), __k))
{
// ... then try after.
iterator __after = __pos;
if (__pos._M_node == _M_rightmost())
return _Res(0, _M_rightmost()); // 在最右节点右子树插入
else if (_M_impl._M_key_compare(__k, _S_key((++__after)._M_node)))
{ // pos < k && ++pos > k
if (_S_right(__pos._M_node) == 0)
return _Res(0, __pos._M_node);
else
return _Res(__after._M_node, __after._M_node);
}
else
return _M_get_insert_unique_pos(__k);
}
else
// Equivalent keys.
return _Res(__pos._M_node, 0);
}
6.2.2、insert()
insert() 函数返回一个 std::pair 遍历,first 是一个迭代器,second 是一个 bool 遍历,表示插入是否成功。如果成功,first 指向插入的元素。
/// stl_map.h
std::pair<iterator, bool>
insert(value_type&& __x)
{ return _M_t._M_insert_unique(std::move(__x)); }
_M_insert_unique() 函数逻辑比较简单,先调用 _M_get_insert_unique_pos() 函数搜索插入位置,如果可以插入,就执行插入函数。
/// stl_tree.h
template<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
#if __cplusplus >= 201103L
template<typename _Arg>
#endif
pair<typename _Rb_tree<_Key, _Val, _KeyOfValue,
_Compare, _Alloc>::iterator, bool>
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
#if __cplusplus >= 201103L
_M_insert_unique(_Arg&& __v)
#else
/* ... */
#endif
{
typedef pair<iterator, bool> _Res;
pair<_Base_ptr, _Base_ptr> __res
= _M_get_insert_unique_pos(_KeyOfValue()(__v));
if (__res.second)
{
_Alloc_node __an(*this);
return _Res(_M_insert_(__res.first, __res.second,
_GLIBCXX_FORWARD(_Arg, __v), __an),
true);
}
return _Res(iterator(__res.first), false); // 插入失败
}
6.3、multimap
multimap 和 map 定义相同,知识处理插入操作时,不检查插入后是否有相同的元素。
/// stl_multimap.h
template <typename _Key, typename _Tp,
typename _Compare = std::less<_Key>,
typename _Alloc = std::allocator<std::pair<const _Key, _Tp> > >
class multimap
{
public:
typedef _Key key_type;
typedef _Tp mapped_type;
typedef std::pair<const _Key, _Tp> value_type;
typedef _Compare key_compare;
typedef _Alloc allocator_type;
private:
#ifdef _GLIBCXX_CONCEPT_CHECKS
// concept requirements
typedef typename _Alloc::value_type _Alloc_value_type;
# if __cplusplus < 201103L
__glibcxx_class_requires(_Tp, _SGIAssignableConcept)
# endif
__glibcxx_class_requires4(_Compare, bool, _Key, _Key,
_BinaryFunctionConcept)
__glibcxx_class_requires2(value_type, _Alloc_value_type, _SameTypeConcept)
#endif
#if __cplusplus >= 201103L
#if __cplusplus > 201703L || defined __STRICT_ANSI__
static_assert(is_same<typename _Alloc::value_type, value_type>::value,
"std::multimap must have the same value_type as its allocator");
#endif
#endif
public:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
class value_compare
: public std::binary_function<value_type, value_type, bool>
{
friend class multimap<_Key, _Tp, _Compare, _Alloc>;
protected:
_Compare comp;
value_compare(_Compare __c)
: comp(__c) { }
public:
bool operator()(const value_type& __x, const value_type& __y) const
{ return comp(__x.first, __y.first); }
};
#pragma GCC diagnostic pop
private:
/// This turns a red-black tree into a [multi]map.
typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
rebind<value_type>::other _Pair_alloc_type;
typedef _Rb_tree<key_type, value_type, _Select1st<value_type>,
key_compare, _Pair_alloc_type> _Rep_type;
/// The actual tree structure.
_Rep_type _M_t;
typedef __gnu_cxx::__alloc_traits<_Pair_alloc_type> _Alloc_traits;
public:
// many of these are specified differently in ISO, but the following are
// "functionally equivalent"
typedef typename _Alloc_traits::pointer pointer;
typedef typename _Alloc_traits::const_pointer const_pointer;
typedef typename _Alloc_traits::reference reference;
typedef typename _Alloc_traits::const_reference const_reference;
typedef typename _Rep_type::iterator iterator;
typedef typename _Rep_type::const_iterator const_iterator;
typedef typename _Rep_type::size_type size_type;
typedef typename _Rep_type::difference_type difference_type;
typedef typename _Rep_type::reverse_iterator reverse_iterator;
typedef typename _Rep_type::const_reverse_iterator const_reverse_iterator;
#if __cplusplus > 201402L
using node_type = typename _Rep_type::node_type;
#endif
/* ... */
};
6.3.1、insert
insert() 函数返回一个迭代器,指向插入的元素。
/// stl_multimap.h
iterator
insert(const value_type& __x)
{ return _M_t._M_insert_equal(__x); }
iterator
insert(value_type&& __x)
{ return _M_t._M_insert_equal(std::move(__x)); }
_M_insert_equal() 函数首先调用 _M_get_insert_equal_pos() 函数检索插入位置,然后执行插入。
/// stl_tree.h
template<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
#if __cplusplus >= 201103L
template<typename _Arg>
#endif
typename _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::iterator
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
#if __cplusplus >= 201103L
_M_insert_equal(_Arg&& __v)
#else
/* ... */
#endif
{
pair<_Base_ptr, _Base_ptr> __res
= _M_get_insert_equal_pos(_KeyOfValue()(__v));
_Alloc_node __an(*this);
return _M_insert_(__res.first, __res.second,
_GLIBCXX_FORWARD(_Arg, __v), __an);
}
6.3.2、lower_bound()
lower_bound() 函数返回一个迭代器,迭代器指向第一个不小于 x 的元素。
/// stl_multimap.h
iterator
lower_bound(const key_type& __x)
{ return _M_t.lower_bound(__x); }
rbtree 红黑树 lower_bound() 函数实现比较简单,当 k <= x 时,在左子树查找,否则在右子树查找。与 k 相等的元素,就在 y 的右边,也就是中序遍历在 y 的后续节点。
/// stl_tree.h
iterator
lower_bound(const key_type& __k)
{ return _M_lower_bound(_M_begin(), _M_end(), __k); }
template<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
typename _Rb_tree<_Key, _Val, _KeyOfValue,
_Compare, _Alloc>::iterator
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
_M_lower_bound(_Link_type __x, _Base_ptr __y,
const _Key& __k)
{
while (__x != 0)
if (!_M_impl._M_key_compare(_S_key(__x), __k)) // k <= x
__y = __x, __x = _S_left(__x);
else
__x = _S_right(__x); // x < k
return iterator(__y);
}
6.3.3、upper_bound()
upper_bound() 函数返回一个迭代器,迭代器指向第一个大于 x 的元素。
/// stl_multimap.h
iterator
upper_bound(const key_type& __x)
{ return _M_t.upper_bound(__x); }
_M_upper_bound() 函数当 k < x 时,在左子树查找,否则在右子树查找。这样子,与 k 相等元素,在 y 的左边,中序遍历时,是 y 的前序节点。
/// stl_tree.h
iterator
upper_bound(const key_type& __k)
{ return _M_upper_bound(_M_begin(), _M_end(), __k); }
template<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
typename _Rb_tree<_Key, _Val, _KeyOfValue,
_Compare, _Alloc>::iterator
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
_M_upper_bound(_Link_type __x, _Base_ptr __y,
const _Key& __k)
{
while (__x != 0)
if (_M_impl._M_key_compare(__k, _S_key(__x)))
__y = __x, __x = _S_left(__x);
else
__x = _S_right(__x);
return iterator(__y);
}
6.4、set
set 和 map 的定义基本相同,唯一的不同是 rbtree 的元素类型是 value_type。除此之外,其他定义相同,没有特殊之处。
/// stl_set.h
template<typename _Key, typename _Compare = std::less<_Key>,
typename _Alloc = std::allocator<_Key> >
class set
{
public:
// typedefs:
///@{
/// Public typedefs.
typedef _Key key_type;
typedef _Key value_type;
typedef _Compare key_compare;
typedef _Compare value_compare;
typedef _Alloc allocator_type;
///@}
private:
typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
rebind<_Key>::other _Key_alloc_type;
typedef _Rb_tree<key_type, value_type, _Identity<value_type>,
key_compare, _Key_alloc_type> _Rep_type;
_Rep_type _M_t; // Red-black tree representing set.
typedef __gnu_cxx::__alloc_traits<_Key_alloc_type> _Alloc_traits;
public:
///@{
/// Iterator-related typedefs.
typedef typename _Alloc_traits::pointer pointer;
typedef typename _Alloc_traits::const_pointer const_pointer;
typedef typename _Alloc_traits::reference reference;
typedef typename _Alloc_traits::const_reference const_reference;
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// DR 103. set::iterator is required to be modifiable,
// but this allows modification of keys.
typedef typename _Rep_type::const_iterator iterator;
typedef typename _Rep_type::const_iterator const_iterator;
typedef typename _Rep_type::const_reverse_iterator reverse_iterator;
typedef typename _Rep_type::const_reverse_iterator const_reverse_iterator;
typedef typename _Rep_type::size_type size_type;
typedef typename _Rep_type::difference_type difference_type;
///@}
#if __cplusplus > 201402L
using node_type = typename _Rep_type::node_type;
using insert_return_type = typename _Rep_type::insert_return_type;
#endif
/* ... */
};
insert() 函数也和 map::insert 定义相同
/// stl_set.h
std::pair<iterator, bool>
insert(const value_type& __x)
{
std::pair<typename _Rep_type::iterator, bool> __p =
_M_t._M_insert_unique(__x);
return std::pair<iterator, bool>(__p.first, __p.second);
}
6.5、multiset
multiset 也和 multimap 定义基本相同,只是 rbtree 节点元素是 value_type。
/// stl_multiset.h
template <typename _Key, typename _Compare = std::less<_Key>,
typename _Alloc = std::allocator<_Key> >
class multiset
{
public:
// typedefs:
typedef _Key key_type;
typedef _Key value_type;
typedef _Compare key_compare;
typedef _Compare value_compare;
typedef _Alloc allocator_type;
private:
/// This turns a red-black tree into a [multi]set.
typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
rebind<_Key>::other _Key_alloc_type;
typedef _Rb_tree<key_type, value_type, _Identity<value_type>,
key_compare, _Key_alloc_type> _Rep_type;
/// The actual tree structure.
_Rep_type _M_t;
typedef __gnu_cxx::__alloc_traits<_Key_alloc_type> _Alloc_traits;
public:
typedef typename _Alloc_traits::pointer pointer;
typedef typename _Alloc_traits::const_pointer const_pointer;
typedef typename _Alloc_traits::reference reference;
typedef typename _Alloc_traits::const_reference const_reference;
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// DR 103. set::iterator is required to be modifiable,
// but this allows modification of keys.
typedef typename _Rep_type::const_iterator iterator;
typedef typename _Rep_type::const_iterator const_iterator;
typedef typename _Rep_type::const_reverse_iterator reverse_iterator;
typedef typename _Rep_type::const_reverse_iterator const_reverse_iterator;
typedef typename _Rep_type::size_type size_type;
typedef typename _Rep_type::difference_type difference_type;
#if __cplusplus > 201402L
using node_type = typename _Rep_type::node_type;
#endif
/* ... */
};
insert()/lower_bound()/upper_bound() 函数也和 multimap 对应的相同。
/// stl_multiset.h
iterator
insert(const value_type& __x)
{ return _M_t._M_insert_equal(__x); }
iterator
lower_bound(const key_type& __x)
{ return _M_t.lower_bound(__x); }
iterator
upper_bound(const key_type& __x)
{ return _M_t.upper_bound(__x); }
#晒一晒我的offer##实习,投递多份简历没人回复怎么办##c++#C/C++ 语言基础
查看10道真题和解析