当前位置: 首页 > web >正文

哈希表与unorder_set,unorder_map的学习

哈希(hash),是一种理论思想,与各种各样的树一样,其是一种数据组织的方式。依据我的理解,哈希的本质就是映射,将一组数据根据某种规则(哈希函数)映射到一个有序可查的表中。这个表里存放size_t(unsigned int)类型的数据,用于直接访问。因此,哈希表的增删查改的时间效率为O(1),效率很高。但是其也有缺点,就是其得出的结果不会有序,因此哈希的本意为散列,就是散乱的数列。

概念

上面已经提到,哈希的本质是将数据转换为size_t类型的数据来存放,具体来说比如一题经典的例题:我们有一句string,然后想要找到第二次出现的字符,我们就可以将a~z映射到0~26,放入一个26大小的int类型数组,每当出现一个字母,我们就进行一次对应数组的++. 当第一个为2大小的数组单元出现时,这个单元对应的字符急速出现了第一个第二次的字符。这就是最简单的哈希思想:直接定址法。但是,在实际应用中,我们往往很难做到这种方法,因为这种方法只适合数据聚集的场景,当数据比如是{1,9,2,22,5555555}这样十分分散的数据时,就十分浪费内存。并且还有一种情况,两个不同的Key可能会映射到同一个位置,导致哈希冲突

我们假设,目前哈希表中已经存放了N个数据,然后表的大小为M,那么此时这个表的负载因子就是(N/M)。负载因子用于定义这个哈希表的一些状况。当负载因子小的时候,表示这张表利用效率低,哈希冲突概率低。当负载因子大,甚至逼近于1时,表示这张表利用效率高,哈希冲突概率高,该表需要扩容。因此,找到一个合适的平衡点是很重要的。

除留余数法

一个好的哈希函数,可以尽量的让数据平均的落在哈希表中的每个位置。但是实操起来非常苦难,哈希冲突在所难免,只能说尽量。我们接下来说一个非常经典的哈希函数:除留余数法。

除留余数法,顾名思义,就是找余数。比如现在我进来了一个key大小的size_t类型数据,我要为他在M大小的哈希表中找一个位置,那么其公式为:h(key) = key % M.当我们使用这个方法的时候,重点便为M值的选取。一般来说,我们不会选择2的幂和10的幂作为M值,因为很容易发生哈希冲突。如果选择的是2的幂,那就是2进制的后幂次位,10就是10进制的后幂次位。那么一般我们选择什么作为M呢?我们选择质数,在c++sgi版本中,便是如此。他们使用了一个大小趋近于2倍的质数集。在我们以下的哈希表模拟实现中,为了方便,我并不会严格按照这个要求。

static const unsigned long __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
};

其他方法不作介绍,包括乘法散列法和全域散列法等等,其本质也都是为了减少哈希冲突,提高哈希效率。

处理哈希冲突

开放定址法

开放定址法的思想由直接定址法延伸而来,本质还是在同一张表里面找坑蹲。当自己本来的坑被别人蹲了以后,就去找别人的坑蹲,其中有两种找坑的方法比较出名,线性探测法和二次探测法。线性探测法是直接到下一个坑去找位置,直到找到了个空的坑。二次探测法是跳跃找坑法,按二次方的速度进行左右横跳,直到找到一个空坑。下面是线性探测的模拟实现,这个初始空间只开10个其实不严谨,但是只是模拟实现就这样吧。

