关于STL的时间复杂度分析

摘要

本文旨在对C++ STL库(Standard Template Library)中各类容器的时间复杂度进行详细分析与总结。通过深入研究STL中常用容器如vector、list、deque、set、map等在不同操作下的时间复杂度表现,揭示其在实际应用中的性能特点。同时,对比不同容器在相同操作下的时间复杂度差异,为开发者在选择合适的数据结构时提供理论依据。此外,还探讨了STL的底层实现逻辑和使用注意事项,以帮助开发者更高效地运用STL进行程序设计与开发。

Abstract

This paper aims to provide a detailed analysis and summary of the time complexities of various containers in the C++ Standard Template Library (STL). By thoroughly examining the time complexity performance of commonly used STL containers such as vector, list, deque, set, and map under different operations, we reveal their performance characteristics in practical applications. Additionally, by comparing the time complexities of different containers under identical operations, this study offers a theoretical basis for developers when selecting appropriate data structures. Furthermore, the underlying implementation logic of the STL and important usage considerations are discussed to assist developers in utilizing the STL more efficiently for program design and development.

0.结论速查

0.1 容器

容器 操作 复杂度
map 查询 \(\Omega(1)\sim O(\log n)\)
修改 \(\Omega(1)\sim O(\log n)\)
插入 \(\Omega(1)\sim O(\log n)\)
unordered_map 查询 \(\Omega(1)\sim O(n)\)
修改 \(\Omega(1)\sim O(n)\)
插入 \(\Omega(1)\sim O(n)\)

1.map

map的头文件位于include/c++/map中,经查看,发现主要引入头文件bits/stl_map.hbits/stl_tree.h

其中,stl_map.h主要实现了map类,而stl_tree.h主要实现了红黑树类_Rb_tree

1.1 查询与修改操作

代码摘自头文件:

/**
*  @brief  Subscript ( @c [] ) access to %map data.
*  @param  __k  The key for which data should be retrieved.
*  @return  A reference to the data of the (key,data) %pair.
*
*  Allows for easy lookup with the subscript ( @c [] )
*  operator.  Returns data associated with the key specified in
*  subscript.  If the key does not exist, a pair with that key
*  is created using default values, which is then returned.
*
*  Lookup requires logarithmic time.
*/
mapped_type& operator[](const key_type& __k)
{

	iterator __i = lower_bound(__k);
	// __i->first is greater than or equivalent to __k.
	if (__i == end() || key_comp()(__k, (*__i).first))
	  	__i = _M_t._M_emplace_hint_unique(__i, std::piecewise_construct,std::tuple<const key_type&>(__k),std::tuple<>());
	return (*__i).second;
}

mapped_type& operator[](key_type&& __k)
{

	iterator __i = lower_bound(__k);
	// __i->first is greater than or equivalent to __k.
	if (__i == end() || key_comp()(__k, (*__i).first))
	  	__i = _M_t._M_emplace_hint_unique(__i, std::piecewise_construct,std::forward_as_tuple(std::move(__k)),std::tuple<>());
	return (*__i).second;
}

其中,mapped_type代表这个map中值的类型。

可以清楚地看到,operator[]函数的两次重载均主要使用函数lower_bound,此处的lower_bound的参数定义是key_type __x而不是我们熟知的ForwardIterator first, ForwardIterator last,const T& val。显然它是这个头文件中定义的:

/**
*  @brief Finds the beginning of a subsequence matching given key.
*  @param  __x  Key of (key, value) pair to be located.
*  @return  Iterator pointing to first element equal to or greater
*           than key, or end().
*
*  This function returns the first element of a subsequence of elements
*  that matches the given key.  If unsuccessful it returns an iterator
*  pointing to the first element that has a greater value than given key
*  or end() if no such element exists.
*/
iterator lower_bound(const key_type& __x)
{ return _M_t.lower_bound(__x); }

_M_t的定义是:

_Rep_type _M_t;

_Rep_type的定义是:

typedef _Rb_tree<key_type, value_type, _Select1st<value_type>,key_compare, _Pair_alloc_type> _Rep_type;

到这里我们证明了map的底层是红黑树。

