文章目录

  • 0. 前言
  • 1. 开散列
    • 1.1 开散列概念
  • 2. 开散列的代码实现
    • 2.0 定义
    • 2.1 插入实现--Insert
    • 2.2 查找实现--Find
    • 2.3 删除实现--Erase
    • 2.4 仿函数
  • 3. 完整代码实现
  • 4. 代码测试并运行结果:

0. 前言

  • 我们上一章我们了解了哈希表闭散列线性探测实现方法
    链接:哈希表闭散列线性探测实现
  • 这一章我们谈谈哈希表开散列实现。

1. 开散列

1.1 开散列概念

  • 开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。

    表中每个位置的元素像桶一样挂起来。
  • 从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素。
  • 上一章我们了解了哈希表闭散列线性探测冲突时往后找空位置填写,这样就会导致空间利用率比较低

2. 开散列的代码实现

2.0 定义

  • 一张表中需要桶挂起来,我们就需要节点。
 template<class K, class V>struct HashNode{pair<K, V> _kv;HashNode<K, V>* _next;HashNode(const pair<K, V>& kv):_kv(kv),_next(nullptr){}};

2.1 插入实现–Insert

  • 插入的时候我们选择头插。

  • 原因:

    • 我们采用的是单链表头插效率高。
  • 扩容方法:

    • 这一次我们不能像哈希表闭散列线性探测复用的方尺扩容,可能里面一些节点后来就不冲突了,所以我们手动扩容;不过这些节点是可以再次利用的~。
    • 桶的个数是一定的,随着元素的不断插入,每个桶中元素的个数不断增多,极端情况下,可能会导致一个桶中链表节点非常多,会影响的哈希表的性能 。扩容条件是负载因子到达也就是元素个数刚好等于桶的个数时,可以给哈希表增容。

