赞
踩
目录
上一篇文章讲的哈希表,属于闭散列。可以解决哈希冲突有两种方式:闭散列和开散列。现在要学习的哈希桶就是开散列。
闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有
空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地
址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链
接起来,各链表的头结点存储在哈希表中。
下面则是即将学习的哈希桶的简易图:
类似于一个数组中存了若干个链表头,每个头所代表的链表成为一个桶。
在上一篇博客的最后:哈希表HashTable-CSDN博客
探讨过当 Key 为负数、浮点数、字符串...时,类函数逻辑中有关 Key 取模的问题,当 Key 为负数、浮点数、字符、字符串时,显然这几个内置类型无法完成取模的操作,这时就用到了仿函数,这里不再多说,直接来看仿函数的代码,下面会直接使用仿函数来完成 HashBucket
- template<class T>
- class HashFunc<>
- {
- size_t operator()(const T& Key)
- {
- return (size_t)Key;
- }
- }
- template<>
- class HashFunc<string>
- {
- size_t operator()(const string& Key)
- {
- size_t hash = 0;
- for (auto ch : Key)
- {
- hash *= 131;
- hash += ch;
- }
- return hash;
- }
- }

由上图就可以看出来,每个结点必要存一个 pair 和一个指向下一个节点的指针 _next。
- template<class K, class V>
- struct HashNode
- {
- pair<K, V> _kv;
- HashNode* _next;
- }
哈希桶类的构成和哈希表类似,都是一个由一个 vector 存放每个节点,但是这里与 HashTable 不同的是需要存放的是节点的指针。还有一个 _n 代表有效数据的个数:
- template<class K, class V, class Hash = HashFunc<K>>
- class HashBucket
- {
- typedef HashNode Node;
- private:
- vector<Node*> _bucket;
- size_t _n;
- };
下面要介绍的是不需要扩容时的插入逻辑:
此时只需要使用 Key 模数组的大小来计算出该节点需要连接在 vector 上的位置,然后使用 new 得到储存 kv 的新节点,当 new 一个新节点时,节点的构造函数必不可少,下面先来看一下单个节点的构造函数以及类的构造函数:
- template<class K, class V>
- struct HashNode
- {
- pair<K, V> _kv;
- HashNode* _next;
- HashNode(const pair<K, V>& kv):_kv(kv), _next(nullptr)
- {}
- };
- template<class K, class V, class Hash = HashFunc<K>>
- class HashBucket
- {
- typedef HashNode<K, V> Node;
- HashBucket()
- {
- _bucket.resize(10, nullptr);
- _n = 0;
- }
- private:
- vector<Node*> _bucket;
- size_t _n;
- }

此时需要思考的是,既然 vector 每个节点都要存放一个链表,那么链表头插还是尾插的效率更高呢?
显然是头插,所以这个新结点就需要以类似头插的方式添加到 vector 的这个位置上,
- bool Insert(const pair<K, V>& kv)
- {
- Hash hs;
- size_t index = hs(kv.first) % _bucket.size();
- Node* newnode = new Node(kv);
- newnode->_next = _bucket[index];
- _buckte[index] = newnode;
- ++_n;
- return true;
- }
这里的载荷因子可以直接设为1,至于载荷因子是什么,可以查看上一篇博客哈希表HashTable-CSDN博客,在扩容中的何时扩容标题下,介绍了载荷因子的概念。
在扩容中,既可以使用 HashTable 中类似的写法直接复用 Insert ,也可以直接挨个让节点插入,下面先介绍每种方式,再进行优缺点的处理:
- bool Insert(const pair<K, V>& kv)
- {
- Hash hs;
- if (_n == _bucket.size())
- {
- HashBucket newHB = new HashBucket;
- newHB._bucket.resize(_bucket.size() * 2, nullptr);
- for (size_t i = 0; i < _bucket.size(); i++)
- {
- Node* cur = _bucket[i];
- while(cur)
- {
- Node* next = cur->_next; // 保存下一个节点指针
- newHB.Insert(cur->_kv); // 插入当前节点的键值对到新哈希表
- cur = next; // 移动到下一个节点
- }
- _bucket[i] = nullptr;
- }
- _bucket.swap(newHB);
- }
- }

- bool Insert(const pair<K, V>& kv)
- {
- Hash hs;
- if (_n == _bucket.size())
- {
- vector<Node*> newBucket(_bucket.size() * 2, nullptr);
- for (size_t i = 0; i < _bucket.size(); i++)
- {
- Node* cur = _bucket[i];
- while (cur)
- {
- Node* next = cur->_next;
- size_t index = hs(cur->_kv.first) % newBucket.size();
- cur->_next = newBucket[index];
- newBucket[index] = cur;
- cur = next;
- }
- _bucket[i] = nullptr;
- }
- _bucket.swap(newBucket);
- }
- }