再来追根溯源,在stl_tree.h中寻找_Rb_tree::lower_bound。定义如下:

iterator lower_bound(const key_type& __k)
{ return _M_lower_bound(_M_begin(), _M_end(), __k); }

const_iterator lower_bound(const key_type& __k) const
{ return _M_lower_bound(_M_begin(), _M_end(), __k); }

_M_lower_bound的实现是:

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))
  			__y = __x, __x = _S_left(__x);
		else
  			__x = _S_right(__x);
  	return iterator(__y);
}

看得出来,与平常的lower_bound函数所差不多,主要还是二分操作。且红黑树为平衡二叉树,所以时间复杂度为 \(\Omega(1)\sim O(\log n)\)

\(\mathscr{Q.E.D.}\)

1.2 插入操作

我并没有找到map[key]=value的实现,所以直接跳跃到插入操作map.insert,定义如下:

/**
*  @brief Attempts to insert a std::pair into the %map.
*  @param __x Pair to be inserted (see std::make_pair for easy
*	     creation of pairs).
*
*  @return  A pair, of which the first element is an iterator that
*           points to the possibly inserted pair, and the second is
*           a bool that is true if the pair was actually inserted.
*
*  This function attempts to insert a (key, value) %pair into the %map.
*  A %map relies on unique keys and thus a %pair is only inserted if its
*  first element (the key) is not already present in the %map.
*
*  Insertion requires logarithmic time.
*  @{
*/
std::pair<iterator, bool> insert(const value_type& __x)
{ return _M_t._M_insert_unique(__x); }

std::pair<iterator, bool> insert(value_type&& __x)
{ return _M_t._M_insert_unique(std::move(__x)); }
template<typename _Pair>
__enable_if_t<is_constructible<value_type, _Pair>::value,pair<iterator, bool>> insert(_Pair&& __x)
{ return _M_t._M_emplace_unique(std::forward<_Pair>(__x)); }

可见,重载的三个函数均调用_M_t._M_insert_unique来完成插入工作。定义和实现在stl_tree.h中,实现如下:

template<typename _Arg>
iterator _M_insert_unique_(const_iterator __pos, _Arg&& __x)
{
	_Alloc_node __an(*this);
	return _M_insert_unique_(__pos, std::forward<_Arg>(__x), __an);
}

可以看到,它调用了函数_M_insert_unique_,继续溯源。这个函数的实现是:

typename _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::iterator 
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_insert_unique_(const_iterator __position, _Arg&& __v, _NodeGen& __node_gen)
{
    pair<_Base_ptr, _Base_ptr> __res = _M_get_insert_hint_unique_pos(__position, _KeyOfValue()(__v));
    if (__res.second)
		return _M_insert_(__res.first, __res.second,_GLIBCXX_FORWARD(_Arg, __v),__node_gen);
    return iterator(__res.first);
}

继续查看函数_M_insert_的代码:

typename _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::iterator
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_insert_(_Base_ptr __x, _Base_ptr __p,_Arg&& __v,_NodeGen& __node_gen)
{
	bool __insert_left = (__x != 0 || __p == _M_end()|| _M_impl._M_key_compare(_KeyOfValue()(__v),_S_key(__p)));

	_Link_type __z = __node_gen(_GLIBCXX_FORWARD(_Arg, __v));

	_Rb_tree_insert_and_rebalance(__insert_left, __z, __p,this->_M_impl._M_header);
	++_M_impl._M_node_count;
	return iterator(__z);
}

这个函数主要是调用_Rb_tree_insert_and_rebalance函数,所以我们看看它的实现。

这个不能在头文件找到,它是编译好的.a文件,当然你会逆向也能找到,需要下载gcc的源代码,源代码中libstdc++-v3/src/c++98/tree.cc就是实现。实现如下:

