赞
踩
本文参考文档:https://legacy.cplusplus.com/reference/unordered_map/
1.unordered_map接口说明
函数声明 | 功能介绍 |
---|---|
unordered_map | 构造不同格式的unordered_map对象 |
2. unordered_map的容量
函数声明 | 功能介绍 |
---|---|
bool empty() const | 检测unordered_map是否为空 |
size_t size() const | 获取unordered_map的有效元素个数 |
3.unordered_map的迭代器(无序set和map的迭代器都是单向迭代器)
函数功能 | 函数介绍 |
---|---|
begin () | 返回unordered_map中第一个元素的迭代器 |
end() | 返回unordered_map中最后一个元素后一个位置的迭代器 |
cbegin() | 返回unordered_map中第一个元素的const迭代器 |
cend() | 返回unordered_map中最后一个元素后一个位置的const迭代器 |
4.unordered_map的元素访问
函数功能 | 函数介绍 |
---|---|
operator[] | 返回key值对应的val值(没有默认值) |
注意:该函数中实际调用哈希桶的插入操作,用参数key与V()构造一个默认值往底层哈希桶
中插入,如果key不在哈希桶中,插入成功,返回V(),插入失败,说明key已经在哈希桶中,
将key对应的value返回。
5.unordered_map查询
函数功能 | 函数介绍 |
---|---|
find() | 查询key值是否存在,存在返回key值对应的迭代器的位置,返回key在哈希桶中的位置 |
count | 返回哈希桶中关键码为key的键值对的个数 |
注意:unordered_map中key是不能重复的,因此count函数的返回值最大为1
6. unordered_map的修改操作
函数功能 | 函数介绍 |
---|---|
insert | 向容器中插入键值对 |
erase | 删除容器中的键值对 |
void clear() | 清空容器中有效元素个数 |
void swap() | 交换两个容器中的元素 |
7.unordered_map的桶操作
函数功能 | 函数介绍 |
---|---|
size_t bucket_count()const | 返回哈希桶中桶的总个数 |
size_t bucket_size(size_t n)const | 返回n号桶中有效元素的总个数 |
size_t bucket(const K& key) | 返回元素key所在的桶号 |
unordered_set接口根map差不多一样,不过set只存key值且不能修改
这里就不过多赘述了,详情可自行观看文档
set文档资料
顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即
O(
l
o
g
2
N
log_2 N
log2N) ,搜索的效率取决于搜索过程中元素的比较次数。
理想的搜索方法:可以 不经过任何比较,一次直接从表中得到要搜索的元素。
如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立
一一映射的关系,那么在查找时通过该函数可以很快找到该元素。
哈希/散列:映射 一个值和另一个值建立关系。
哈希表/散列表: 映射 关键字和存储位置建立一个关系。
不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突
或哈希碰撞。
把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。
当前位置被占用,在开放空间里按某种规则,找一个没有被占用的位置存储
enum Stutas { //删除状态的意义: //1.再插入,这个位置可以覆盖 //2.防止后面冲突的值,出现找不到的情况,遇到删除状态还要继续向后寻找 DELETE, EMPTY, EXIST }; template<class K,class V> struct HashData { pair<K,V> _kv; Stutas _s = EMPTY; }; //开放地址法 //线性查找,插入 template<class K> struct Hashfunc { size_t operator()(const K& key) { return (size_t)key; } }; //struct HashfuncString //{ // //BKDR算法 // size_t operator()(const string& key) // { // size_t hash = 0; // //把他们的ascll码值加起来 // for (auto e : key) // { // hash += e; // } // return hash; // } //}; template<> struct Hashfunc<string> { size_t operator()(const string& key) { size_t hash = 0; //把他们的ascll码值加起来 for (auto e : key) { hash += e; } return hash; } }; template<class K,class V, class Hash = Hashfunc<K>> class HashTable { public: HashTable() { _tables.resize(10);//resize也直接会开初始化size的大小。reserve之会开capecity的大小 } //提供find解决insert能够插入相同key的问题 HashData<K, V>* Find(const K& key) { Hash ht; size_t hashi = ht(key) % _tables.size(); while (_tables[hashi]._s != EMPTY) { if (_tables[hashi]._s == EXIST && _tables[hashi]._kv.first == key) { //找到return找到位置的地址 //没找到return空 return &_tables[hashi]; } hashi++; //hashi超出table的size大小就给他%回来 hashi = hashi % _tables.size(); } return nullptr; } bool Insert(const pair<K, V>& kv) { Hash ht; if (Find(kv.first)) { return false; } if ((double)_n / _tables.size() >= 0.7) { //不能直接扩容size的二倍,还要转移数据,重新映射关系(扩容之后值和空间的位置关系已经乱了) //释放旧空间 size_t newsize = _tables.size() * 2; HashTable<K,V,Hash> newHT; newHT._tables.resize(newsize); //遍历旧表,插入新表 for ( size_t i= 0; i < _tables.size(); i++) { if (_tables[i]._s == EXIST) { newHT.Insert(_tables[i]._kv); } } _tables.swap(newHT._tables); } //size_t hashi = kv.first % _tables.size();//key不一定是整形,如果是string呢?用仿函数把kv.first的值转化整形值 //先用字符串映射一个整形 size_t hashi = ht(kv.first) % _tables.size(); while (_tables[hashi]._s == EXIST) { hashi++; //hashi超出table的size大小就给他%回来 hashi = hashi % _tables.size(); } _tables[hashi]._kv = kv; _tables[hashi]._s = EXIST; ++_n; } bool Erase(const K& key) { HashData<K,V>* ret = Find(key); //find找到会返回找到位置的地址 if (ret) { ret->_s = DELETE; --_n; return true; } return false; } void Print() { for (size_t i = 0; i < _tables.size(); i++) { if (_tables[i]._s == EXIST) { //cout << i << "->"; cout <<"["<<i<<"]->"<< _tables[i]._kv.first << ":"<< _tables[i]._kv.second << endl; /*printf("[%d]->%d\n", i, _tables[i]._kv.first); printf("")*/ } else if (_tables[i]._s == EMPTY) { printf("[%d]->\n", i); } else { printf("[%d]->DELETE\n", i); } } cout << endl; } private: vector<HashData<K,V>> _tables; size_t _n = 0;//存储的关键字个数 };
哈希桶的平均时间复杂度为O(1)
思路概念:
我们把每个hashi映射的位置,替换成list,链接每次插入的冲突的新key
可以参考下图:如4位置都是冲突的关键字。
整形还是可以用除留余数法计算key映射的位置,但是例如string这种字符串呢?
我们可以使用仿函数,实现类型的转换。
例如把字符串的每个字符的ascll码值加起来。
代码实现:
template<class K> struct Hashfunc { size_t operator()(const K& key) { return (size_t)key; } }; struct HashfuncString { size_t operator()(const string& key) { size_t hash = 0; //把他们的ascll码值加起来 for (auto e : key) { hash += e; } return hash; } };
但是这种转化函数会有一些缺陷,例如abc
和acb
这两个不同的字符串ascll码值加起来都是相同的,导致了映射位置冲突。
有没有什么办法能解决这类的问题呢?
有大佬专门研究出了很多种算法我们可以参考:
字符串哈希算法
例如:
这类算法会减少字符串转哈希算法的冲突。但是肯定不能完全避免。
我们也可以模仿大佬算法完善我们的代码:
struct HashfuncString
{
size_t operator()(const string& key)
{
size_t hash = 0;
//把他们的ascll码值加起来
for (auto e : key)
{
hash *= 31;//在加ascll码值前*31/131
hash += e;
}
return hash;
}
};
这种方式也是类似库里面unordered_map的实现方式(要传一个仿函数)
但是库里面其实比我们实现的更好一些,
库里面不用传第三个模板参数直接取模,
库里面的unordered_map用string做key可以直接转化为整型。
std::unordered_map<string,string> dict;
我们可以用特化模板参数来模拟实现这总功能。
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; //把他们的ascll码值加起来 for (auto e : key) { hash += e; } return hash; } };
利用哈希桶实现哈希表代码:
template<class K,class V> struct HashNode { HashNode* _next; pair<K, V> _kv; HashNode(const pair<K, V>& kv) :_kv(kv) ,_next(nullptr) {} }; template<class K> struct Hashfunc { size_t operator()(const K& key) { return (size_t)key; } }; template<> struct Hashfunc<string> { //BKDR算法 size_t operator()(const string& key) { size_t hash = 0; //把他们的ascll码值加起来 for (auto e : key) { hash *= 31;//在加ascll码值之前*31/131... hash += e; } return hash; } }; template<class K,class V ,class Hash = Hashfunc<K>> class HashTable { typedef HashNode<K, V> Node; public: HashTable() { _tables.resize(10); } ~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; } } bool Insert(const pair<K, V>& kv) { if (Find(kv.first)) return false; Hash ht; //扩容用开放地址法的扩容方法会产生大量的资源浪费 //可以考虑直接把旧表的数据挪动下来 if (_n == _tables.size()) { vector<Node*> newtable; newtable.resize(_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 = ht(cur->_kv.first) % newtable.size(); cur->_next = newtable[i]; newtable[i] = cur; //利用cur把节点挪动到新表 //cur利用next返回到旧表以此循环 cur = next; } _tables[i] = nullptr; //旧表这个桶数据已经被挪动完,记得制空。 //以防后面析构有问题 } _tables.swap(newtable); } //先算出数据要存入哪个桶 size_t hashi = ht(kv.first) % _tables.size(); //头插 Node* newnode = new Node(kv); newnode->_next = _tables[hashi]; _tables[hashi] = newnode; ++_n; return true; } Node* Find(const K& key) { Hash ht; size_t hashi = ht(key) % _tables.size(); Node* cur = _tables[hashi]; while (cur) { if (cur->_kv.first == key) { return cur; } cur = cur->_next; } return NULL; } bool Erase(const K& key) { Hash ht; size_t hashi = ht(key) % _tables.size(); Node* cur = _tables[hashi]; Node* prev = nullptr; while (cur) { if (cur->_kv.first == key) { if (prev == nullptr) { _tables[hashi] = cur->_next; } else { prev->_next = cur->_next; } delete cur; return true; } prev = cur; cur = cur->_next; } return false; } void Some() { size_t bucketsize = 0; size_t maxbucketlen = 0; size_t sumbucketlen = 0; double averagebucketlen = 0; for (size_t i = 0; i < _tables.size(); i++) { Node* cur = _tables[i]; if (cur == nullptr) { ++bucketsize; } size_t bucketlen = 0; while (cur) { ++bucketlen; cur = cur->_next; } sumbucketlen += bucketlen; if (bucketlen > maxbucketlen) { maxbucketlen = bucketlen; } } averagebucketlen = (double)sumbucketlen / (double)bucketsize; printf("bucketsize:%d\n", bucketsize); printf("maxbucketlen:%d\n", maxbucketlen); printf("averagebucketlen:%lf\n", averagebucketlen); } private: vector<Node*> _tables; size_t _n = 0; };
#include"HashTable.h" namespace goat { template<class K,class V, class Hash = Hash_Bucket::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; pair<iterator,bool> insert(const pair<K,V>& kv) { return _mp.Insert(kv); } iterator begin() { return _mp.begin(); } iterator end() { return _mp.end(); } V& operator[](const K& key) { pair<iterator, bool>ret = _mp.Insert(make_pair(key,V())); return ret.first->second; } const V& operator[](const K& key)const { pair<iterator, bool>ret = _mp.Insert(make_pair(key, V())); return ret.first->second; } private: Hash_Bucket::HashTable<K, pair<const K,V> ,MapKeyOfT ,Hash> _mp; }; }
#include"HashTable.h" namespace goat { template<class K ,class Hash = Hash_Bucket::Hashfunc<K>> class unordered_set { struct SetKeyOfT { const K& operator()(const K& key) { return key; } }; public: typedef typename Hash_Bucket::HashTable<K, K, SetKeyOfT, Hash>::const_iterator iterator; typedef typename Hash_Bucket::HashTable<K, K, SetKeyOfT, Hash>::const_iterator const_iterator; /*iterator begin() { return _st.begin(); } iterator end() { return _st.end(); }*/ const_iterator begin() const { return _st.begin(); } const_iterator end() const { return _st.end(); } iterator find(const K& key) { return _st.Find(key); } bool erase(const K& key) { _st.Erase(key); } //返回值pair要实现operator[]再去修改 pair<iterator, bool> insert(const K& key) { //return _st.Insert(key);//返回普通迭代器,但是这里insert接受的是const迭代器 //正常解决方法1:支持const迭代器转化成普通迭代器,方法二:下一层Insert使用节点的指针返回(指针会产生隐式类型转换为迭代器 //但是这里unordered迭代器有三个值不能使用方法二 //iterator(cur , this ,hashi) //这里用另外一种简单的方法 auto ret = _st.Insert(key); return pair<iterator, bool>(iterator(ret.first._node, ret.first._pht, ret.first._hashi), ret.second); } private: Hash_Bucket::HashTable<K, K , SetKeyOfT, Hash> _st; }; }
#include<iostream> #include<vector> #include<utility> #include<list> #include<set> using namespace std; namespace Hash_Bucket { template<class T> struct HashNode { HashNode<T>* _next; T _data; //库里面的unordered是实现插入顺序遍历的 //要单独维护一个链表 //HashNode<T>* _linknext; //HashNode<T>* _linkprev; HashNode(const T& data) :_data(data) ,_next(nullptr) {} }; 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 e : key) { hash *= 31; hash += e; } return hash; } }; //解决互相依赖方法一 //前置声明(不用给缺省参数) //因为hashiterator和hashtable是互相依赖的关系 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 __HashIterator { typedef HashNode<T> Node; typedef __HashIterator<K, T,Ref,Ptr, KeyOfT, Hash> Self; Node* _node; const HashTable<K, T, KeyOfT, Hash>* _pht; //解决互相依赖方法二 //实际上哈希迭代器需要的是: //vector<Node*>* _ptb; //1.记录当前桶的所在位置 size_t _hashi; __HashIterator(Node* node,const HashTable<K, T, KeyOfT, Hash>* pht, size_t hashi) : _node(node) , _pht(pht) , _hashi(hashi) {} Self& operator++() { if (_node->_next) { //桶还有节点,就走下一个节点 _node = _node->_next; } else { //当前桶已经走完了,需要下一个桶的开始 //传个哈希表过来 2.直接算出当前所在桶位置 //KeyOfT kot; //Hash hf; //size_t hashi = hf(kot(_node->_data)) % _pht._tables.size(); ++_hashi; //_tables是私有成员变量 //可以考虑友元 while (_hashi < _pht->_tables.size()) { if (_pht->_tables[_hashi]) { _node = _pht->_tables[_hashi]; break; } else { _hashi++; } } if (_hashi == _pht->_tables.size()) { _node = nullptr; } } return *this; } Ref operator*() { return _node->_data; } Ptr operator->() { return &_node->_data; } bool operator!=(const Self& s) { return _node != s._node; } }; //unordered_set -> HashTable<K,K> //unordered_map -> HashTable<K,pair<K,V>> template<class K,class T , class KeyOfT,class Hash = Hashfunc<K>> class HashTable { template<class K, class T,class Ref ,class Ptr ,class KeyOfT, class Hash> friend struct __HashIterator; typedef HashNode<T> Node; public: typedef __HashIterator<K, T, T&,T* , KeyOfT, Hash> iterator; typedef __HashIterator<K, T, const T&,const T* , KeyOfT, Hash> const_iterator; iterator begin() { for (size_t i = 0; i < _tables.size(); i++) { if (_tables[i]) { return iterator(_tables[i] ,this , i); } } return end(); } iterator end() { return iterator(nullptr, this, -1); } const_iterator begin() const //const修饰this-> HashTable<K, T, KeyOfT, Hash>* { for (size_t i = 0; i < _tables.size(); i++) { if (_tables[i]) { return const_iterator(_tables[i], this, i); } } return end(); } const_iterator end() const { return const_iterator(nullptr, this, -1); } HashTable() { _tables.resize(10); } ~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; /*if (Find(kot(data))) { return false; }*/ iterator it = Find(kot(data)); if(it != end()) { return make_pair(it, false); } Hash ht; //扩容用开放地址法的扩容方法会产生大量的资源浪费 //可以考虑直接把旧表的数据挪动下来 if (_n == _tables.size()) { vector<Node*> newtable; newtable.resize(_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 = ht(kot(cur->_data)) % newtable.size(); cur->_next = newtable[i]; newtable[i] = cur; //利用cur把节点挪动到新表 //cur利用next返回到旧表以此循环 cur = next; } _tables[i] = nullptr; //旧表这个桶数据已经被挪动完,记得制空。 //以防后面析构有问题 } _tables.swap(newtable); } //先算出数据要存入哪个桶 size_t hashi = ht(kot(data)) % _tables.size(); //头插 Node* newnode = new Node(data); newnode->_next = _tables[hashi]; _tables[hashi] = newnode; ++_n; return make_pair(iterator(newnode,this,hashi),true); } iterator Find(const K& key) { Hash ht; KeyOfT kot; size_t hashi = ht(key) % _tables.size(); Node* cur = _tables[hashi]; while (cur) { if (kot(cur->_data) == key) { return iterator(cur , this ,hashi); } cur = cur->_next; } return end(); } bool Erase(const K& key) { Hash ht; KeyOfT kot; size_t hashi = ht(key) % _tables.size(); Node* cur = _tables[hashi]; Node* prev = nullptr; while (cur) { if (kot(cur->_data) == key) { if (prev == nullptr) { _tables[hashi] = cur->_next; } else { prev->_next = cur->_next; } delete cur; return true; } prev = cur; cur = cur->_next; } return false; } void Some() { size_t bucketsize = 0; size_t maxbucketlen = 0; size_t sumbucketlen = 0; double averagebucketlen = 0; for (size_t i = 0; i < _tables.size(); i++) { Node* cur = _tables[i]; if (cur == nullptr) { ++bucketsize; } size_t bucketlen = 0; while (cur) { ++bucketlen; cur = cur->_next; } sumbucketlen += bucketlen; if (bucketlen > maxbucketlen) { maxbucketlen = bucketlen; } } averagebucketlen = (double)sumbucketlen / (double)bucketsize; printf("bucketsize:%d\n", bucketsize); printf("maxbucketlen:%d\n", maxbucketlen); printf("averagebucketlen:%lf\n", averagebucketlen); } private: vector<Node*> _tables; size_t _n = 0; }; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。