newHB.Insert(cur->_kv)
来重新插入节点,重用了 Insert
方法,减少了代码重复。Insert
,可能会多次计算哈希值和处理冲突,性能可能稍差。Insert
方法,减少了函数调用和重复计算,提高了性能。- bool Insert(const pair<K, V>& kv)
- {
- if (Find(kv.first)) return false;
- Hash hs;
- if (_n == _bucket.size())
- {
- HashBucket newHB;
- newHB._bucket.resize(_bucket.size() * 2, nullptr);
- for (size_t i = 0; i < _bucket.size(); i++)
- {
- Node* cur = _bucket[i];
- while (cur)
- {
- Node* next = cur->_next; // 保存下一个节点指针
- newHB.Insert(cur->_kv); // 插入当前节点的键值对到新哈希表
- cur = next; // 移动到下一个节点
- }
- _bucket[i] = nullptr;
- }
- _bucket.swap(newHB._bucket);
- }
- size_t index = hs(kv.first) % _bucket.size();
- Node* newnode = new Node(kv);
- newnode->_next = _bucket[index];
- _bucket[index] = newnode;
- ++_n;
- return true;
- }
- bool Insert(const pair<K, V>& kv)
- {
- if (Find(kv.first)) return false;
- Hash hs;
- if (_n == _bucket.size())
- {
- vector<Node*> newBucket(_bucket.size() * 2, nullptr);
- for (size_t i = 0; i < _bucket.size(); i++)
- {
- Node* cur = _bucket[i];
- while (cur)
- {
- Node* next = cur->_next;
-
- size_t index = hs(cur->_kv.first) % newBucket.size();
- cur->_next = newBucket[index];
- newBucket[index] = cur;
-
- cur = next;
- }
- _bucket[i] = nullptr;
- }
- _bucket.swap(newBucket);
- }
- size_t index = hs(kv.first) % _bucket.size();
- Node* newnode = new Node(kv);
- newnode->_next = _bucket[index];
- _bucket[index] = newnode;
- ++_n;
- return true;
- }

根据 Insert 函数中,可以得知, HashBucket 的每个节点都是 new 出来的,那删除的时候就要使用 delete ,又因为每个节点都是自定义类型,所以要为 HashBucket 写一个析构函数。
对类的析构就是遍历 vector 的每个节点,再从每个节点遍历每个链表,以此遍历全部节点。
- ~HashBucket()
- {
- for (size_t i = 0; i < _bucket.size(); i++)
- {
- Node* cur = _bucket[i];
- while (cur)
- {
- Node* next = cur->_next;
- delete cur;
- cur = next;
- }
- _bucket[i] = nullptr;
- }
- }
下面介绍一下Erase函数的步骤:
计算哈希值:使用哈希函数 hs
计算给定键 Key
的哈希值,并确定它在桶中的索引 index
。
遍历链表:从索引 index
开始,遍历链表中的每个节点。
查找节点:检查当前节点的键是否等于 Key
。
_bucket[index]
指向下一个节点。_next
指针指向当前节点的下一个节点。cur
,释放内存。true
,表示删除成功。prev
和 cur
。返回结果:如果遍历完整个链表未找到匹配节点,返回 false
,表示删除失败。
- bool Erase(const K& Key)
- {
- Hash hs;
- size_t index = hs(Key) % _bucket.size();
- Node* cur = _bucket[index];
- Node* prev = nullptr;
- while (cur)
- {
- if (cur->_kv.first == Key)
- {
- //删除的是第一个节点
- if (prev == nullptr)
- {
- _bucket[index] = cur->_next;
- }
- else
- {
- prev->_next = cur->_next;
- }
- delete cur;
- return true;
- }
- else
- {
- prev = cur;
- cur = cur->_next;
- }
- }
- return false;
- }

