目录
1.底层结构
1)方式
2)哈希冲突
3)哈希函数
4)哈希冲突的解决
1.闭散列
1)线性探测
扩容:
2)二次探测
2.开散列
1)概念
2)实现
插入操作:
删除操作:
查找操作:
哈希桶的销毁:
3)迭代器的实现
4)begin -- end
2.unordered_set的实现
3.unordered_map的实现
1.底层结构
两种容器的底层结构均为哈希表
概念:通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立
一一映射的关系,那么在查找时通过该函数可以很快找到该元素
1)方式
搜索:对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功
2)哈希冲突
在上述的操作中,难免会出现不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。
而哈希冲突的发生是不可避免的
3)哈希函数
引起哈希冲突的一个原因可能是:哈希函数设计不够合理。
哈希函数设计原则 :1.哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有 m 个地址时,其值域必须在0 到 m-1 之间2.哈希函数计算出来的地址能均匀分布在整个空间中3.哈希函数应该比较简单
常见的哈希哈数:直接定址法、除留余数法、平方取中法、折叠法、随机数法、数学分析法
4)哈希冲突的解决
1.闭散列
1)线性探测
从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止
比如上述,如果删除了6号元素,直接标记空的话:查询44号元素的话,就会查找失败
扩容:
扩容有个前提条件,即载荷因子超出预定值时进行扩容
散列表中载荷因子的定义为:α = 填入表中的元素个数 / 散列表的长度
α 是散列表装满程度的标志因子。由于表长是定值,α 与“填入表中的元素个数”成正比,所以,α 越大,表明填入表中的元素越多,产生冲突的可能性就越大:反之,越小,标明填入表中的元素越少,产生冲突的可能性就越小。实际上,散列表的平均查找长度是载荷因子 α 的函数,只是不同处理冲突的方法有不同的函数。
2)二次探测
为了避免线性探测的逐个寻找空位,即有了二次探测:
若冲突,则:
(x + i ^ 1)/ m、(x + i ^ 2)、......i为1,2,3,4,5.....
2.开散列
1)概念
2)实现
template<class T>
struct HashNode
{
HashNode<T>* _next;
T _data;
HashNode(const T& data)
:_next(nullptr)
, _data(data)
{}
};
template<class K, class T, class KeyOfT, class Hash = HashFunc<K>>
class HashTable
{
typedef HashNode<T> Node;
public:
HashTable()
{
_tables.resize(10, nullptr);
}
private:
vector<Node*> _tables;
size_t _n = 0;
};
底层即为vector,vector存储的每个数据为节点的指针
HashTable的构造函数即为重要,因为要事先开辟一些空间,防止访问空指针导致错误
插入操作:
未达到载荷因子就无须扩容,直接头插即可,若达到,则扩容后插入即可
pair<Iterator, bool> Insert(const T& data)
{
Hash hs;
KeyOfT kot;
Node* cur = nullptr;
Iterator it = Find(kot(data));
if (it != End())
return make_pair(it, false);
if (_n == _tables.size())
{
// 扩容
// ......
}
else // 正常找空位插入即可
{
size_t hashi = hs(kot(data)) % _tables.size();
if (_tables[hashi] == nullptr)
{
_tables[hashi] = new Node(data);
cur = _tables[hashi]; // 这里cur一开始忘了给值,小修bug
}
else
{
cur = new Node(data);
Node* tmp = _tables[hashi];
cur->_next = tmp;
_tables[hashi] = cur;
}
_n++;
}
return make_pair(Iterator(cur, this), true);
}
返回值用pair<Iterator, bool> 是因为方便unordered_map的 [] 的实现
扩容操作:
创建一个新表,然后遍历旧表,将旧表的值一个个按照新表的规则插入新表中,最后将需要插入的值正常插入即可
// 扩容
size_t newsize = _tables.size() * 2;
vector<Node*> newtables(newsize, nullptr);
for (int i = 0; i < _tables.size(); ++i)
{
if (_tables[i])
{
cur = _tables[i];
while (cur)
{
Node* next = cur->_next; // 先保存下一个位置
size_t hashi = hs(kot(cur->_data)) % newsize;
if (newtables[hashi] == nullptr)
{
cur->_next = nullptr; // cur的next应该及时处理,不然容易出现死循环
newtables[hashi] = cur;
}
else
{
// 头插
cur->_next = newtables[hashi];
newtables[hashi] = cur;
}
cur = next;
}
_tables[i] = nullptr;
}
}
_tables.swap(newtables);
Insert(data);
注意,每当拿出来一个节点时,要将其_next置空或者赋值,否则会保留原表中的链接逻辑,导致后续错乱
删除操作:
删除相比插入起来就简单多了
只需要找到相应的节点,然后复原链接关系,最后delete即可:
两种情况:
1.删除头部
直接将_tables[hashi] 赋值给next然后delete掉头即可
2.删除非头部
直接prev->next = cur->next然后delete掉cur即可
bool Erase(const K& key)
{
Hash hs;
size_t hashi = hs(key) % _tables.size();
Node* prev = nullptr;
Node* cur = _tables[hashi];
while (cur)
{
if (kot(cur->_data) == key)
{
// 删头
if (prev == nullptr)
{
_tables[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
return true;
}
else
{
prev = cur;
cur = cur->_next;
}
}
return false;
}
查找操作:
找到头节点后遍历即可:
Iterator Find(const K& key)
{
Hash hs;
KeyOfT kot;
size_t hashi = hs(key) % _tables.size();
Node* cur = _tables[hashi];
while (cur)
{
if (kot(cur->_data) == key)
{
return Iterator(cur, this);
}
cur = cur->_next;
}
return Iterator(nullptr, this);
}
哈希桶的销毁:
// 哈希桶的销毁
~HashTable()
{
for (int i = 0; i < _tables.size(); ++i)
{
if (_tables[i])
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
}
_tables[i] = nullptr;
}
}
3)迭代器的实现
为了实现++,我们需要获取到_tables(属于哈希的底层结构),因此必须设置友元或者实现内部类
template<class K, class T, class KeyOfT, class Hash, class Ref, class Ptr>
struct __HTIterator
{
typedef __HTIterator<K, T, KeyOfT, Hash, Ref, Ptr> Self;
typedef HashNode<T> Node;
Node* _node;
HashTable<K, T, KeyOfT, Hash>* _pht;
KeyOfT kot;
Hash hs;
__HTIterator(Node* node, HashTable<K, T, KeyOfT, Hash>* pht)
:_node(node)
, _pht(pht)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const Self& it)
{
return _node != it._node;
}
};
构造时,本来只需要node节点的,但是由于需要_tables的原因,需要拿到一个HashTable的指针方便访问。
这里的迭代器中,难度稍微高点的依旧是++操作:
1.若该桶未走完,则next即可
2.若该桶走完,则寻找下一个头不为空的点返回其头部即可
3.若所有桶走完,返回nullptr即可
Self& operator++()
{
if (_node->_next)
{
// 1.当前桶没走完,找当前桶的下一个节点
_node = _node->_next;
}
else
{
// 2.当前桶走完了,找下一个不为空的桶的第一个节点
KeyOfT kot;
Hash hs;
size_t i = hs(kot(_node->_data)) % _pht->_tables.size();
++i;
for (; i < _pht->_tables.size(); i++)
{
if (_pht->_tables[i])
break;
}
if (i == _pht->_tables.size())
{
// 3.所有桶都走完了,最后一个的下一个用nullptr标记
_node = nullptr;
}
else
{
_node = _pht->_tables[i];
}
}
return *this;
}
逻辑理清楚了,实现起来不会太难
4)begin -- end
template<class K, class T, class KeyOfT, class Hash = HashFunc<K>>
class HashTable
{
typedef HashNode<T> Node;
// 友元声明
template<class K, class T, class KeyOfT, class Hash, class Ref, class Ptr>
friend struct __HTIterator;
public:
typedef __HTIterator<K, T, KeyOfT, Hash, T&, T*> Iterator;
typedef __HTIterator<K, const T, KeyOfT, Hash, const T&, const T*> Const_Iterator;
Iterator Begin()
{
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
if (cur)
{
return Iterator(cur, this);
}
}
return End();
}
Iterator End()
{
return Iterator(nullptr, this);
}
Const_Iterator Begin() const
{
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
if (cur)
{
return Const_Iterator(cur, this);
}
}
return End();
}
Const_Iterator End() const
{
return Const_Iterator(nullptr, this);
}
};
2.unordered_set的实现
template<class K>
class myunordered_set
{
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
typedef typename Hash_bucket::HashTable<K, const K, SetKeyOfT>::Iterator iterator;
typedef typename Hash_bucket::HashTable<K, const K, SetKeyOfT>::Const_Iterator const_iterator;
iterator begin()
{
return _ht.Begin();
}
iterator end()
{
return _ht.End();
}
const_iterator begin() const
{
return _ht.Begin();
}
const_iterator end() const
{
return _ht.End();
}
pair<iterator, bool> insert(const K& key)
{
return _ht.Insert(key);
}
iterator find(const K& key)
{
return _ht.Find(key);
}
bool erase(const K& key)
{
return _ht.Erase(key);
}
private:
Hash_bucket::HashTable<K, const K, SetKeyOfT> _ht;
};
3.unordered_map的实现
template<class K, class V>
class myunordered_map
{
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
public:
typedef typename Hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;
typedef typename Hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::Const_Iterator const_iterator;
public:
iterator begin()
{
return _ht.Begin();
}
iterator end()
{
return _ht.End();
}
const_iterator begin() const
{
return _ht.Begin();
}
const_iterator end() const
{
return _ht.End();
}
pair<iterator, bool> insert(const pair<K, V>& data)
{
return _ht.Insert(data);
}
bool erase(const K& key)
{
return _ht.Erase(key);
}
iterator find(const K& key)
{
return _ht.Find(key);
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert(make_pair(key, V()));
return (*(ret.first)).second;
}
private:
Hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT> _ht;
};
具体代码参考 ---- Hash