哈希表详解

目录

1.unordered系列关联式容器

2.哈希

3.unordered_map和unordered_set哈希实现

4.代码总和


1.unordered系列关联式容器

1.1unordered系列

        c++98的STL里面提供了底层为红黑树的关联式容器(set和map); 但是在结点数目很多的时候查询的效率就低了, 所以c++11里STL又提供了四个新的unordered的关联式容器;分别是unordered_map 和 unordered_set ; 以及unordered_multimap和unordered_multiset.


2.哈希

2.1哈希概念

(1)通过某种函数(HashFunc)使得元素的存储位置和它的关键码之间可以一一对应的映射关系;那么查找这个元素的时候就会很快.

(2)哈希方法中使用的转换函数就是哈希(散列)函数,构造出来的结构是哈希表(散列表).

2.2哈希函数

Hash(key) = key % capacity;​​​​​

(1).直接定值法: 值和位置都有一个唯一的对应关系; but直接定值法会导致值的分布很散,空间浪费比较大.

(2)除留余数法: hashi = key % len; key和位置建立关系.

肯定会造成不同值映射到同一个地方,就是造成哈希碰撞; 就只能够将这个值放到映射后面的其他位置.

(3)哈希函数设计的越好,产生的哈希碰撞就越少, 但非哈希碰撞能够避免.

 2.3哈希冲突的解决

(1)闭散列(开放定值法): 当发生哈希冲突的时候, 如果哈希表没有满,那么就还有可以存放数据的位置,就将这个数据放到冲突位置的下一个空位置即可.

(2)开散列(拉链法): 首先对关键码用散列函数计算散列地址, 具有相同地址的关键码放到同一个子集合(), 各个桶里面的元素通过数据结构单链表进行连接, 链表的头节点放到哈希表中.

 2.4哈希表的实现

2.4.1闭散列(开放定值法)

(1)哈希表的结构:首先是pair类型的数据; 以及哈希表的状态(这个结构对后面插入删除有很大作用);.

(2)闭散列又分为线性探测二次探测;

线性探测:

        插入数据就是如果发生哈希冲突就将这个数据放到下一个没有存放过数据的空位置;

        删除数据:不能随便删除数据, 因为如果删除数据可能影响后面数据的查找.比如删除4的话,那么查找44就会受到影响.所以线性探测都是采用伪删除方法(就是状态改变为delete).

线性探测的优缺点:

        优点:简单实现;       

        缺点: 一旦产生哈希碰撞的数据很多, 就会数据的堆积,那么搜索的效率就会很低.

二次探测:

        当表的数据已经超过负载因子并且为质数; 那么可以将哈希碰撞的数据重新映射到新表中在将数据重新排放.

有数学验证:

        当表的长度为质数且表负载因子不超过0.5时,新的表项一定能够插入,而且任

何一个位置都不会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在
搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a不超过0.5,如果超出
必须考虑增容。
    enum status{EMPTY,EXIST,DELETE};template<class K, class V>struct HashDate{pair<K, V> _kv;status _s;};