namespace open_address
{enum State//利用三个状态确定当前节点的状态,使用枚举值,枚举能保证state只能在这三个里面{EXIST,EMPTY,DELETE};template<class K, class V>struct HashData{pair<K, V> _kv;//使用kv来存储数据State _state = EMPTY;};template<class K, class V, class Hash = HashFunc<K>>//Hash是用来提取数据的,将数据转换为size_t类型class HashTable{public:HashTable(){_tables.resize(10);//先开10个,不够再开}bool Insert(const pair<K, V>& kv){if (Find(kv.first))//如果哈希表里面已经有了,那就直接returnreturn false; K key = kv.first;//记录key的数据if (_n * 10 / _tables.size() > 7)//如果目前的数据量已经占到哈希表的70%以上,扩容{HashTable<K, V,Hash > new_t;//构建一个哈希表new_t._tables.resize(2 * _tables.size());//直接扩两倍size_t i = 0;while (i != _tables.size())//直接将数据插入_tables中,然后i++,当i = tables的大小时嗲表扩容结束了{if (_tables[i]._state == EXIST){new_t.Insert(_tables[i]._kv);}i++;}_tables.swap(new_t._tables);//直接运行vector的swap函数,}Hash hash;//定义仿函数size_t hash0 = hash(key) % _tables.size();//获得哈希值size_t hashi = hash0;//获取哈希位置变化值size_t i = 1;while (_tables[hashi]._state != EMPTY)//如果当前节点状态不是empty,表示不能插入{hashi = (hash0 + i) % _tables.size();//运行哈希函数i++;}_tables[hashi]._kv = kv;//当找到空节点了,_tables[hashi]._state = EXIST;//将当前节点状态改为存在_n++;//数量++return true;}HashData<K, V>* Find(const K& key){Hash hash;size_t hash0 = hash(key) % _tables.size();size_t hashi = hash0;int i = 1;while (_tables[hashi]._state != EMPTY){if (_tables[hashi]._state == EXIST&& _tables[hashi]._kv.first == key){return &_tables[hashi];}hashi = (hash0 + i) % _tables.size();i++;}return nullptr;}bool Erase(const K& key){HashData<K, V>* hd  = Find(key);if (hd == nullptr)return false;hd->_state = DELETE;_n--;}private:vector<HashData<K, V>> _tables;size_t _n = 0;  // 表中存储数据个数};
}

链定址法

链定址法就能比较好的解决哈希冲突严重的问题。链定址法的本质就是把相关数据以链表的形式挂载在对应哈希值的下面,变成哈希桶。因此,在这种情况下负载因子是可以大于1的,但是依然不推荐负载因子大于1,因为会导致某个哈希桶挂载的数据太多,导致遍历依旧效率低。

namespace hash_bucket
{template<class T>struct HashNode{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data), _next(nullptr){}};// K 为 T 中key的类型// T 可能是键值对,也可能是K// KeyOfT: 从T中提取key// Hash将key转化为整形,因为哈市函数使用除留余数法template<class K, class T, class KeyOfT, class Hash>class HashTable{typedef HashNode<T> Node;public:HashTable(){_tables.resize(10, nullptr);//先简单把哈希表大小定为10}// 哈希桶的销毁~HashTable(){// 依次把每个桶释放for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];//while (cur){Node* next = cur->_next;delete cur;//注意,我们的数据空间都new出来的,所以要delete删除。cur = next;} _tables[i] = nullptr;}}// 插入值为data的元素,如果data存在则不插入bool Insert(const T& data){if (Find(KeyOfT(data)))//先确定不存在相同的数据。如果已经有了,直接returnreturn false;if (_n / _tables.size() >= 1)//保证负载因子小于1,大于1就扩容{vector<Node*> new_tables;//先弄一个新哈希表的tablenew_tables.resize(2 * _tables.size());//把他的表大小扩为2倍,for (int i = 0;i < _tables.size();i++)//接着通过遍历方式的去找每个哈希值{Node* cur = _tables[i];//while (cur)//把每个哈希值下带的数据全部头插到new_tables那边去{Node* next = cur->_next;size_t hashi = Hash(KeyOfT(cur->_data)) % new_tables.size();cur->_next = new_tables[hashi];new_tables[hashi] = cur;cur = next;}_tables[i] = nullptr;//将原表对应哈希值的地方置空}_tables.swap(new_tables);//交换,让new_tables变为_tables}size_t hash0 = Hash(KeyOfT(data)) % _tables.size();//保存插入数据对应的哈希值。Node* cur = _tables[hash0];//将目标哈希地址设为curNode* new_node = new Node(data);//创造新的插入节点new_node->_next = _tables[hash0];//头插数据_tables[hash0] = new_node;++_n;//存在节点数量++return true;}// 在哈希桶中查找值为key的元素,存在返回true否则返回falsebool Find(const K& key){size_t hash0 = Hash(key) % _tables.size();//直接遍历即可Node* cur = _tables[hash0];while (cur){if (KeyOfT(cur->_data) == key)return true;cur = cur->_next;}return false;}// 哈希桶中删除key的元素,删除成功返回true,否则返回falsebool Erase(const K& key){if ( !Find( key ) )//先确定有没有数据{return false;}size_t hash0 = Hash(key) % _tables.size();Node* cur = _tables[hash0];Node* parent = _tables[hash0];//定义一个parent用来链接父子节点。while (cur)//遍历找数据{if (KeyOfT(cur->_data) == key){break;}parent = cur;cur = cur->_next;}if (cur == parent){_tables[hash0] = cur->_next;	}else{parent->_next = cur->_next;}delete cur;cur = nullptr;return true;}private:vector<Node*> _tables;  // 指针数组size_t _n = 0;			// 表中存储数据个数};
}

