前言
接着上一篇文章,我们知道了闭散列的弊端是空间利用率比较低,希望今天学习的开散列可以帮我们解决这个问题
引入
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址**,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。**
实现
基本结构
template<class K, class V>
struct HashNode
{HashNode<K, V>* _next;pair<K, V> _kv;
};
template<class K,class V>
class HashTable
{using Node = HashNode<K, V>;public://构造HashTable(size_t size = 10){_tables.resize(size, nullptr);_n = 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;}}private:vector<Node*> _tables;size_t _n = 0;
}
插入
这就是头插
bool Insert(const pair<K, V>& kv){size_t hashi = kv.first % _tables.size(); //找到关键码Node* newnode = new Node(kv); //创建新节点newnode->_next = _tables[hashi]; //先链接上_tables[hashi] = nuwnode; //再更新头节点}
扩容
(可以参考set和map的扩容,都使用了提供的swap函数)
if (_n == _tables.size()){vector<Node*> newTable(_tables.size() * 2, nullptr);for (size_t i = 0; i < _tables.size(); i++){//遍历旧表,重新计算Node* cur = _tables[i];while (cur){Node* next = cur->_next;size_t hashi = cur->_kv, first% newTable.size();cur->_next = newTable[hashi];newTable[hashi] = cur;cur = next;}tables[i] = nullptr;}_tables.swap(newTables);}//扩容机制
删除
删除,删的是_key是key的这个桶,
返回值:
可以是bool也可以是下一个桶的指针
bool Erase(const K& key){Hash hs; //取出key的value值,//可以是仿函数、lambda,看传入的是什么size_t hashi = hs(key) % _tables.size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur){if (cur->_kv.first == key)//满足条件{// 删除if (prev){prev->_next = cur->_next;}else{_tables[hashi] = cur->_next;}delete cur;--_n;//桶个数--return true;}prev = cur;cur = cur->_next;}return false;}
对各项的检查函数
void Some(){size_t bucketSize = 0;size_t maxBucketLen = 0;size_t sum = 0;double averageBucketLen = 0;for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){++bucketSize;}size_t bucketLen = 0;while (cur){++bucketLen;cur = cur->_next;}sum += bucketLen;if (bucketLen > maxBucketLen){maxBucketLen = bucketLen;}}averageBucketLen = (double)sum / (double)bucketSize;printf("load factor:%lf\n", (double)_n/ _tables.size());//负载因子printf("all bucketSize:%d\n", _tables.size());//桶的个数printf("bucketSize:%d\n", bucketSize);printf("maxBucketLen:%d\n", maxBucketLen);//桶的最大值printf("averageBucketLen:%lf\n\n", averageBucketLen);//桶的平均大小}
补充
当某个桶数据过多时,我们可以把这个桶指向的节点变成红黑树,但什么时候变目前不去考虑
结语
文章写到这里有点戛然而止的感觉,不过本文主要是为了介绍概念、只实现了插入和删除。