void _Rb_tree_insert_and_rebalance(const bool          __insert_left, _Rb_tree_node_base* __x, _Rb_tree_node_base* __p, _Rb_tree_node_base& __header) throw ()
{
    _Rb_tree_node_base *& __root = __header._M_parent;

    // Initialize fields in new node to insert.
    __x->_M_parent = __p;
    __x->_M_left = 0;
    __x->_M_right = 0;
    __x->_M_color = _S_red;

    // Insert.
    // Make new node child of parent and maintain root, leftmost and
    // rightmost nodes.
    // N.B. First node is always inserted left.
    if (__insert_left)
    {
        __p->_M_left = __x; // also makes leftmost = __x when __p == &__header

        if (__p == &__header)
        {
            __header._M_parent = __x;
            __header._M_right = __x;
        }
        else if (__p == __header._M_left)
          __header._M_left = __x; // maintain leftmost pointing to min node
    }
    else
    {
        __p->_M_right = __x;

        if (__p == __header._M_right)
          	__header._M_right = __x; // maintain rightmost pointing to max node
    }
    // Rebalance.
    while (__x != __root && __x->_M_parent->_M_color == _S_red)
    {
		_Rb_tree_node_base* const __xpp = __x->_M_parent->_M_parent;

		if (__x->_M_parent == __xpp->_M_left)
	  	{
	    	_Rb_tree_node_base* const __y = __xpp->_M_right;
	    	if (__y && __y->_M_color == _S_red)
	      	{
				__x->_M_parent->_M_color = _S_black;
				__y->_M_color = _S_black;
				__xpp->_M_color = _S_red;
				__x = __xpp;
	      	}
	    	else
	    	{
				if (__x == __x->_M_parent->_M_right)
			  	{
			    	__x = __x->_M_parent;
			    	local_Rb_tree_rotate_left(__x, __root);
			  	}
				__x->_M_parent->_M_color = _S_black;
				__xpp->_M_color = _S_red;
				local_Rb_tree_rotate_right(__xpp, __root);
	    	}
	 	}
		else
		{
		    _Rb_tree_node_base* const __y = __xpp->_M_left;
		    if (__y && __y->_M_color == _S_red)
		    {
				__x->_M_parent->_M_color = _S_black;
				__y->_M_color = _S_black;
				__xpp->_M_color = _S_red;
				__x = __xpp;
		    }
		    else
		    {
				if (__x == __x->_M_parent->_M_left)
				  {
				    __x = __x->_M_parent;
				    local_Rb_tree_rotate_right(__x, __root);
				  }
				__x->_M_parent->_M_color = _S_black;
				__xpp->_M_color = _S_red;
				local_Rb_tree_rotate_left(__xpp, __root);
		    }
		}
    }
    __root->_M_color = _S_black;
}

就是很正常的红黑树,时间复杂度为 \(\Omega(1)\sim O(\log n)\)。这里不做具体证明,具体证明见 https://wwsi.lanzouq.com/iK7Sx2lv4rpg

\(\mathscr{Q.E.D.}\)

1.∞ 参考&参见

2.unordered_map

头文件位于include/c++/bits/unordered_map.h中,与map除了名字没有一点关系

2.1 查询与修改操作

函数如下:

/**
*  @brief  Subscript ( @c [] ) access to %unordered_map data.
*  @param  __k  The key for which data should be retrieved.
*  @return  A reference to the data of the (key,data) %pair.
*
*  Allows for easy lookup with the subscript ( @c [] )operator.  Returns
*  data associated with the key specified in subscript.  If the key does
*  not exist, a pair with that key is created using default values, which
*  is then returned.
*
*  Lookup requires constant time.
*/
mapped_type& operator[](const key_type& __k)
{ return _M_h[__k]; }

mapped_type& operator[](key_type&& __k)
{ return _M_h[std::move(__k)]; }

可见这个功能是靠_M_h的相同功能实现的。寻找这个东西的定义:

_Hashtable _M_h;

到这里我们证明了unordered_map的底层是哈希表。

_Hashtable的定义在文件include/c++/bits/hashtable.h中。其关于查询代码如下:

template<typename _Key, typename _Value,typename _Alloc, typename _ExtractKey, typename _Equal, typename _H1, typename _H2, typename _Hash, typename _RehashPolicy,typename _Traits>
auto _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,_H1, _H2, _Hash, _RehashPolicy, _Traits>::find(const key_type& __k)-> iterator
{
	__hash_code __code = this->_M_hash_code(__k);
	std::size_t __n = _M_bucket_index(__k, __code);
	__node_type* __p = _M_find_node(__n, __k, __code);
	return __p ? iterator(__p) : end();
}