具体实现代码如下:

     bool Insert(const pair<K, V>& kv){//去重if (Find(kv.first))//查找后面有哈~{return false;}//负载因子到了就扩容if (_tables.size() == _size){size_t newSize = _tables.size() == 0 ? 10 : 2 * _tables.size();vector<Node*> newTables;newTables.resize(newSize, 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 % newTables.size();//头插cur->_next = newTables[hashi];newTables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newTables);}size_t hashi = kv.first % _tables.size();//头插Node* newNode = new Node(kv);newNode->_next = _tables[hashi];_tables[hashi] = newNode;++_size;return true;}
  • 这个时候uu们就会疑惑,为什么小丁实现的扩容跟库里面的思路不一样?库里面以素数作为扩容的大小会提高效率(大佬们研究表明的)
  • 我们一起看看库里面怎么实现的~

    没错使用一个数组把类似二倍扩容的数都包括了;注意不会超出这个范围,超出就溢出来(算了一下走到最后光开空间就浪费了32G这怎么可能?)

加上后的代码如下:

inline size_t __stl_next_prime(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 (size_t i = 0; i < __stl_num_primes; ++i){if (__stl_prime_list[i] > n){return __stl_prime_list[i];}}return -1;}bool Insert(const pair<K, V>& kv){//去重if (Find(kv.first)){return false;}//负载因子到了就扩容if (_tables.size() == _size){//size_t newSize = _tables.size() == 0 ? 10 : 2 * _tables.size();vector<Node*> newTables;//newTables.resize(newSize, nullptr);newTables.resize(__stl_next_prime(_tables.size()), 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 % newTables.size();//头插cur->_next = newTables[hashi];newTables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newTables);}size_t hashi = kv.first % _tables.size();//头插Node* newNode = new Node(kv);newNode->_next = _tables[hashi];_tables[hashi] = newNode;++_size;return true;}

2.2 查找实现–Find

  • 查找思路:通过取模方式找到数值的映射位置(size_t hashi = key % _tables.size();)进行查询即可。

具体实现代码如下:

     Node* Find(const K& key){if (_tables.size() == 0){return nullptr;}size_t hashi = key % _tables.size();Node* cur = _tables[hashi];while (cur){if (cur->_kv.first == key){//找到了return cur;}cur = cur->_next;}//未找到return nullptr;}

2.3 删除实现–Erase

  • 删除思路:我们需要prev来记录前一个节点(我们这是单链表)
  • 删除情况分为头删和中间删(cur:当前要删除的节点)
    • 头删:我们只需把头节点换一下就行了再把cur这个节点释放掉。
    • 中间删:我们需要prev指向下一个后再把cur这个节点释放掉。

具体实现代码如下:

     bool Erase(const K& key){if (_tables.size() == 0){return false;}int hashi = key % _tables.size();Node* cur = _tables[hashi];Node* prev = nullptr;while (cur){if (key == cur->_kv.first){if (prev)   //中间删{prev->_next = cur->_next;}else      //头删{_tables[hashi] = cur->_next;}delete cur;--_size;return true;}prev = cur;cur = cur->_next;}//未找到return false;}

2.4 仿函数

  • 我们发现我们现在实现的哈希只能存数字,字符串等不行;这个时候我们需要借助仿函数。

  • 代码实现思路跟上一章哈希表闭散列线性探测实现的仿函数一样。

不多说了,来看代码吧!

具体实现代码如下:

template<class k>
struct HashFunc
{size_t operator()(const k& key){return (size_t)key;}
};
//特化--string
template<>
struct HashFunc<string>
{size_t operator()(const string& s){size_t val = 0;for (const auto ch : s) //迭代器{val *= 131;val += ch;}return val;}
};

3. 完整代码实现

namespace HashBucket
{template<class k>struct HashFunc{size_t operator()(const k& key){return (size_t)key;}};//特化--stringtemplate<>struct HashFunc<string>{size_t operator()(const string& s){size_t val = 0;for (const auto ch : s)   //迭代器{val *= 131;val += ch;}return val;}};template<class K, class V>struct HashNode{pair<K, V> _kv;HashNode<K, V>* _next;HashNode(const pair<K, V>& kv):_kv(kv),_next(nullptr){}};template<class K, class V, class Hash = HashFunc<K>>class HashTable{typedef HashNode<K, V> Node;public:~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;}}inline size_t __stl_next_prime(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 (size_t i = 0; i < __stl_num_primes; ++i){if (__stl_prime_list[i] > n){return __stl_prime_list[i];}}return -1;}bool Insert(const pair<K, V>& kv){//去重if (Find(kv.first)){return false;}Hash hash;//负载因子到了就扩容if (_tables.size() == _size){//size_t newSize = _tables.size() == 0 ? 10 : 2 * _tables.size();vector<Node*> newTables;//newTables.resize(newSize, nullptr);newTables.resize(__stl_next_prime(_tables.size()), nullptr);// 旧表中节点移动映射新表for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[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;}Node* Find(const K& key){if (Empty()){return nullptr;}Hash hash;size_t hashi = hash(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (cur->_kv.first == key){//找到了return cur;}cur = cur->_next;}//未找到return nullptr;}bool Empty() const{return _size == 0;}bool Erase(const K& key){if (Empty()){return false;}Hash hash;int hashi = hash(key) % _tables.size();Node* cur = _tables[hashi];Node* prev = nullptr;while (cur){if (key == cur->_kv.first){if (prev)   //中间删{prev->_next = cur->_next;}else      //头删{_tables[hashi] = cur->_next;}delete cur;--_size;return true;}prev = cur;cur = cur->_next;}//未找到return false;}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;}//最长桶的链长size_t MaxBucketLenth(){size_t maxLen = 0;for (size_t i = 0; i < _tables.size(); ++i){size_t len = 0;Node* cur = _tables[i];while (cur){++len;cur = cur->_next;}//if (len > 0)//printf("[%d]号桶长度:%d\n", i, len);if (len > maxLen){maxLen = len;}}return maxLen;}private:vector<Node*> _tables;size_t _size = 0;       // 存储有效数据个数};void TestHT1(){int a[] = { 1, 11, 4, 15, 26, 7, 44,55,99,78, 4 };HashTable<int, int> ht;for (auto e : a){ht.Insert(make_pair(e, e));}ht.Insert(make_pair(22, 22));}void TestHT2(){string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };//HashTable<string, int, HashFuncString> countHT;HashTable<string, int> countHT;for (auto& str : arr){auto ptr = countHT.Find(str);if (ptr){ptr->_kv.second++;}else{countHT.Insert(make_pair(str, 1));}}}void TestHT3(){int n = 19000000;vector<int> v;v.reserve(n);srand(time(0));for (int i = 0; i < n; ++i){//v.push_back(i);v.push_back(rand() + i);  // 重复少//v.push_back(rand());  // 重复多}size_t begin1 = clock();HashTable<int, int> ht;for (auto e : v){ht.Insert(make_pair(e, e));}size_t end1 = clock();cout << "数据个数:" << ht.Size() << endl;cout << "表的长度:" << ht.TablesSize() << endl;cout << "桶的个数:" << ht.BucketNum() << endl;cout << "平均每个桶的长度:" << (double)ht.Size() / (double)ht.BucketNum() << endl;cout << "最长的桶的长度:" << ht.MaxBucketLenth() << endl;cout << "负载因子:" << (double)ht.Size() / (double)ht.TablesSize() << endl;}}

4. 代码测试并运行结果:

c++哈希(哈希表开散列实现)相关推荐

  1. 哈希(Hash) - 开散列/闭散列

    文章目录: 认识哈希 哈希函数 处理冲突的方法 闭散列(开放定址法) 开散列(链地址法) 哈希表闭散列实现 闭散列基本框架 哈希表闭散列插入(insert) 哈希表闭散列删除(erase) 哈希表闭散 ...

  2. C++泛型编程实现哈希表(开散列法)

    代码如下: #include <iostream> #include <vector> using namespace std;template<typename K&g ...

  3. 哈希之开散列,闭散列

    先从数据查找开始说起吧,在线性结构,树形结构当中查找一个元素必须经过多次和一些元素进行比较,然后通过比较,查找到对应元素,这种方法多多少少,时间复杂度都是比较高的. 有没有一种方法时间复杂度,仅仅O( ...

  4. C++:哈希(闭散列、开散列)

    文章目录 哈希概念 哈希冲突 哈希函数 哈希冲突解决 闭散列 什么时机增容,如何增容? 线性探测的实现 开散列 开散列增容 开散列的实现 开散列与闭散列比较 unordered_map模拟实现(应用开 ...

  5. 【C++哈希桶(开散列)】

    1. 开散列(哈希桶)概念 开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各 ...

  6. C++---哈希闭散列与开散列

    产生原因 在顺序结构或树形结构的数据集合中,我们想要查询一个元素时,必须进行遍历,所以顺序结构的查询时间复杂度为O(N),树形结构查询的时间复杂度为O(log2^N),但是我们想要一种不用遍历就知道其 ...

  7. 【C++】哈希——unordered系列容器|哈希冲突|闭散列|开散列

    文章目录 一.unordered系列关联式容器 二.哈希概念 三.哈希冲突 四.哈希函数 五.解决哈希冲突 1.闭散列--开放定址法 2.代码实现 3.开散列--开链法 4.代码实现 六.结语 一.u ...

  8. 哈希 ---《哈希函数》------除数的选取为什么是质数?、《哈希冲突》------解决方法、《闭散列》、《开散列》

    一.哈希概念 顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较**.顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(logN ) ...

  9. 哈希(散列):(四)C语言实现 哈希 开散列法

    哈希(散列)的概念: https://blog.csdn.net/mowen_mowen/article/details/82943192 C语言实现:静态哈希表: https://blog.csdn ...

最新文章

  1. linux使用free命令查看内存占用
  2. kdump和crash的配置方法与内核故障原因分析(一)
  3. GreyBox和ThickBox
  4. python中的编码问题 1
  5. ELK收集docker日志
  6. mc1.8.1怎么局域网java_同一台电脑同时装jdk1.8和jdk1.7
  7. R语言中的read.table()
  8. html颜色(背景,字体等)
  9. Git学习-本地版本库的创建与简单操作
  10. 我用php构建了魔兽世界服务器,只为证明php是世界上最好的语言
  11. 内存取证-volatility工具的使用 (史上更全教程,更全命令)
  12. Git使用的奇技淫巧,看这篇就够了!
  13. 网络流行语2016_“云”作为流行语
  14. JS中设计模式的深入理解
  15. 友盟 集成到 java web_友盟消息推送SDK集成
  16. 分支-12. 计算火车执行时间(15)
  17. CAN FD、CANDTU、CAN记录仪实战之示波器捕捉波形及对照CAN FD帧定义解析CAN FD帧
  18. 力扣算法学习(十四)
  19. 一种适用于单片机的低功耗软件设计
  20. C盘快满了,如何删除不需要的文件?

热门文章

  1. pc端和移动端两套样式在vue中的切换
  2. ROS2-Gazebo仿真
  3. php下载安装方法,phpstudy 2016免费版-php开发环境下载 v2016.11.03 附带安装教程 - 安下载...
  4. (Python)sum函数的用法
  5. 2018上半年最有价值科技类的微信公众号有哪些?
  6. 腾讯云Cannot parse privatekey: unsupported key format问题解决
  7. python 使用API并将获取到的数据可视化的基本方法(详细)
  8. 以TSPITR方式恢复表空间数据一例
  9. HTML注册登录页面模板,左右切换,用户注册、用户登录两种功能。
  10. 用Python实现斗地主游戏(终端版)