unorder_map和unorder_set的实现

这两个数据结构的底层都是哈希表,更确切的说是哈希桶。因此,实现unorder_map和unorder_set的重点其实是实现可泛化的哈希桶。在实现中,有几个比较指的注意的地方:1.类的声明嵌套。当出现A类中使用了B类,但是B类也使用A类,那么就可以进行前置声明,让编译器先对对参数,然后去下面找定义。2.模版友元。有的时候我们的数据是private的,但是其他类需要调用这个数据,按照以前学的那就是设置为友元类。对于模版类也可以这样进行设置。3.按照常理,set和map中的key是不能修改的。因此我们使用const进行修饰。map需要修饰pair中的key,而set直接修饰key即可。当然,当改了以后,需要在所有相关定义或者重定义的位置进行更改,比如typedef.

#pragma once
#include<vector>enum State
{EXIST,EMPTY,DELETE
};template<class K, class V>
struct HashData
{pair<K, V> _kv;State _state = EMPTY;
};template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};template<>
struct HashFunc<string>
{size_t operator()(const string& s){// BKDRsize_t hash = 0;for (auto ch : s){hash += ch;hash *= 131;}return hash;}
};inline unsigned long __stl_next_prime(unsigned long n)
{// Note: assumes long is at least 32 bits.static const int __stl_num_primes = 28;static const unsigned long __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};const unsigned long* first = __stl_prime_list;const unsigned long* last = __stl_prime_list + __stl_num_primes;const unsigned long* pos = lower_bound(first, last, n);return pos == last ? *(last - 1) : *pos;
}namespace open_address
{template<class K, class V, class Hash = HashFunc<K>>class HashTable{public:HashTable():_tables(__stl_next_prime(0)), _n(0){}bool Insert(const pair<K, V>& kv){if (Find(kv.first))return false;// 负载因子 >= 0.7扩容if (_n * 10 / _tables.size() >= 7){//vector<HashData<K, V>> newtables(_tables.size()*2);//for (auto& data : _tables)//{//	// 旧表的数据映射到新表//	if (data._state == EXIST)//	{//		size_t hash0 = data._kv.first % newtables.size();//		// ...//	}//}//_tables.swap(newtables);HashTable<K, V, Hash> newht;//newht._tables.resize(_tables.size() * 2);newht._tables.resize(__stl_next_prime(_tables.size() + 1));for (auto& data : _tables){// 旧表的数据映射到新表if (data._state == EXIST){newht.Insert(data._kv);}}_tables.swap(newht._tables);}Hash hash;size_t hash0 = hash(kv.first) % _tables.size();size_t hashi = hash0;size_t i = 1;int flag = 1;while (_tables[hashi]._state == EXIST){// 线性探测hashi = (hash0 + i) % _tables.size();++i;/*hashi = (hash0 + (i*i*flag)) % _tables.size();if (hashi < _tables.size())hashi += _tables.size();if (flag == 1){flag = -1;}else{++i;flag = 1;}*/}_tables[hashi]._kv = kv;_tables[hashi]._state = EXIST;++_n;return true;}HashData<K, V>* Find(const K& key){Hash hash;size_t hash0 = hash(key) % _tables.size();size_t hashi = hash0;size_t i = 1;while (_tables[hashi]._state != EMPTY){if (_tables[hashi]._state == EXIST&& _tables[hashi]._kv.first == key){return &_tables[hashi];}// 线性探测hashi = (hash0 + i) % _tables.size();++i;}return nullptr;}bool Erase(const K& key){HashData<K, V>* ret = Find(key);if (ret){ret->_state = DELETE;return true;}else{return false;}}private:vector<HashData<K, V>> _tables;size_t _n;  // 记录数据个数};
}namespace hash_bucket
{template<class T>struct HashNode{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data), _next(nullptr){}};// 前置声明template<class K, class T, class KeyOfT, class Hash>class HashTable;template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>struct HTIterator{typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> HT;typedef HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;Node* _node;const HT* _ht;HTIterator(Node* node, const HT* ht):_node(node),_ht(ht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}// 16:46Self& operator++(){if (_node->_next){// 当前桶还有数据,走到当前桶下一个节点_node = _node->_next;}else{// 当前桶走完了,找下一个不为空的桶KeyOfT kot;Hash hash;size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size();++hashi;while (hashi < _ht->_tables.size()){_node = _ht->_tables[hashi];if (_node)break;else++hashi;}// 所有桶都走完了,end()给的空标识的_nodeif (hashi == _ht->_tables.size()){_node = nullptr;}}return *this;}};template<class K, class T, class KeyOfT, class Hash>class HashTable{// 友元声明template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>friend struct HTIterator;typedef HashNode<T> Node;public:typedef HTIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;typedef HTIterator<K, T, const T&, const T*, KeyOfT, Hash> ConstIterator;Iterator Begin(){if (_n == 0)return End();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);}ConstIterator Begin() const{if (_n == 0)return End();for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return ConstIterator(cur, this);}}return End();}ConstIterator End() const{return ConstIterator(nullptr, this);}HashTable():_tables(__stl_next_prime(0)), _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;}}pair<Iterator, bool> Insert(const T& data){KeyOfT kot;Iterator it = Find(kot(data));if (it != End())return { it, false};Hash hash;// 负载因子 == 1时扩容if (_n == _tables.size()){/*HashTable<K, V> newht;newht._tables.resize(__stl_next_prime(_tables.size() + 1));for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){newht.Insert(cur->_kv);cur = cur->_next;}}_tables.swap(newht._tables);*/vector<Node*> newTable(__stl_next_prime(_tables.size()+1));for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;// 头插到新表size_t hashi = hash(kot(cur->_data)) % newTable.size();cur->_next = newTable[hashi];newTable[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newTable);}size_t hashi = hash(kot(data)) % _tables.size();// 头插Node* newnode = new Node(data);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return { Iterator(newnode, this), false };}Iterator Find(const K& key){KeyOfT kot;Hash hash;size_t hashi = hash(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){return Iterator(cur, this);}cur = cur->_next;}return End();}bool Erase(const K& key){KeyOfT kot;size_t hashi = 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;--_n;return true;}else{prev = cur;cur = cur->_next;}}return false;}private:vector<Node*> _tables; // 指针数组size_t _n = 0;		   // 表中存储数据个数};
}
#pragma once#include"HashTable.h"// MyUnorderedMap.h
namespace bit
{template<class K, class V, class Hash = HashFunc<K>>class unordered_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, Hash>::Iterator iterator;typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::ConstIterator 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();}V& operator[](const K& key){pair<iterator, bool> ret = insert({ key, V() });return ret.first->second;}pair<iterator, bool> insert(const pair<K, V>& kv){return _ht.Insert(kv);}iterator Find(const K& key){return _ht.Find(key);}bool Erase(const K& key){return _ht.Erase(key);}private:hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;};void test_map1(){unordered_map<string, string> dict;dict.insert({ "sort", "排序" });dict.insert({ "字符串", "string" });dict.insert({ "sort", "排序" });dict.insert({ "left", "左边" });dict.insert({ "right", "右边" });dict["left"] = "左边,剩余";dict["insert"] = "插入";dict["string"];for (auto& kv : dict){cout << kv.first << ":" << kv.second << endl;}cout << endl;unordered_map<string, string>::iterator it = dict.begin();while (it != dict.end()){// 不能修改first,可以修改second//it->first += 'x';it->second += 'x';cout << it->first << ":" << it->second << endl;++it;}cout << endl;}
}
#pragma once#include"HashTable.h"namespace bit
{template<class K, class Hash = HashFunc<K>>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::Iterator iterator;typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::ConstIterator 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, Hash> _ht;};void print(const unordered_set<int>& s){unordered_set<int>::const_iterator it = s.begin();while (it != s.end()){//*it = 1;cout << *it << " ";++it;}cout << endl;for (auto e : s){cout << e << " ";}cout << endl;}void test_set1(){int a[] = { 3,11,86,7,88,82,1,881,5,6,7,6 };unordered_set<int> s;for (auto e : a){s.insert(e);}unordered_set<int>::iterator it = s.begin();while (it != s.end()){//*it = 1;cout << *it << " ";++it;}cout << endl;for (auto e : s){cout << e << " ";}cout << endl;print(s);}
}

http://www.xdnf.cn/news/18202.html

相关文章:

  • 【Linux系列】常见查看服务器 IP 的方法
  • 深入了解 Filesystem Hierarchy Standard (FHS) 3.0 规范
  • 17.5 展示购物车缩略信息
  • 【Linux】文件基础IO
  • Google Earth Engine | (GEE)逐月下载的MODIS叶面积指数LAI
  • Rust 入门 生命周期(十八)
  • 【牛客刷题】字符串按索引二进制1个数奇偶性转换大小写
  • C#高级语法_委托
  • java基础(十)sql的mvcc
  • 字节 Golang 大模型应用开发框架 Eino简介
  • 进程互斥的硬件实现方法
  • 私人AI搜索新突破:3步本地部署Dify+Ollama+QwQ,搜索能力MAX
  • 《动手学深度学习v2》学习笔记 | 1. 引言
  • Nacos 注册中心学习笔记
  • C++入门自学Day11-- String, Vector, List 复习
  • Kafka 面试题及详细答案100道(23-35)-- 核心机制2
  • 3D打印——给开发板做外壳
  • 最新技术论坛技术动态综述
  • XF 306-2025 阻燃耐火电线电缆检测
  • 【Linux | 网络】高级IO
  • JMeter(进阶篇)
  • (一)Python + 地球信息科学与技术 (GeoICT)=?
  • CentOS7安装部署GitLab社区版
  • 第3章 Java NIO核心详解
  • Portkey-AI gateway 的一次“假压缩头”翻车的完整排障记:由 httpx 解压异常引发的根因分析
  • java八股文-(spring cloud)微服务篇-参考回答
  • FreeRTOS在中断服务例程(ISR)中使用队列
  • 小白成长之路-k8s部署discuz论坛
  • Python爬虫-解决爬取政务网站的附件,找不到附件链接的问题
  • Blender模拟结构光3D Scanner(二)投影仪内参数匹配