(2)哈希表的插入(包括查找和扩容)

         查找:首先可以判断一下这个值是否存在,存在就无需插入,不存在再插入; 通过key;先找到hashi下标,如果这个值的状态不为空(可能为delete或者exist); 那么再判断是否为exist和值是否相等,相等就返回,不相等就继续小标查找.最后还没找到就返回nullptr.

        插入 扩容: 扩容这里采用负载因子来检查, 就是用来减少哈希冲突的, 但是负载因子如果太大,就很容易冲突加剧, 效率就低, 负载因子太小冲突降低但是空间利用率就低了.所以合适的负载因子很重要,一般设置为0.7.而且扩容不是采用在原本空间扩容而是在新空间扩,原本的数据也要挪过来.

          插入数据: 先使用哈希函数找到下标,然后判断状态为非exist,最后插入数据即可.

        HashDate<K, V>* Find(const K& key){size_t hashi = _kv.first % _tables.size();while (_tables[hashi]._s != EMPTY){if (_tables[hashi]._s == EXIST && _tables[hashi]._kv.first == key){return &_tables[hashi];}hashi++;hashi %= _tables.size();}return nullptr;}

		bool Insert(const pair<K, V>& kv){if (Find(kv.first))  //先判断这个值是否存在return false;//负载因子;if (_n * 10 / _tables.size() == 7){size_t newSize = _tables.size() * 2;HashTable<K, V> newHT;newHT._tables.resize(newSize);for (size_t i = 0; i < _table.size(); i++){if (_tables[i]._s == EXIST){newHT.Insert(_table[i]._kv);}}_table.swap(newHT._tables);}//哈希函数size_t hashi = kv.first % _tables.size();while (_tables[hashi]._s == EXIST)//如果存在就跳过{hashi++;hashi %= _tables.size();}//空位置或者删除位置就填写数据_tables[hashi]._kv = kv;_tables[hashi]._s = EXIST;_n++;return true;}
2.4.2 哈希桶( Hash_bucket)

       由于线性探测(闭散列)如果哈希冲突很多,搜索效率降低,那么我们想到开散列(拉链法),在每个表里面存放头节点,然后挂单链表.


 (1)哈希桶的结构(HashNode):

HashNode里面包含着next指针以及数据data;

    template<class T>struct HashNode{HashNode<T>* next;T _data;HashNode(const T& data):_data(data),next(nullptr){}};

(2)哈希桶的指针(__HTIterator):

1. 首先就是因为有使用到HashTable, 所以提前进行前置声明.

2. 迭代器的构造有const类型和非const类型的.

3. operator++: 因为哈希表挂的单链表, 那么迭代到下一个链表可能是非空(后面还有结点);还有就是已经遍历完了一个桶,那么就要找下一个桶. 然后前面传hashi就是这个用的, 找到哈希下标, 如果结点挂链表就赋给node, 直到遍历完还没有就nullptr;

4. operator* : 返回数据, 返回类型Ref就是T&;

5. operator->: 返回地址, 返回类型Ptr就是T*;

6. operator!= : 结点不相同.

	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 __HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> self;Node* _node;const HashTable<K, T, KeyOfT, Hash>* _pht;size_t _hashi;__HTIterator(Node* node, HashTable<K, T, KeyOfT, Hash>* pht, size_t hashi):_node(node), _pht(pht), _hashi(hashi){}__HTIterator(Node* node,const HashTable<K, T, KeyOfT, Hash>* pht, size_t hashi):_node(node), _pht(pht), _hashi(hashi){}//返回值为迭代器iterator.self& operator++(){//桶里面还有下一个结点if (_node->next){_node = _node->next;}else//当前桶已经走完那么就到下一个桶{_hashi++;while (_hashi < _pht->_tables.size()){if (_pht->_tables[_hashi]){_node = _pht->_tables[_hashi];break;}++_hashi;}if (_hashi == _pht->_tables.size()){_node = nullptr;}}return *this;}//Ref == T&Ref operator*(){return _node->_data;}//Ptr == T*Ptr operator->(){return &_node->_data;}bool operator!=(const self& s){return _node != s._node;}};

(3)哈希桶(HashTable):

1 迭代器的begin(): 

        因为是单链表结构, 先遍历哈希表, 找到有链表连接的第一个结点然后返回iterator, 如果最后没找到就是nullptr.

2 迭代器的end(): 

        结构规定,返回nullptr且hashi为-1.

3.HashTable析构:

        将哈希表挂的链表都delete. 哈希表的结点置空.

	template<class K, class T, class KeyOfT, class Hash>class HashTable{typedef HashNode<T> Node;template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>friend struct __HTIterator;public:typedef __HTIterator<K, T, T&, T*, KeyOfT, Hash> iterator;typedef __HTIterator<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{for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]){return iterator(_tables[i], this, i);}}return end();}const_iterator end()const{return 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;}}

(4)哈希桶插入数据

1. 首先进行数据的查找, 如果存在就不需要插入,反之则要;

2. hf和kot都是仿函数,处理数据的.

       pair<iterator, bool> Insert(const T& date){Hash hf;KeyOfT kot;//查找数据在否,不在就创建,在就不需要了.iterator it = Find(kot(date));if (it != end())return make_pair(it, false);//负载因子为1;if (_n == _tables.size()){vector<Node*> newTables;newTables.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 = hf(kot(cur->_data)) % newTables.size();cur->next = newTables[i];newTables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newTables);}size_t hashi = hf(kot(date)) % _tables.size();Node* newnode = new Node(date);//头插newnode->next = _tables[hashi];_tables[hashi] = newnode;++_n;return make_pair(iterator(newnode, this, hashi), true);}

(5)哈希桶的查找:

        通过key进行查找, 首先通过hashi进行查找到在哈希表的那个位置, 然后再根据cur链表进行遍历查找到date和key相同的结点.

        iterator Find(const K& key){Hash hf;KeyOfT kot;size_t hashi = hf(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){return iterator(cur, this, hashi);}cur = cur->next;}return end();}

