赞
踩
目录
之前的博客中我提到过,可以采用闭散列(二次探测、线性探测)的方法来解决哈希冲突,但是,无论是线性探测还是二次探测,其解决哈希冲突的本质方法都是去“占位”,即:发生哈希冲突的Key会去占用其他Key值原本应当占用的位置,这样一个冲突可能引发一串冲突。
为了解决闭散列方法的上述弊端,可以使用哈希桶(开散列)的方法来解决哈希冲突。在hashTable的每个位置都挂一个哈希桶,哈希桶用单链表来表示。将所有发生冲突的数据都挂在一个桶上。
此时,hashTable存储的数据类型时单链表节点,hashTable[i]是每个哈希桶的头结点,发生冲突时的数据插入相当于单链表的头插操作。
由于哈希桶的本质是挂在哈希表上的单链表,因此,哈希表节点的数据应当为单链表节点,包括:
在Hash类模板中,将hashDate<K, V>类型重定义为Node。
代码2.1:(哈希表节点)
- template<class K, class V>
- struct hashDate
- {
- std::pair<K, V> _kv;
- hashDate<K, V>* _next;
-
- hashDate(const std::pair<K, V>& kv)
- : _kv(kv)
- , _next(nullptr)
- { }
- };
函数Node* find(const K& key),找到了返回key所在的节点地址,找不到返回nullptr。
代码2.2:(find函数的实现)
- Node* find(const K& key)
- {
- //哈希表中没有数据就直接返回false
- //这里是为了避免模0(%0)错误
- if (_hashTable.size() == 0)
- {
- return nullptr;
- }
-
- HashKey hashKey;
- //通过哈希函数计算key所在哈希桶
- size_t hashi = hashKey(key) % _hashTable.size();
- //哈希桶单链表头结点
- Node* cur = _hashTable[hashi];
-
- //变量哈希桶(单链表),寻找key
- while (cur)
- {
- if (hashKey(cur->_kv.first) == hashKey(key))
- {
- return cur;
- }
-
- cur = cur->_next;
- }
-
- return nullptr;
- }
如果通过闭散列(线性探测或二次探测)的方法来解决哈希冲突,那么哈希表中存储的数据个数_size一定不能超过_table.size()。但是如果使用哈希桶,哈希表的每个位置挂的哈希桶可以有多个数据,因此负载因子是可以大于1的。
闭散列一般当负载因子大于0.7~0.8是扩容,而哈希桶则应适度提高。可以认为当插入数据后负载因子大于1就扩容。
哈希桶扩容同样需要改变原来的结构,因为不同的Key值对于的存储位置会发生改变。这里不再复用insert函数来实现数据位置的改变,而是依次遍历每个哈希桶的每个节点,创建一个新的vector作为hashTable,通过单链表头插,来使数据满足扩容后的哈希结构要求。
代码2.3:(哈希表扩容)
- //扩容(负载因子大于1)
- if (_hashTable.size() == 0 || _size == _hashTable.size())
- {
- size_t newSize = _hashTable.size() == 0 ? 10 : 2 * _hashTable.size();
-
- //将原来哈希表数据的位置进行相应改变
- //这里使用一个新的vector来实现
- std::vector<Node*> newTable;
- newTable.resize(newSize, nullptr);
-
- //将原来table每个位置处挂的单链表,挪到新的table对应位置
- for (size_t i = 0; i < _hashTable.size(); ++i)
- {
- Node* cur = _hashTable[i];
-
- while (cur)
- {
- Node* next = cur->_next;
- size_t newHashi = hashKey(cur->_kv.first) % newSize;
-
- //执行头插操作
- cur->_next = newTable[newHashi];
- newTable[newHashi] = cur;
-
- cur = next;
- }
-
- _hashTable[i] = nullptr;
- }
-
- _hashTable.swap(newTable);
- }
哈希桶是挂在hashTable指定位置处的单链表。进行数据插入时,只需在检查数据是否已经存在、是否需要扩容之后,要通过哈希函数Hash(Key)找到对应的位置,然后执行单链表头插操作即可。
通过哈希函数获得哈希桶的位置后,遍历单链表(哈希桶)找到key所在的位置,执行单链表节点删除操作即可。
代码2.5:(数据删除)
- bool erase(const K& key)
- {
- if (_hashTable.size() == 0)
- {
- return false;
- }
-
- HashKey hashKey;
-
- size_t hashi = hashKey(key) % _hashTable.size();
- Node* prev = nullptr;
- Node* cur = _hashTable[hashi];
-
- while (cur)
- {
- //找到key了,删除
- if (hashKey(cur->_kv.first) == key)
- {
- //头删
- if (prev == nullptr)
- {
- _hashTable[hashi] = cur->_next;
- free(cur);
- cur = nullptr;
- }
- else //中间删或尾删
- {
- prev->_next = cur->_next;
- free(cur);
- cur = nullptr;
- }
-
- --_size;
- return true;
- }
-
- prev = cur;
- cur = cur->_next;
- }
-
- return false;
- }
自己编写代码,依次对每个哈希桶进行释放,每个哈希桶的析构等同于单链表的析构。由于vector为自定义类型数据,编译的会主动调用vector的析构函数释放hashTable的空间。
代码2.6:(析构函数)
- ~Hash() //析构函数
- {
- //释放每个节点上挂的数据
- for (size_t i = 0; i < _hashTable.size(); ++i)
- {
- Node* cur = _hashTable[i];
-
- //释放每个非空节点
- while (cur)
- {
- Node* next = cur->_next;
- delete cur;
- cur = next;
- }
-
- _hashTable[i] = nullptr;
- }
- }
- #include<vector>
-
- template<class K, class V>
- struct hashDate
- {
- std::pair<K, V> _kv;
- hashDate<K, V>* _next;
-
- hashDate(const std::pair<K, V>& kv)
- : _kv(kv)
- , _next(nullptr)
- { }
- };
-
- template<class K>
- struct HashKeyVal
- {
- size_t operator()(const K& key)
- {
- return (size_t)key;
- }
- };
-
- template<class K, class V, class HashKey = HashKeyVal<K>>
- class Hash
- {
- typedef hashDate<K, V> Node;
-
- public:
- ~Hash() //析构函数
- {
- //释放每个节点上挂的数据
- for (size_t i = 0; i < _hashTable.size(); ++i)
- {
- Node* cur = _hashTable[i];
-
- //释放每个非空节点
- while (cur)
- {
- Node* next = cur->_next;
- delete cur;
- cur = next;
- }
-
- _hashTable[i] = nullptr;
- }
- }
-
- bool insert(const std::pair<K, V>& kv)
- {
- //查找 -- 确保不插入重复数据
- if (find(kv.first))
- {
- return false;
- }
-
- HashKey hashKey;
-
- //扩容(负载因子大于1)
- if (_hashTable.size() == 0 || _size == _hashTable.size())
- {
- size_t newSize = _hashTable.size() == 0 ? 10 : 2 * _hashTable.size();
-
- //将原来哈希表数据的位置进行相应改变
- //这里使用一个新的vector来实现
- std::vector<Node*> newTable;
- newTable.resize(newSize, nullptr);
-
- //将原来table每个位置处挂的单链表,挪到新的table对应位置
- for (size_t i = 0; i < _hashTable.size(); ++i)
- {
- Node* cur = _hashTable[i];
-
- while (cur)
- {
- Node* next = cur->_next;
- size_t newHashi = hashKey(cur->_kv.first) % newSize;
-
- //执行头插操作
- cur->_next = newTable[newHashi];
- newTable[newHashi] = cur;
-
- cur = next;
- }
-
- _hashTable[i] = nullptr;
- }
-
- _hashTable.swap(newTable);
- }
-
- //获取插入位置
- size_t hashi = hashKey(kv.first) % _hashTable.size();
- Node* node = new Node(kv);
-
- //插入数据
- node->_next = _hashTable[hashi];
- _hashTable[hashi] = node;
-
- ++_size;
- }
-
- bool erase(const K& key)
- {
- if (_hashTable.size() == 0)
- {
- return false;
- }
-
- HashKey hashKey;
-
- size_t hashi = hashKey(key) % _hashTable.size();
- Node* prev = nullptr;
- Node* cur = _hashTable[hashi];
-
- while (cur)
- {
- //找到key了,删除
- if (hashKey(cur->_kv.first) == key)
- {
- //头删
- if (prev == nullptr)
- {
- _hashTable[hashi] = cur->_next;
- free(cur);
- cur = nullptr;
- }
- else //中间删或尾删
- {
- prev->_next = cur->_next;
- free(cur);
- cur = nullptr;
- }
-
- --_size;
- return true;
- }
-
- prev = cur;
- cur = cur->_next;
- }
-
- return false;
- }
-
- Node* find(const K& key)
- {
- //哈希表中没有数据就直接返回false
- //这里是为了避免模0(%0)错误
- if (_hashTable.size() == 0)
- {
- return nullptr;
- }
-
- HashKey hashKey;
- //通过哈希函数计算key所在哈希桶
- size_t hashi = hashKey(key) % _hashTable.size();
- //哈希桶单链表头结点
- Node* cur = _hashTable[hashi];
-
- //变量哈希桶(单链表),寻找key
- while (cur)
- {
- if (hashKey(cur->_kv.first) == hashKey(key))
- {
- return cur;
- }
-
- cur = cur->_next;
- }
-
- return nullptr;
- }
-
- private:
- size_t _size = 0; //哈希表中数据个数
- std::vector<Node*> _hashTable; //哈希表(存储桶节点)
- };
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。