开散列概念 开散列法又叫链地址法(拉链法)。首先计算映射位置,具有相同地映射关系的值归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
跟闭散列一样,只是二者在实现的时候,用的存储结构不同
我们写在一个自定义类域 Bucket 里面
节点结构体
namespace Bucket
{
template
struct HashNode
{
pair _kv;
HashNode* _next;
HashNode(const pair& kv)
:_kv(kv)
,_next(nullptr)
{}
};
}
哈希表类
template>
class HashTable
{
typedef HashNode Node;
public:
~HashTable();
bool Insert(const pair& kv);
Node* find(const K& key);
bool Erase(const K& key);
private:
vector_tables;//开散列是用数组保存的 个个链表的头结点
size_t size = 0;//存储有效数据的个数
};
哈希表的析构函数
~HashTable()
{
for (size_t i = 0; i < _tables.size(); ++i)
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
_tables[i] = nullptr;
}
}
记得要先写析构函数 因为这里在堆上new了一堆节点,默认生成的构造函数不能满足需要了
bool Insert(const pair& kv)
{ //if (Find(kv.first)) return false;
Hash hash;//仿函数 不理解的可以看看上一篇 哈希表开散列 的博文
if (_size == _tables.size())
{
//存满了需要扩容
size_t newsize = _tables.size() == 0 ? 10: _tables.size() * 2;
//资本家式扩容
vector newTables;
newTables.resize(newsize, nullptr);
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
//将i下标位置存放的链表指针 一个一个插入到新表
while (cur)
{
Node* next = cur->_next;
size_t hashi=hash(cur->_kv.first)%_newTables.size();
//头插
cur->_next = newTables[hashi];
newTables[hashi] = cur;
cur = next;
}
_tables[i] = nullptr;
}
_tables.swap(newTables);
}
//剩余位置足够 或者 扩容完毕
size_t hashi = hash(kv.first) % _tables.size();
Node* newnode = new Node(kv);
newnode->_next = _tables[hashi];
_tables[hashi] = newnode;
_size++;
return true;
}
测试用例
void TestHT1()
{
int a[] = { 1, 11, 4, 15, 26, 7, 44,55,99,78 };
HashTable ht;
for (auto e : a)
{
ht.Insert(make_pair(e, e));
}
ht.Insert(make_pair(22, 22));
}
Node* Find(const K& key)
{
if (_tables.size() == 0) return nullptr;
size_t hashi = key % _tables.size();
Node* cur = _tables[hashi];//时刻记得_tables里面放的是Node*
while (cur)
{
if (cur->_kv.first == key) return cur;
cur = cur->_next;
}
return nullptr;
}
bool Erase(const K& key)
{
if (_tables.size() == 0) return false;
Hash hash;
size_t hashi = hash(key) % _tables.size();
Node* prev = nullptr;
Node* cur = _tables[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
if (prev == nullptr)
{
_tables[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
_size--;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
_tables.size()的选取决定拉链法的效率,一般都采取质数,而非二倍。在源码中,标准库给了一个数组,如下:
static const size_t __stl_num_primes = 28;
static const size_t __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,
1610612741, 3221225473, 4294967291
};
我们对于_tables.size()的改进可以依此
inline size_t next_size(size_t n)
{
static const size_t __stl_num_primes = 28;
static const size_t __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,
1610612741, 3221225473, 4294967291
};
for(int i=0;i<28;i++)
{
if(__stl__prime_list[i]>n)
{
return __stl__prime_list[i];
}
}
return -1;
}
bool Insert(const pair& kv)
{
//...
if (_size == _tables.size())
{
vector newTables;
newTables.resize(next_size(_tables.size()),nullptr);
//...
}
//...
return true;
}
为了让使用者可以访问我们的私有成员变量的值,进行封装
//...
{
public:
size_t Size()
{
return _size;
}
// 表的长度
size_t TablesSize()
{
return _tables.size();
}
// 桶的个数
size_t BucketNum()
{
size_t num = 0;
for (size_t i = 0; i < _tables.size(); ++i)
{
if (_tables[i])
{
++num;
}
}
return num;
}
};