我们逐行分析。

首先_M_bucket_index函数经过二次溯源后其在include/c++/bits/hashtable_policy.h中有如下定义:

std::size_t _M_bucket_index(const _Key& __k, __hash_code, std::size_t __n) const
{ return _M_ranged_hash()(__k, __n); }

std::size_t _M_bucket_index(const __node_type* __p, std::size_t __n) const
noexcept( noexcept(declval<const _Hash&>()(declval<const _Key&>(),(std::size_t)0)) )
{ return _M_ranged_hash()(_M_extract()(__p->_M_v()), __n); }

其中,_M_ranged_hash的定义为:

const _Hash& _M_ranged_hash() const { return __ebo_hash::_S_cget(*this); }

_Hash& _M_ranged_hash() { return __ebo_hash::_S_get(*this); }

_S_cget_S_get的定义为:

static const _Tp& _S_cget(const _Hashtable_ebo_helper& __eboh)
{ return static_cast<const _Tp&>(__eboh); }

static _Tp& _S_get(_Hashtable_ebo_helper& __eboh)
{ return static_cast<_Tp&>(__eboh); }

这只是一个类型转换,所以时间复杂度为\(O(1)\)

继续来看下一个函数_M_find_node,它的定义为:

__node_type* _M_find_node(size_type __bkt, const key_type& __key, __hash_code __c) const
{
	__node_base* __before_n = _M_find_before_node(__bkt, __key, __c);
	if (__before_n)
	  	return static_cast<__node_type*>(__before_n->_M_nxt);
	return nullptr;
}

它调用了函数_M_find_before_node,再来看这个函数的源代码:

// Find the node whose key compares equal to k in the bucket n.
// Return nullptr if no node is found.
template<typename _Key, typename _Value,typename _Alloc, typename _ExtractKey, typename _Equal,typename _H1, typename _H2, typename _Hash, typename _RehashPolicy,typename _Traits>
auto _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,_H1, _H2, _Hash, _RehashPolicy, _Traits>::_M_find_before_node(size_type __n, const key_type& __k,__hash_code __code) const-> __node_base*
{
	__node_base* __prev_p = _M_buckets[__n];
	if (!__prev_p)
		return nullptr;
	
	for (__node_type* __p = static_cast<__node_type*>(__prev_p->_M_nxt);;__p = __p->_M_next())
	{
		if (this->_M_equals(__k, __code, __p))
			return __prev_p;
	
		if (!__p->_M_nxt || _M_bucket_index(__p->_M_next()) != __n)
			break;
		__prev_p = __p;
	}
	return nullptr;
}

这是一个解决哈希表冲突的代码,时间复杂度为 \(\Omega(1)\sim O(n)\)

\(\mathscr{Q.E.D.}\)

2.2 插入操作

插入操作的函数是insert,它的源代码是:

 /// Re-insert an extracted node.
insert_return_type insert(node_type&& __nh)
{ return _M_h._M_reinsert_node(std::move(__nh)); }

/// Re-insert an extracted node.
iterator insert(const_iterator, node_type&& __nh)
{ return _M_h._M_reinsert_node(std::move(__nh)).position; }

可见其调用了函数 _M_h._M_reinsert_node,这个函数的定义是:

/// Re-insert an extracted node into a container with unique keys.
insert_return_type _M_reinsert_node(node_type&& __nh)
{
	insert_return_type __ret;
	if (__nh.empty())
		__ret.position = end();
	else
	{
		__glibcxx_assert(get_allocator() == __nh.get_allocator());
		
		const key_type& __k = __nh._M_key();
		__hash_code __code = this->_M_hash_code(__k);
		size_type __bkt = _M_bucket_index(__k, __code);
		if (__node_type* __n = _M_find_node(__bkt, __k, __code))
		{
			__ret.node = std::move(__nh);
			__ret.position = iterator(__n);
			__ret.inserted = false;
		}
		else
		{
			__ret.position = _M_insert_unique_node(__bkt, __code, __nh._M_ptr);
			__nh._M_ptr = nullptr;
			__ret.inserted = true;
		}
	}
	return __ret;
}