(6)哈希桶的删除

删除也是先找到要删除的结点, 定义prev结点和cur结点, 如果找到且prev非空那么就是将prev置给cur->next; prev为空就将_tables[hashi] 置给cur->next; 没找到就一直遍历链表.

        bool Erase(const K& key){Hash hf;KeyOfT kot;size_t hashi = hf(key) % _tables.size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur){if (kot(cur->_date) == key){if (prev == nullptr){_tables[hashi] = cur->next;}else{prev->next = cur->next;}delete cur;return true;}prev = cur;cur = cur->next;}return false;}

(7)计算桶的大小

bucketsize是计算每个桶的大小;

bucketlen是计算每个桶的大小并且给sum.

maxbucketlen是计算最大的桶的大小;

averagebucketlen是计算平均每个桶的大小.

		//计算桶的各种大小void some(){size_t bucketSize = 0;//一个桶的大小size_t maxbucketLen = 0;size_t sum = 0;double averagebucketlen = 0.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("all bucketSize:%d\n", _tables.size());printf("bucketSize:%d\n", bucketSize);printf("maxBucketLen:%d\n", maxBucketLen);printf("averageBucketLen:%lf\n\n", averageBucketLen);}

 

3.unordered_map和unordered_set哈希实现.

3.1unordered_map

(1) 直接从HashTable.h文件里面去拿begin, end, insert, find, erase...

(2) unordered_map的时候对应HashTable里面的函数模板的参数,尤其是仿函数.

#pragma once
#include"HashTable.h"namespace study1
{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;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}pair<iterator, bool> insert(const pair<K, V>& kv){return  _ht.Insert(kv);}V& operator[](const K& key){pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));return ret.first->second;}const V& operator[](const K& key)const{pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));return ret.first->second;}iterator find(const K& key){return _ht.Find(key);}iterator earse(const K& key){return _ht.Erase(key);}private:Hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;};void test_map(){unordered_map<string, string> dict;dict.insert(make_pair("sort", ""));dict.insert(make_pair("string", ""));dict.insert(make_pair("insert", ""));for (auto& kv : dict){kv.second += 'x';cout << kv.first << ":" << kv.second << endl;}cout << endl;string arr[] = { "香蕉", "哈密瓜", "香蕉", "西瓜", "桃子" };unordered_map<string, int> cout_map;for (auto& e : arr){cout_map[e]++;}for (auto& kv : cout_map){cout << kv.first << ":" <<  kv.second << endl;}cout << endl;}
}

3.2unordered_set

同理处理完unordered_map, unordered_set也是一样的处理,只是接口和仿函数修改一点.

#pragma once
#include"HashTable.h"namespace study2
{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, K, SetKeyOfT, Hash>::iterator iterator;typedef typename Hash_bucket::HashTable<K, K, SetKeyOfT, Hash>::const_iterator const_iterator;iterator begin(){return _hf.begin();}iterator end(){return _hf.end();}const_iterator begin()const{return _hf.begin();}const_iterator end()const{return _hf.end();}pair<const_iterator, bool> insert(const K& key){auto ret = _hf.Insert(key);return pair<const_iterator, bool>(const_iterator(ret.first._node, ret.first._pht, ret.first._hashi), ret.second);}iterator find(const K& key){return _hf.Find(key);}iterator erase(const K& key){return _hf.Erase(key);}private:Hash_bucket::HashTable<K, K, SetKeyOfT, Hash> _hf;};void test_set(){unordered_set<int> us;us.insert(5);us.insert(15);us.insert(52);us.insert(3);unordered_set<int>::iterator it = us.begin();while (it != us.end()){cout << *it << " ";++it;}cout << endl;for (auto e : us){cout << e << " ";}cout << endl;}
}

4.代码总和

4.1Hash_Bucket的代码:

#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){//BKDR;是一种算法用来查找的方法,避免哈希碰撞的,比较高效.size_t hash = 0;for (auto e : key){hash *= 31;hash += e;}return hash;}
};namespace open_address
{enum status{EMPTY,EXIST,DELETE};template<class K, class V>struct HashDate{pair<K, V> _kv;status _s;};//丢到全局去,因为hash_bucket也要使用.//template<class K>//struct HashFunc//{//	//重载()//	size_t operator()(const K& key)//	{//		return (size_t)key;//	}//};//template<>//struct HashFunc<string>//{//	//重载()string//	size_t operator()(const string& key)//	{//		size_t hash = 0;//		for (auto e : key)//		{//			hash *= 31;//			hash += e;//		}//		cout << key << ":" << hash << endl;//		return hash;//	}//};template<class K, class V, class Hash = HashFunc<K>>class HashTable{public:HashTable(){_tables.resize(10);  //空间初始化先开10个空间;}bool Insert(const pair<K, V>& kv){if (Find(kv.first))  //先判断这个值是否存在return false;//负载因子;用来扩容操作,扩容直接开新的空间,还要将原来的数据移动到新空间,不用担心原来表是否释放,自动调用析构函数if (_n * 10 / _tables.size() == 7){size_t newSize = _tables.size() * 2;HashTable<K, V> 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);}//HashFuncHash hf;//哈希函数size_t hashi = hf(kv.first) % _tables.size();while (_tables[hashi]._s == EXIST)//如果存在就跳过{hashi++;hashi %= _tables.size();}//空位置或者删除位置就填写数据_tables[hashi]._kv = kv;_tables[hashi]._s = EXIST;_n++;return true;}HashDate<K, V>* Find(const K& key){Hash hf;size_t hashi = hf(key) % _tables.size();while (_tables[hashi]._s != EMPTY){if (_tables[hashi]._s == EXIST && _tables[hashi]._kv.first == key){return &_tables[hashi];}hashi++;hashi %= _tables.size();}return NULL;}bool Erase(const K& key){HashDate<K, V>* ret = Find(key);if (ret){ret->_s = DELETE;_n--;return true;}else{return false;}}void print(){for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]._s == EXIST){printf("[%d]-> %d\n", i, _tables[i]._kv.first);}else if (_tables[i]._s == EMPTY){printf("[%d]-> E\n", i);}else{printf("[%d]-> D\n", i);}}cout << endl;}size_t Size()const{return _n;}bool Empty() const{return _n == 0;}private:vector< HashDate<K, V>> _tables;size_t _n = 0;//存储关键字的个数;};void TsetHT1(){HashTable<int, int> ht;int a[] = { 4, 14, 24, 34, 5, 7, 1 };for (auto e : a){ht.Insert(make_pair(e, e));}//ht.print();ht.Insert(make_pair(3, 3));ht.Insert(make_pair(3, 3));ht.Insert(make_pair(-3, -3));ht.print();ht.Erase(3);ht.print();if (ht.Find(3)){cout << "3存在" << endl;}else{cout << "3不存在" << endl;}cout << ht.Size() << endl;}
}namespace Hash_bucket
{template<class T>struct HashNode{HashNode<T>* next;T _data;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 __HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> self;Node* _node;const HashTable<K, T, KeyOfT, Hash>* _pht;size_t _hashi;__HTIterator(Node* node, HashTable<K, T, KeyOfT, Hash>* pht, size_t hashi):_node(node), _pht(pht), _hashi(hashi){}__HTIterator(Node* node,const HashTable<K, T, KeyOfT, Hash>* pht, size_t hashi):_node(node), _pht(pht), _hashi(hashi){}//返回值为迭代器iterator.self& operator++(){//桶里面还有下一个结点if (_node->next){_node = _node->next;}else//当前桶已经走完那么就到下一个桶{_hashi++;while (_hashi < _pht->_tables.size()){if (_pht->_tables[_hashi]){_node = _pht->_tables[_hashi];break;}++_hashi;}if (_hashi == _pht->_tables.size()){_node = nullptr;}}return *this;}//Ref == T&Ref operator*(){return _node->_data;}//Ptr == T*Ptr operator->(){return &_node->_data;}bool operator!=(const self& s){return _node != s._node;}};template<class K, class T, class KeyOfT, class Hash>class HashTable{typedef HashNode<T> Node;template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>friend struct __HTIterator;public:typedef __HTIterator<K, T, T&, T*, KeyOfT, Hash> iterator;typedef __HTIterator<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{for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]){return iterator(_tables[i], this, i);}}return end();}const_iterator end()const{return 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& date){Hash hf;KeyOfT kot;//查找数据在否,不在就创建,在就不需要了.iterator it = Find(kot(date));if (it != end())return make_pair(it, false);//负载因子为1;if (_n == _tables.size()){vector<Node*> newTables;newTables.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 = hf(kot(cur->_data)) % newTables.size();cur->next = newTables[i];newTables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newTables);}size_t hashi = hf(kot(date)) % _tables.size();Node* newnode = new Node(date);//头插newnode->next = _tables[hashi];_tables[hashi] = newnode;++_n;return make_pair(iterator(newnode, this, hashi), true);}iterator Find(const K& key){Hash hf;KeyOfT kot;size_t hashi = hf(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 hf;KeyOfT kot;size_t hashi = hf(key) % _tables.size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur){if (kot(cur->_date) == 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 sum = 0;double averagebucketlen = 0.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("all bucketSize:%d\n", _tables.size());printf("bucketSize:%d\n", bucketSize);printf("maxBucketLen:%d\n", maxbucketLen);printf("averageBucketLen:%lf\n\n", averagebucketlen);}private:vector<Node*> _tables;size_t _n = 0;};
}

4.2 unordered_set代码:

#pragma once
#include"HashTable.h"namespace study2
{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, K, SetKeyOfT, Hash>::iterator iterator;typedef typename Hash_bucket::HashTable<K, K, SetKeyOfT, Hash>::const_iterator const_iterator;iterator begin(){return _hf.begin();}iterator end(){return _hf.end();}const_iterator begin()const{return _hf.begin();}const_iterator end()const{return _hf.end();}pair<const_iterator, bool> insert(const K& key){auto ret = _hf.Insert(key);return pair<const_iterator, bool>(const_iterator(ret.first._node, ret.first._pht, ret.first._hashi), ret.second);}iterator find(const K& key){return _hf.Find(key);}iterator erase(const K& key){return _hf.Erase(key);}private:Hash_bucket::HashTable<K, K, SetKeyOfT, Hash> _hf;};void test_set(){unordered_set<int> us;us.insert(5);us.insert(15);us.insert(52);us.insert(3);unordered_set<int>::iterator it = us.begin();while (it != us.end()){cout << *it << " ";++it;}cout << endl;for (auto e : us){cout << e << " ";}cout << endl;}
}

