文章目录
1. hashtable概述
hashtable
,即我们在数据结构中所说的散列表,也叫哈希表,在插入、删除、搜寻等操作上具有“常数平均时间”的表现,而且这种表现是以统计为基础,不需仰赖输入元素的随机性- 使用hash function将某一元素映射为一个“大小可接受的索引”,通过该索引找到在array中的位置,从而构成一个hashtable;使用hash function可能带来一个问题:即不同的元素经过hash function的作用被映射到相同的位置,这样就产生了碰撞,我们应该采取什么方法来解决碰撞呢?
- 解决碰撞的方法有许多,我们在这主要分析三种:
1.线性探测 2.二次探测3.开链法
1.1 线性探测
- 1.
利用hash function计算出某个元素的插入位置
- 2.
若该位置上的空间不可用,则循序往下寻找,直到找到一个可用空间为止
分析线性探测的表现:
首先需做两个假设:1)表格足够大 2)每个元素能够独立
在此假设情况下,最坏的情况是线性巡访整个表格,平均情况则是巡访一半表格,可这也与我们期望的常数时间相差甚远
线性探测还可能带来主集团问题,主集团即平均插入成本的成长幅度远高于负载系数的成长幅度
1.2 二次探测
- 1.
利用hash function计算出某个元素的插入位置H
- 2.
若该位置实际上已被使用,则依序尝试H + 1 ^2、H + 2 ^2...H + i ^2,直到发现新空间
分析二次探测:
二次探测是为了消除主集团,但却可能造成次集团:两个元素经hash function计算出来的位置若相同,则插入时所探测的位置也相同,形成某种浪费
1.3 开链法
- 1.
在每一个表格元素种维护一个list
- 2.
hash function为我们分配某一个list,然后在list上执行元素的插入、搜寻、删除操作
SGI STL的hashtable正是采用这种做法实现的
2. hashtable的桶与节点
-
称hashtable表格中的元素为“桶”,是因为表格内的每个单元,涵盖的不只是个节点,甚至可能是
一“桶”节点
-
hashtable的节点定义:
template <class Value>
struct __hashrable_node {
__hashtable_node* next;
Vlalue val;
};
STL并不以list或slist维护hashtable node,而是以vector制造的bucket来维护,至于为什么用vector,是因为需要动态扩充能力
3. hashtable迭代器
- hashtable迭代器的定义(仅部分源代码):
template <class Value, class Key, class HashFcn,
class ExtractKey, class EqualKey, class Alloc>
struct __hashtable_iterator {
//...
typedef __hashtable_node <Value> node;
typedef forward_iterator_tag iterator_category; //hashtable没有后退操作
//...
node* cur; //迭代器所指节点
hashtable* ht; //连接容器
//构造函数
__hashtable_iterator(node* n, hashtable* tab) : cur(n), ht(tab) {}
__hashtable_iterator() {}
//取值
reference operator*() const { return cur->val; }
pointer operator->() const { return &(operator*()); }
//迭代器递增操作
iterator& operator++();
iterator operator++(int);
//判断迭代器是否相等
bool operator==(const iterator& it) const { return cur == it.cur; }
bool operator!=(const iterator& it) const { return cur!= it.cur; }
};
//递增操作实现
template <class V, class K, class HF, class ExK, class EqK, class A>
__hashtable_iterator<V, K, HF, ExK, EqK, A>&
__hashtable_iterator<V, K, HF, ExK, EqK, A>::operator++()
{
const node* old = cur;
cur = cur->next; //如果存在节点则就是该cur,不存在节点则进入if流程,找到下一个bucket
if (!cur) {
size_type bucket = ht->bkt_num(old->val); //找到当前值所对应的桶
while (!cur && ++ bucket < ht->buckets.size())
cur = ht->buckets[bucket]; //令cur指向下一个bucket
}
return *this;
}
template <class V, class K, class HF, class ExK, class EqK, class A>
__hashtable_iterator<V, K, HF, ExK, EqK, A>&
__hashtable_iterator<V, K, HF, ExK, EqK, A>::operator++(int)
{
iterator tmp = *this;
++*this;
return tmp;
{
4. hashtable数据结构
- 在下面定义中,可以看到
buckets聚合体
以vector完成:
template <class Value, class Key, class HashFcn,
class ExtractKey, class EqualKey, class Alloc>
class hashtable {
public:
typedef HashFcn hasher;
typedef EqualKey key_equal;
typedef size_t size_type;
private:
hasher hash;
key_equal equals;
ExtractKey get_key;
typedef __hashtable_node<Value> node;
typedef simple_allloc<node, Alloc> node_allocator;
vector<node*, Allloc> buckets; //以buckets维护节点
size_type num_elements; //节点个数
public:
size_type bucket_count() const { return buckets.size(); }
...
};
value:节点的实值型别
Key:节点的键值型别
HashFcn:hashfunction的函数型别
ExtractKey:从节点中取出键值的方法
E权力Key: 判断键值相同与否的方法
Alloc:空间配置器
SGI STL以质数来设计表格大小,并且先将28个质数计算好,已备随时访问,同时提供一个函数,用来查询在这28个函数中“最接近并大于或等于n的那个质数”
:
static const int __stl_num_primes = 28;
static const unsigned long __stl_prime_list[__stl_num_primes] =
{
53, 97, 193, 389, 769,
1543, 3079, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433,
1572869, 3145739, 6291469, 12582917, 25165843,
50331653, 100663319, 201326611, 402653189, 805306457,
16110612741, 3221225473ul, 4294967291ul
};
//在28个质数中找出最接近并大于或等于n的质数
inline unsigned long __stl_next_prime(unsigned long n)
{
const unsigned long* first = __stl_prime_list;
const unsigned long* last = __stl_prime_list + __stl_num_primes;
const unsigned long* pos = lower_bound(first, last, n);
return pos == last ? *(last-1): *pos;
}
//最大buckets数
size_type max_bucket_count() const
{
return __stl_prime_list[__stl_num_primes - 1]; }
5. hashtable构造与内存管理
- 节点配置与节点释放函数:
node* new_node(const value_type& obj)
{
node* n = node_allocator::allocate();
n->next = 0;
__STL_TRY {
construct(&n->val, obj);
return n;
}
__STL_UNWIND(node_allocator::deallocate(n));
}
void delete_node(node* n)
{
destroy(&n->val);
node_alllocaor::deallocate(n);
}
- hashtable构造:
hashtable(size_type n, const HashFcn& hf, const EqualKey& eql)
: hash(hf), equals(eql), get_key(ExtractKey()), num_elements(0)
{
initialize_buckets(n);
}
void initialize_buckets(size_type n)
{
buckets.reserve(n_bckets);
buckets.insert(buckets.end(), n_buckets, (node*) 0);
num_elements = 0;
}
- 插入元素:分为两种,insert_unique与insert_equal
// 插入操作,不允许重复
pair<iterator, bool> insert_unique(const value_type& obj)
{
resize(num_elements + 1);
return insert_unique_noresize(obj);
}
template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
pair<typename hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::iterator, bool>
hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
::insert_unique_noresize(const value_type& obj)
{
const size_type n = bkt_num(obj);
node* first = buckets[n];
// 如果已经存在则直接返回
for (node* cur = first; cur; cur = cur->_next)
if (equals(get_key(cur->val), get_key(obj)))
return pair<iterator, bool>(iterator(cur, this), false);
// 插入新节点
node* tmp = new_node(obj); //产生新节点
tmp->next = first; //将新节点插入链表头部
buckets[n] = tmp;
++num_elements; //节点个数累计加1
return pair<iterator, bool>(iterator(tmp, this), true); ///返回一个迭代器,指向新增节点
}
//插入操作,允许重复
iterator insert_equal(const value_type& obj)
{
resize(num_elements + 1);
return insert_equal_noresize(obj);
}
template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
pair<typename hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::iterator, bool>
hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
::insert_equal_noresize(const value_type& obj)
{
const size_type n = bkt_num(obj);
node* first = buckets[n];
// 如果已经存在则直接插入并返回
for (node* cur = first; cur; cur = cur->_next)
if (equals(get_key(cur->val), get_key(obj))) {
node* tmp = new_node(obj); //产生新节点
tmp->next = first; //将新节点插入链表头部
buckets[n] = tmp;
++num_elements; //节点个数累计加1
return pair<iterator, bool>(iterator(tmp, this), true); ///返回一个迭代器,指向新增节点
}
// 表示没有发现新节点
node* tmp = new_node(obj); //产生新节点
tmp->next = first; //将新节点插入链表头部
buckets[n] = tmp;
++num_elements; //节点个数累计加1
return pair<iterator, bool>(iterator(tmp, this), true); ///返回一个迭代器,指向新增节点
}
//判断是否需要重建表格
template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
void hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
::resize(size_type num_elements_hint)
{
const size_type old_n = buckets.size();
// 超过原来表格的大小时才进行调整
if (__num_elements_hint > old_n) {
// 新的表格大小
const size_type n = next_size(num_elements_hint);
// 在边界情况下可能无法调整(没有更大的素数了)
if (n > old_n) {
vector<node*, All> tmp(n, (node*)(0),
__STL_TRY {
// 填充新的表格
for (size_type bucket = 0;bucket < old_n; ++bucket) {
node* first = buckets[bucket]; //指向节点所对应的起始节点
while (first) {
//以下找出节点落在哪一个新bucket内
size_type __new_bucket = bkt_num(first->val, n);
//令旧节点指向对应串行的下一个节点
buckets[bucket] = first->next;
//将当前节点插入到新bucket内
first->next = tmp[new_bucket];
tmp[new_bucket] = first;
//准备处理下一个节点
first = buckets[bucket];
}
}
// 通过swap交换
buckets.swap(tmp);
}
}
}
}
- 判知元素的落脚处:
size_type bkt_num(const value_type& obj, size_t n) const {
return bkt_num_key(get_key(obj), n);
}
size_type bkt_num(const value_type& obj) const
{
return bkt_num_key(get_key(obj));
}
size_type bkt_num_key(const value_type& key) const {
return bkt_num_key(key, buckets.size());
}
size_type bkt_num_key(const value_type& key,szie_t n) const {
return hash(key) % n;
}
6. 元素操作
操作 | 功能 |
---|---|
copy_from | 复制整个hashtable |
clear | hashtable整体删除 |
find | 搜寻键值为key的元素 |
count | 计算键值为key的元素个数 |
- 一个实例:
#include <iostream>
#include <hash_set>
#include <algorithm>
using namespace std;
int main()
{
//指定保留50个buckets
hashtable<int, int, hash<int>,
identify<int>, equal_to<int>, alloc> iht(50, hash<int>(), equal_to<iny>());
cout << iht.size() << endl; //0
cout << iht.bucket_count() << endl; //53,满足要求的最小质数
cout << iht.max_bucket_count() << endl; //4294967291
iht.insert_unique(59);
iht.insert_unique(63);
iht.insert_unique(108);
iht.insert_unique(2);
iht.insert_unique(53);
iht.insert_unique(55);
cout << iht.size() << endl; //6
//声明迭代器
hashtable<int, int, hash<int>,
identify<int>, equal_to<int>, alloc>::iterator ite = iht.begin();
for (int i = 0; i < iht.size(); ++i, ++ite)
cout << *ite << ' '; //53 53 2 108 59 63
cout << endl;
for (int i = ); i < iht.bucket_count() ; ++i) {
int n = iht.elems_in_bucket(i);
if (n != 0)
cout << "bucket[" << i << "] has " << n << " elems." << endl;
}
//bucket[0] has 1 elems
//bucket[2] has 3 elems
//bucket[6] has 1 elems
//bucket[10] has 1 elems
//插入48个元素
for (int i = 0; i <= 47; ++i)
{
iht.insert_equal(i);
}
cout << iht.size() << endl; //54
cout << iht.bucket_count() << endl; //大于53,更改buckets个数为大于53的质数,97
for (int i = 0; i < iht.bucket_count(); ++i){
int n = iht.elems_in_bucket(i);
if (n != 0)
cout << "bucket[" << i << "] has " << n << " elems." << endl;
}
//打印结果:bucket[2]和bucket[11]的节点个数为2
//其余的bucket[0]~bucket[47]的节点个数为1
//此外,bucket[53],[55],[59],[63]的节点个数为1
ite = ite.begin();
for (int i = 0; i < iht.size(); ++i, ++ite)
cout << *ite << ' ';
cout << endl;
//0 1 2 2 3 4 5 6 7 8 9 10 11 108 12 13 14 15 16 17 18 19 20 21
//22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
//43 44 45 46 47 53 55 59 63
cout << *(ite.find(2)) << endl; //2
cout << iht.count(2) << endl; //1
return 0;
}
通过上述这个实例,可以对于resize有更深的理解,对于元素操作也能又更好的的理解
7. hash functions
hash function是计算元素位置的函数,SGI将这项任务交给bkt_num函数,然后再调用下列提供的hash function,取得一个可以对hashtable进行模运算的值
template <class Key> struct hash{}
//以下这个转换函数还没看太懂?????
inline size_t __stl_hash_string(const char* s)
{
unsigned long h = 0;
for (; *s; ++s)
h = 5*h + *s;
return size_t(h);
}
template <>
__STL_TEMPLATE_NULL struct hash<char*>
{
size_t operator()(const char *s) const { return __stl_hash_string(s); }
};
//...