这里的 \(16\sim 18\) 行是有相同键的插入,时间复杂度 \(O(1)\)\(22\sim 24\) 行是插入操作。同时,这个函数调用了函数 _M_find_node,根据上面的证明,这个函数的时间复杂度为 \(O(1)\sim O(n)\)

再来看函数_M_insert_unique_node的实现:

template<typename _Key, typename _Value, typename _Alloc, typename _ExtractKey, typename _Equal, typename _H1, typename _H2, typename _Hash, typename _RehashPolicy, typename _Traits>
auto _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, _Traits>::_M_insert_unique_node(size_type __bkt, __hash_code __code,__node_type* __node, size_type __n_elt) -> iterator
{
	const __rehash_state& __saved_state = _M_rehash_policy._M_state();
	std::pair<bool, std::size_t> __do_rehash = _M_rehash_policy._M_need_rehash(_M_bucket_count, _M_element_count, __n_elt);
					  
	if (__do_rehash.first)
	{
	    _M_rehash(__do_rehash.second, __saved_state);
	    __bkt = _M_bucket_index(this->_M_extract()(__node->_M_v()), __code);
	}

	this->_M_store_code(__node, __code);

	// Always insert at the beginning of the bucket.
	_M_insert_bucket_begin(__bkt, __node);
	++_M_element_count;
	return iterator(__node);
}

这里第 \(9\sim 10\) 行是解决哈希冲突的代码。众所周知,解决哈希冲突一直是哈希表最费时间的操作。所以我们来看这两行代码。其中_M_rehash就是解决冲突的函数,它的代码如下:

template<typename _Key, typename _Value, typename _Alloc, typename _ExtractKey, typename _Equal, typename _H1, typename _H2, typename _Hash, typename _RehashPolicy, typename _Traits>
void _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,_H1, _H2, _Hash, _RehashPolicy, _Traits>::_M_rehash(size_type __n, const __rehash_state& __state)
{
  _M_rehash_aux(__n, __unique_keys());
}

函数_M_rehash_aux的代码是:

// Rehash when there is no equivalent elements.
template<typename _Key, typename _Value, typename _Alloc, typename _ExtractKey, typename _Equal, typename _H1, typename _H2, typename _Hash, typename _RehashPolicy, typename _Traits>
void _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, _Traits>::_M_rehash_aux(size_type __n, std::true_type)
{
	__bucket_type* __new_buckets = _M_allocate_buckets(__n);
	__node_type* __p = _M_begin();
	_M_before_begin._M_nxt = nullptr;
	std::size_t __bbegin_bkt = 0;
	while (__p)
	{
		__node_type* __next = __p->_M_next();
		std::size_t __bkt = __hash_code_base::_M_bucket_index(__p, __n);
		if (!__new_buckets[__bkt])
		{
			__p->_M_nxt = _M_before_begin._M_nxt;
			_M_before_begin._M_nxt = __p;
			__new_buckets[__bkt] = &_M_before_begin;
			if (__p->_M_nxt)
				__new_buckets[__bbegin_bkt] = __p;
			__bbegin_bkt = __bkt;
		}
		else
		{
			__p->_M_nxt = __new_buckets[__bkt]->_M_nxt;
			__new_buckets[__bkt]->_M_nxt = __p;
		}
		__p = __next;
	}
	
	_M_deallocate_buckets();
	_M_bucket_count = __n;
	_M_buckets = __new_buckets;
}