4.3 unordered_map的代码:

#pragma once
#include"HashTable.h"namespace study1
{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;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}pair<iterator, bool> insert(const pair<K, V>& kv){return  _ht.Insert(kv);}V& operator[](const K& key){pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));return ret.first->second;}const V& operator[](const K& key)const{pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));return ret.first->second;}iterator find(const K& key){return _ht.Find(key);}iterator earse(const K& key){return _ht.Erase(key);}private:Hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;};void test_map(){unordered_map<string, string> dict;dict.insert(make_pair("sort", ""));dict.insert(make_pair("string", ""));dict.insert(make_pair("insert", ""));for (auto& kv : dict){kv.second += 'x';cout << kv.first << ":" << kv.second << endl;}cout << endl;string arr[] = { "香蕉", "哈密瓜", "香蕉", "西瓜", "桃子" };unordered_map<string, int> cout_map;for (auto& e : arr){cout_map[e]++;}for (auto& kv : cout_map){cout << kv.first << ":" <<  kv.second << endl;}cout << endl;}
}

后言: 

        博主在这里讨个三联.xdm麻烦给博主个一键三联, 多支持支持,博主更加有动力更新更好的文章.谢谢xdm.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/640333.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SFP、SFP+、SFP28 与 QSFP28 收发器之间的差异:兼容性和性能

近年来&#xff0c;网络技术发展迅速&#xff0c;因此&#xff0c;计算专业人员面临着越来越令人困惑的术语和缩写词。 管理数据中心时必须了解的一个关键领域是收发器&#xff0c;特别是 SFP (1550nm/1310nm)、SFP (850nm) 和 QSFP28 (4x25G) 之间的差异。 这些型号在兼容性方…

绿联 安装Tomcat服务器

