目录
一、哈希表
1.1、哈希表相关概念
对于线性表或树等数据结构,记录在数据结构中的相对位置是随机的,和记录的关键字之间不存在确定的关系,以此,在结构中查找记录时需要进行一系列和关键字的比较。理想的情况是希望不经过任何比较,一次存取便能得到所查记录,那就必须在记录的存储位置和它的关键字之间建立一个确定的对应关系 f,使每个关键字和结构中一个唯一的存储位置相对应。因而在查找时,只要根据这个对应关系 f 找到给定值 K 的像 f(K)。若结构中存在关键字和 K 相等的记录,则必定在 f(K)的存储位置上,由此,不需要进行比较便可直接取得所查记录。我们称这个对应关系 f 为哈希(hash)函数,按这个思想建立的表为哈希表。
对不同的关键字可能得到同一哈希地址,这种现象称为冲突。具有相同函数值的关键字对该哈希函数来说称做同义词。在一般情况下,冲突只能尽可能地少,而不能完全避免。因为,哈希函数是从关键字集合到地址集合的映像。通常,关键字集合比较大,它的元素包括所有可能的关键字,而地址集合的元素仅为哈希表中的地址值。
若对一个关键字集合中的任一个关键字,经过哈希函数映像到地址集合中任何一个地址的概率是相等的,则称此类哈希函数为均匀的哈希函数。换句话说,就是使关键字经过哈希函数得到一个“随机的地址”,以便使一组关键字的哈希地址均匀分布在整个地址区间中,从而减少冲突。
1.2、处理冲突的方法
1.2.1、开放寻址法(open addressing)
在开放寻址法中,所有的元素都存放在哈希表中。当查找某个元素时,要系统地检查所有的表项,直到找到所需的元素,或者最终查明该元素不在表中。
1.2.1.1、线性探测
线性探测采用的哈希函数为:
h(k,i) = (h(k) + i)mod m,i = 0,1,…,m - 1
其中 m 为哈希表表长。
1.2.1.2、二次探测
二次探测采用的哈希函数为:
h(k,i) = (h(k) + a * i + b * i * i)mod m,i = 0,1,…,m - 1
其中 m 为哈希表表长。
1.2.2、链接法(chaining)
将所有关键字为同义词的记录存储在同一线性表中。STL的哈希表采用的便是这种做法。
1.3 具体代码示例
1.3.1、哈希表节点的定义
struct _Hash_node_base
{
_Hash_node_base* _M_nxt;
_Hash_node_base() noexcept : _M_nxt() { }
_Hash_node_base(_Hash_node_base* __next) noexcept : _M_nxt(__next) { }
};
template <typename _Value>
struct _Hash_node_value_base : _Hash_node_base
{
typedef _Value value_type;
...
};
/**
* Primary template struct _Hash_node.
*/
template <typename _Value, bool _Cache_hash_code>
struct _Hash_node;
/**
* Specialization for nodes with caches, struct _Hash_node.
*
* Base class is __detail::_Hash_node_value_base.
*/
template <typename _Value>
struct _Hash_node<_Value, true> : _Hash_node_value_base<_Value>
{
std::size_t _M_hash_code;
_Hash_node *
_M_next() const noexcept
{
return static_cast<_Hash_node *>(this->_M_nxt);
}
};
/**
* Specialization for nodes without caches, struct _Hash_node.
*
* Base class is __detail::_Hash_node_value_base.
*/
template <typename _Value>
struct _Hash_node<_Value, false> : _Hash_node_value_base<_Value>
{
_Hash_node *
_M_next() const noexcept
{
return static_cast<_Hash_node *>(this->_M_nxt);
}
};
1.3.2、哈希表的构造和内存管理
template <typename _Key, typename _Value, typename _Alloc,
typename _ExtractKey, typename _Equal,
typename _H1, typename _H2, typename _Hash,
typename _RehashPolicy, typename _Traits>
class _Hashtable
: public __detail::_Hashtable_base<_Key, _Value, _ExtractKey, _Equal,
_H1, _H2, _Hash, _Traits>,
public __detail::_Map_base<_Key, _Value, _Alloc, _ExtractKey, _Equal,
_H1, _H2, _Hash, _RehashPolicy, _Traits>,
public __detail::_Insert<_Key, _Value, _Alloc, _ExtractKey, _Equal,
_H1, _H2, _Hash, _RehashPolicy, _Traits>,
public __detail::_Rehash_base<_Key, _Value, _Alloc, _ExtractKey, _Equal,
_H1, _H2, _Hash, _RehashPolicy, _Traits>,
public __detail::_Equality<_Key, _Value, _Alloc, _ExtractKey, _Equal,
_H1, _H2, _Hash, _RehashPolicy, _Traits>,
private __detail::_Hashtable_alloc<
__alloc_rebind<_Alloc,
__detail::_Hash_node<_Value,
_Traits::__hash_cached::value>>>
{
__bucket_type *_M_buckets = &_M_single_bucket;
size_type _M_bucket_count = 1;
__node_base _M_before_begin;
size_type _M_element_count = 0;
_RehashPolicy _M_rehash_policy;
__bucket_type _M_single_bucket = nullptr;
...
};
template <typename _Key, typename _Value,
typename _Alloc, typename _ExtractKey, typename _Equal,
typename _H1, typename _H2, typename _Hash, typename _RehashPolicy,
typename _Traits>
_Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,
_H1, _H2, _Hash, _RehashPolicy, _Traits>::
_Hashtable(size_type __bucket_hint,
const _H1 &__h1, const _H2 &__h2, const _Hash &__h,
const _Equal &__eq, const _ExtractKey &__exk,
const allocator_type &__a)
: _Hashtable(__h1, __h2, __h, __eq, __exk, __a)
{
auto __bkt = _M_rehash_policy._M_next_bkt(__bucket_hint);
if (__bkt > _M_bucket_count)
{
_M_buckets = _M_allocate_buckets(__bkt);
_M_bucket_count = __bkt;
}
}
// Return a bucket size no smaller than n (as long as n is not above the
// highest power of 2).
std::size_t _M_next_bkt(std::size_t __n) noexcept
{
const auto __max_width = std::min<size_t>(sizeof(size_t), 8);
const auto __max_bkt = size_t(1) << (__max_width * __CHAR_BIT__ - 1);
std::size_t __res = __clp2(__n);
if (__res == __n)
__res <<= 1;
if (__res == 0)
__res = __max_bkt;
if (__res == __max_bkt)
// Set next resize to the max value so that we never try to rehash again
// as we already reach the biggest possible bucket number.
// Note that it might result in max_load_factor not being respected.
_M_next_resize = std::size_t(-1);
else
_M_next_resize = __builtin_ceil(__res * (long double)_M_max_load_factor);
return __res;
}
__bucket_type * _M_allocate_buckets(size_type __n)
{
if (__builtin_expect(__n == 1, false))
{
_M_single_bucket = nullptr;
return &_M_single_bucket;
}
return __hashtable_alloc::_M_allocate_buckets(__n);
}
template <typename _NodeAlloc>
typename _Hashtable_alloc<_NodeAlloc>::__bucket_type *
_Hashtable_alloc<_NodeAlloc>::_M_allocate_buckets(std::size_t __n)
{
__bucket_alloc_type __alloc(_M_node_allocator());
auto __ptr = __bucket_alloc_traits::allocate(__alloc, __n);
__bucket_type *__p = std::__to_address(__ptr);
__builtin_memset(__p, 0, __n * sizeof(__bucket_type));
return __p;
}
// Insert v if no element with its key is already present.
template <typename _Key, typename _Value,
typename _Alloc, typename _ExtractKey, typename _Equal,
typename _H1, typename _H2, typename _Hash, typename _RehashPolicy,
typename _Traits>
template <typename _Arg, typename _NodeGenerator>
auto _Hashtable<_Key, _Value, _Alloc, _ExtractKey, _Equal,
_H1, _H2, _Hash, _RehashPolicy, _Traits>::
_M_insert(_Arg &&__v, const _NodeGenerator &__node_gen, true_type,
size_type __n_elt)
-> pair<iterator, bool>
{
const key_type &__k = this->_M_extract()(__v);
__hash_code __code = this->_M_hash_code(__k);
size_type __bkt = _M_bucket_index(__k, __code);
__node_type *__n = _M_find_node(__bkt, __k, __code);
if (__n)
return std::make_pair(iterator(__n), false);
__n = __node_gen(std::forward<_Arg>(__v));
return {_M_insert_unique_node(__bkt, __code, __n, __n_elt), true};
}
__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;
}
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);
__try
{
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);
}
__catch(...)
{
this->_M_deallocate_node(__node);
__throw_exception_again;
}
}