// Rehash when there can be equivalent elements, preserve their relative order.
template<typename _Key, typename _Value, typename _Alloc, typename _ExtractKey, typename _Equal, typename _H1, typename _H2, typename _Hash, typename _RehashPolicy, typename _Traits>
void _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, _Traits>::_M_rehash_aux(size_type __n, std::false_type)
{
	__bucket_type* __new_buckets = _M_allocate_buckets(__n);
	
	__node_type* __p = _M_begin();
	_M_before_begin._M_nxt = nullptr;
	std::size_t __bbegin_bkt = 0;
	std::size_t __prev_bkt = 0;
	__node_type* __prev_p = nullptr;
	bool __check_bucket = false;
	
	while (__p)
	{
		__node_type* __next = __p->_M_next();
		std::size_t __bkt = __hash_code_base::_M_bucket_index(__p, __n);
		
		if (__prev_p && __prev_bkt == __bkt)
		{
			// Previous insert was already in this bucket, we insert after
			// the previously inserted one to preserve equivalent elements
			// relative order.
			__p->_M_nxt = __prev_p->_M_nxt;
			__prev_p->_M_nxt = __p;
			
			// Inserting after a node in a bucket require to check that we
			// haven't change the bucket last node, in this case next
			// bucket containing its before begin node must be updated. We
			// schedule a check as soon as we move out of the sequence of
			// equivalent nodes to limit the number of checks.
			__check_bucket = true;
		}
		else
		{
			if (__check_bucket)
			{
				// Check if we shall update the next bucket because of
				// insertions into __prev_bkt bucket.
				if (__prev_p->_M_nxt)
				{
					std::size_t __next_bkt = __hash_code_base::_M_bucket_index(__prev_p->_M_next(),__n);
					if (__next_bkt != __prev_bkt)
					__new_buckets[__next_bkt] = __prev_p;
				}
				__check_bucket = false;
			}
			
			if (!__new_buckets[__bkt])
			{
				__p->_M_nxt = _M_before_begin._M_nxt;
				_M_before_begin._M_nxt = __p;
				__new_buckets[__bkt] = &_M_before_begin;
				if (__p->_M_nxt)
					__new_buckets[__bbegin_bkt] = __p;
				__bbegin_bkt = __bkt;
			}
			else
			{
				__p->_M_nxt = __new_buckets[__bkt]->_M_nxt;
				__new_buckets[__bkt]->_M_nxt = __p;
			}
		}
		__prev_p = __p;
		__prev_bkt = __bkt;
		__p = __next;
	}
	
	if (__check_bucket && __prev_p->_M_nxt)
	{
		std::size_t __next_bkt
		= __hash_code_base::_M_bucket_index(__prev_p->_M_next(), __n);
		if (__next_bkt != __prev_bkt)
			__new_buckets[__next_bkt] = __prev_p;
	}
	
	_M_deallocate_buckets();
	_M_bucket_count = __n;
	_M_buckets = __new_buckets;
}

这里两个函数重载的时间复杂度均为 \(\Omega(1)\sim O(n)\),可以证明没有比这更费时的操作了。

\(\mathscr{Q.E.D.}\)

3.multimap

multimap的实现与map所在头文件相同,且实现差别不大,故本部分简略说明时间复杂度。

3.1 查询与修改操作

map查询与修改操作的时间复杂度已经在前文证明,multimap与它的时间复杂度差异仅存在于解决重复元素部分代码中。在最坏情况下(所有值都存储在一个键下),查询与修改操作的时间复杂度为 \(O(n)\)。故multimap的时间复杂度为 \(\Omega(1)\sim O(n)\)

3.2 插入操作

Under construction.

4.priority_queue

这里探讨更快的ext/pd_ds/priority_queue.hpp

priority_queue的声明在priority_queue.hpp中,它继承于detail::container_base_dispatch::type,这个类的声明位于ext/pb_ds/detail/priority_queue_base_dispatch.hpp,这个类是一个名称,其实等同于binomial_heap类。binomial_heap又继承自pairing_heap_tag,它位于ext/pb_ds/detail/pairing_heap_/pairing_heap_.hpp,它又继承自PB_DS_P_HEAP_BASE类。PB_DS_P_HEAP_BASE类的声明在同一文件中,是一个typedef,如下:

#define PB_DS_P_HEAP_BASE left_child_next_sibling_heap<Value_Type, Cmp_Fn, null_type, _Alloc>

这里的left_child_next_sibling_heap位于ext/pb_ds/detail/left_child_next_sibling_heap_/left_child_next_sibling_heap_.hpp中。

4.1 插入操作

插入操作位于ext/pb_ds/detail/pairing_heap_/insert_fn_imps.hpp中,定义了函数pushmodify

posted @ 2025-01-25 08:30  OIer_hcx2012  阅读(329)  评论(0)    收藏  举报