绿联 安装Tomcat服务器以及反代说明 1、tomcat版本 镜像名&#xff0c;根据项目决定选择哪个版本以及对应的JDK tomcat8.x.x&#xff1a; tomcat:8-jdk8 tomcat:8-jdk11 tomcat:8-jdk17 tomcat:8-jdk21 tomcat9.x.x&#xff1a; tomcat:9-jdk8 tomcat:9-jdk11 tomcat:9-jdk17 …

扫描工具nmap

介绍 说到黑客&#xff0c;知识就是力量。您对目标系统或网络的了解越多&#xff0c;可用的选项就越多。因此&#xff0c;在进行任何利用尝试之前&#xff0c;必须进行适当的枚举。 假设我们获得了一个 IP&#xff08;或多个 IP 地址&#xff09;来执行安全审计。在我们做任何…

《自动机理论、语言和计算导论》阅读笔记:p261-p314

《自动机理论、语言和计算导论》学习第 10 天&#xff0c;p261-p314总结&#xff0c;总计 48 页。 一、技术总结 1.generating & reachable 2.Chomsky Normal Form(CNF) 乔姆斯基范式。 3.pumping lemma 泵作用引理。引理&#xff1a;引理是数学中为了取得某个更好的…

FloodFill算法简介(用BFS、DFS算法解决)

FloodFill算法中文名&#xff1a;洪水灌溉 FloodFill通常是这样一类问题&#xff0c;如下图&#xff1a; 负数表示凹陷的土地&#xff0c;正数表示凸起的土地&#xff0c;发洪水/下雨会淹没凹陷的地方 通常会问这几种问题&#xff1a; 1.被淹没的区域有几块 2.被淹没的最大…

Transformer中的位置编码详解

什么是位置编码 位置编码概述 位置编码的目的是为了补充序列的位置信息&#xff0c;这是因为自注意力机制本身不包含位置的概念&#xff08;例如顺序信息&#xff09;。位置编码的具体作用是&#xff0c;对于不同的输入序列成分&#xff0c;赋予其不同的位置标识&#xff0c;确…

❤️新版Linux零基础快速入门到精通——第三部分❤️

❤️新版Linux零基础快速入门到精通——第三部分❤️ 非科班的我&#xff01;Ta&#xff01;还是来了~~~3. Linux权限管控3.1 认知root用户3.1.1 Switch User——su3.1.2 sudo命令3.1.3 为普通用户配置sudo认证 3.2 用户和用户组3.2.1 用户、用户组3.2.2 用户组管理3.2.3 用户管…

人工智能大模型培训老师叶梓 探索知识库问答中的查询图生成:处理多跳复杂问题的新方法

在人工智能领域&#xff0c;基于知识库的问答&#xff08;KBQA&#xff09;技术正变得越来越重要。它使得机器能够理解自然语言问题&#xff0c;并从结构化的知识库中检索答案。然而&#xff0c;面对多跳复杂问题&#xff0c;传统的KBQA方法往往力不从心。近期&#xff0c;研究…

网工不能不知道这10个工具,全都很好用!

你们好&#xff0c;我的网工朋友。 有阵子没做网工工具合集了&#xff0c;不少朋友私信我说“老杨怎么不一次性多放点安装包”。 所以有了今天这篇10个好用工具安利&#xff0c;这次尽可能把有的安装包都给你安排上了。 它们涵盖了从基础的网络监控、故障排查到高级的网络安…

那些早期的iax和SIP软电话软件界面,看看你见过几个?

目录 一些iax/sip软电话UI图片SIP软电话的界面怎么设计SIP软电话的功能有哪些 早期voip发展中&#xff0c;很多公司开发了自己的SIP软电话&#xff0c;有些已经不存在了&#xff0c;有些还在使用中&#xff0c;比如X-Lite&#xff0c;Zoiper等等&#xff0c;我们一起看看这些早…

基于SpringBoot的“论坛管理系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“论坛管理系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 论坛管理系统结构图 前台首页功能界面图 用户登录…

转行做银行测试,需要了解哪些?

在这个内卷严重的时代&#xff0c;银行的业务不断增加&#xff0c;随着软件信息化的要求越来越高&#xff0c;银行对软件测试人员也提出了非常高的要求。 银行的软件测试是针对银行的软件系统&#xff08;如柜面系统、信贷系统&#xff09;和银行专用设备&#xff08;如ATM机、…