这里的 Find 函数与 HashTable 中的 Find 函数逻辑略有不同,因为这里如果发送哈希冲突,那么存储的位置还是 vector 中取模后的位置,不需要像 HashTable 那样进行线性探测,只需要取一个指针挨个遍历位于 _bucket[index] 链表上的节点即可。
- Node* Find(const K& Key)
- {
- if (_bucket.empty()) return nullptr;
- Hash hs;
- size_t index = hs(Key) % _bucket.size();
- Node* cur = _bucket[index];
- while (cur)
- {
- if (cur->_kv.first == Key)
- return cur;
- else cur = cur->_next;
- }
- return nullptr;
- }
写完 Find 后,还可以进一步改进 Insert 函数,在插入时可以先进行查找,如果存在,那就插入失败;如果不存在,再继续插入。这样避免了哈希桶中数据冗余的结果。
- #pragma once
- #include <iostream>
- #include <vector>
- #include <string>
-
- using namespace std;
-
- template<class K>
- struct HashFunc
- {
- size_t operator()(const K& Key)
- {
- return (size_t)Key;
- }
- };
-
- template<>
- struct HashFunc<string>
- {
- size_t operator()(const string& Key)
- {
- size_t hash = 0;
- for (auto ch : Key)
- {
- hash *= 131;
- hash += ch;
- }
- return hash;
- }
- };
-
- template<class K, class V>
- struct HashNode
- {
- pair<K, V> _kv;
- HashNode* _next;
- HashNode(const pair<K, V>& kv):_kv(kv), _next(nullptr)
- {}
- };
-
- template<class K, class V, class Hash = HashFunc<K>>
- class HashBucket
- {
- typedef HashNode<K, V> Node;
- public:
- HashBucket()
- {
- _bucket.resize(10, nullptr);
- _n = 0;
- }
- ~HashBucket()
- {
- for (size_t i = 0; i < _bucket.size(); i++)
- {
- Node* cur = _bucket[i];
- while (cur)
- {
- Node* next = cur->_next;
- delete cur;
- cur = next;
- }
- _bucket[i] = nullptr;
- }
- }
- bool Insert(const pair<K, V>& kv)
- {
- if (Find(kv.first)) return false;
- Hash hs;
- if (_n == _bucket.size())
- {
- vector<Node*> newBucket(_bucket.size() * 2, nullptr);
- for (size_t i = 0; i < _bucket.size(); i++)
- {
- Node* cur = _bucket[i];
- while (cur)
- {
- Node* next = cur->_next;
-
- size_t index = hs(cur->_kv.first) % newBucket.size();
- cur->_next = newBucket[index];
- newBucket[index] = cur;
-
- cur = next;
- }
- _bucket[i] = nullptr;
- }
- _bucket.swap(newBucket);
- }
- size_t index = hs(kv.first) % _bucket.size();
- Node* newnode = new Node(kv);
- newnode->_next = _bucket[index];
- _bucket[index] = newnode;
- ++_n;
- return true;
- }
- bool Insert(const pair<K, V>& kv)
- {
- if (Find(kv.first)) return false;
- Hash hs;
- if (_n == _bucket.size())
- {
- HashBucket newHB;
- newHB._bucket.resize(_bucket.size() * 2, nullptr);
- for (size_t i = 0; i < _bucket.size(); i++)
- {
- Node* cur = _bucket[i];
- while (cur)
- {
- Node* next = cur->_next; // 保存下一个节点指针
- newHB.Insert(cur->_kv); // 插入当前节点的键值对到新哈希表
- cur = next; // 移动到下一个节点
- }
- _bucket[i] = nullptr;
- }
- _bucket.swap(newHB._bucket);
- }
- size_t index = hs(kv.first) % _bucket.size();
- Node* newnode = new Node(kv);
- newnode->_next = _bucket[index];
- _bucket[index] = newnode;
- ++_n;
- return true;
- }
- bool Erase(const K& Key)
- {
- Hash hs;
- size_t index = hs(Key) % _bucket.size();
- Node* cur = _bucket[index];
- Node* prev = nullptr;
- while (cur)
- {
- if (cur->_kv.first == Key)
- {
- //删除的是第一个节点
- if (prev == nullptr)
- {
- _bucket[index] = cur->_next;
- }
- else
- {
- prev->_next = cur->_next;
- }
- delete cur;
- return true;
- }
- else
- {
- prev = cur;
- cur = cur->_next;
- }
- }
- return false;
- }
- Node* Find(const K& Key)
- {
- if (_bucket.empty()) return nullptr;
- Hash hs;
- size_t index = hs(Key) % _bucket.size();
- Node* cur = _bucket[index];
- while (cur)
- {
- if (cur->_kv.first == Key)
- return cur;
- else cur = cur->_next;
- }
- return nullptr;
- }
- private:
- vector<Node*> _bucket;
- size_t _n;
- };
- void TestHB1()
- {
- int ret[] = {5, 15, 3, 12, 13, 31};
- HashBucket<int, int> hb;
-
- for (auto e : ret)
- {
- hb.Insert(make_pair(e, e));
- }
- cout << hb.Erase(0) << endl;
- cout << hb.Erase(5) << endl;
- }
- void TestHB2()
- {
- HashBucket<string, int> hb;
- hb.Insert(make_pair("sort", 1));
- hb.Insert(make_pair("left", 1));
- hb.Insert(make_pair("insert", 1));
-
- }

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。