通俗易懂->哈希表详解

目录

一、什么是哈希表?

1.1哈希表长什么样?

1.2为什么会有哈希表?

1.3哈希表的特点

1.3.1 取余法、线性探测

1.3.2 映射

1.3.3负载因子

1.4哈希桶

1.5闲散列与开散列

1.6总结

二、设计hash表

1、哈希表的设计

  1)插入

  2)查找

 3)删除

4)字符串哈希算法

2、封装map和set

1、完成对hash表的基础功能

2、完成封装

3、对应的迭代器

4、【】方括号重载

三、设计原码

1、HashTable

2、unordered_map

3、unordered_set

4、test


一、什么是哈希表?

什么是哈希表?

哈希表,顾名思义,就是一个表。

可是为什么叫哈希表?

因为这是从老美哪里音译过来的

叫做->Hash Table

翻译过来就是->哈希表

既然是表,那么

第一,这个哈希表长什么样子?

第二,为什么会有这个哈希表?

第三,这个哈希表用来做什么?

第三,这个哈希表的特点是什么?

第四,什么是取余法?

第五,什么是映射?

第六,什么是线性探测?

第七,什么是哈希桶?

一些常见的概念,是什么?要怎么理解?

下面一一我来解析。

1.1哈希表长什么样?

一般哈希表有两种形式(先别问为什么,先看,后面解释)

1.2为什么会有哈希表?

假设你有一个数组或者链表,

传统的数组访问某一个数据,或者链表访问某一个数据,必须遍历,也只有遍历。

假如你的数组长度为100万,你现在要取某个值,而你知道这个值在就在数组的中间。

怎么办?

此时,无论从前往后,还是从后往前遍历,都绕不过50万个值。

蛋不蛋疼?蛋疼。

难受不难受?难受。

如果数据规模更大,例如100亿,那更难受。

所以,有困难,有麻烦,就会引发思考:我能不能不用遍历,咔的一下,马上就找到这个值?

而传统的数据结构显然无法突破这个难题。

既然旧的不行,干脆,那就搞一个新的。

于是,天空一声巨响,哈希表闪亮登场!

所以,哈希表就是为了解决查找必须遍历的问题而生。

1.3哈希表的特点

如何做到不遍历直接访问到数据?

很简单,非常简单,简单到不能再简单。

举个例子,你有5个值:1, 2, 3, 4 ,10001

我在创建数组的时候,我直接申请10001个空间!

然后所有的数据直接存储在对应的下标位置。

(啊???)

现在你给我一个值,例如说10001。

要我去表里找,此时我不去遍历。

我直接就到数组下标为10001的位置取数据。

因为数组是支持随机访问的。

我直接初始位置+10001,直接就拿到数据了。

有毛病没有?没有。

有问题没有?没有。

你会说,是不是太浪费空间了?

是的,非常浪费。

但是,你就说我遍历了没有?没有。

你就说找到了没有?找到了。

你就说快不快?非常快,O(1)。

所以,解决我们原来要遍历的困扰没有?解决了。

但是,你会发现不对劲,空间太浪费了啊。

假设我有只有2个数据,一个是1,另一个是1000000000000000。

(嗯......?!)

难道我要建立那么大的空间,就为了存这俩货?

不行,很不行,非常不行。

那么,怎么办?

于是,取余法就来了。

1.3.1 取余法、线性探测

现在,我们回到文章开始的地方,哈希表的模样,其中一种是这样的:

为什么是这样的?

下面我们来解释:

假如,我们有以上数据:2,18,22,89,1000000000001

按照原来的办法,我们需要开辟10000000000001个空间,才能构成哈希表

即数据存储在对应位置

但是,这种方式不行,太浪费空间。

怎么办?取余法。

什么是取余法?

很简单,就是取余数。

例如,我们现在有5个数据,那么我们就开10个空间

然后,让这每一个数据%10,得到结果

再将这个结果放到对应的位置。

什么意思?

很简单,现在有6个数据:2,18,22,23,89,1000000000001

2%10 = 2,所以2存放在下标为2的位置

18%10 = 8,所以2存放在下标为8的位置

22%10 = 2,所以存放在下标为2的位置

但是,现在下标为2的位置已经有了一个值

而这个,就是所谓的哈希冲突!非常简单,非常好理解对不对?

是的,就是这么简单。不要被一些看似很牛b的概念给困住了。

所有的概念都只不过是为了更好的概括和综合某个现象,方便于你理解,仅此而已。

学习新事物,也是如此。

好的,那么,这个位置冲突了,怎么办?

那就放到后面没有冲突的位置。

下标为3的位置没有冲突,所以,放到3

23%10 = 3,所以23存放在下标为3的位置

但是,同样的,下标为3的位置已经有数据了。

即所谓哈希冲突了。

怎么办?

很简单,同样的道理,往后挪

挪到没有冲突的位置。

所以,往后下标4的位置没有数据,所以23存在下标为4的位置

89%10 = 9,所以89存放在下标为9的位置

1000000000001%10 = 1,所以1000000000001存放在下标为1的位置

于是,这6个数据,即使有数据为1000000000001那么大的值,

我们也仅仅用了10个空间就存储下来了。

那么,如何取出数据呢?

同样的,很简单,例如取18,将18%10=8,然后到下标为8的位置取即可。

但是,22呢?22%10=2,但是22却不在下标为2的位置。

怎么办呢?

往后找。

23呢?也不在下标为3的位置。

怎么办呢?

往后找。

如果当前位置找不到值,就往后挨个查找,直到找到

往后找,就像是探测,而且是一个一个探测,是线性的查找。

这就是所谓线性探测

所以,我们把这个表,就叫做线性探测表

非常简单。

1.3.2 映射

同时,你会发现:

22并不是存在下标为22的位置,而是存在下标为2的位置

1000000001也不是存在下标为1000000001的位置,而是存在下标为1的位置

这就是所谓映射

即,值与值之间的关联,一种关系。

22和下标为2的位置的映射关系。

1000000001和下标为1的位置的映射关系。

还可以这么理解:

你名字叫做张三,你的发小叫做李四。

别人叫张三,叫的是你,而不是你发小。

这就是映射,张三映射你,李四映射你发小。

这就是一对一映射

同时,还会有这一种情况:

你遇到了一个人,她名字也叫做张三!

现在,有人叫张三的名字,但是张三有两个人。

所以,张三映射对应两个人。

甚至,张三有很多很多,例如说张伟这个名字。

这就是一对多的映射

1.3.3负载因子

取余法,解决数据存放空间太大的问题

好了,现在我们已经解决了空间太大的问题。

但是,问题又来了。

什么问题?

例如,假设我们的数据老是冲突,怎么办?

这个时候的访问,就会偏离我们初始的目的,即不遍历

因为访问一个数据,老是要往后遍历,很麻烦

随着数据的增多,冲突的概率增加,查找的成本越来越高。

也就是说,问题源于数据太多,而空间不够

怎么办?

很简单,扩容。

那么,我们应该什么时候扩容呢?

很简单,用负载因子判断。

好,什么是负载因子?

负载因子就是数据个数所占整个空间的比率。

例如

10个空间,有2个数据

负载因子就是0.2

10个空间,有7个数据

负载因子就是0.7

所以,每插入一个位置,我们就让负载因子+1

而一般来说,负载因子达到0.7就要进行扩容。

1.4哈希桶

回到文章开始,我们说哈希表一般有两种形式,

一个叫做线性探测表,前面已经解释清楚。

另一个,叫做哈希桶。

长这个样子:

我们已经知道哈希桶长什么样子,下面我们来解释:

为什么要有哈希桶?

假如我有一组数据。

这一组数据是:2,22,222,2222,22222,222222,2222222,22222222........

好的,数据我给你了。

你存吧。

你想要怎么存?

如果按照线性探测表的方式进行存储:

好家伙,你一看数据,你发现

取余数,结果全是2

存一个冲突一个

存两个冲突两个

存三个冲突三个

.....

从头冲突到尾,没完没了。

怎么办?

很明显,线性探测形式的哈希表有着致命的弱点

即无法对余数相同的数据进行处理

冲突了,只有往后放

我的位置冲突了,我就去放别人的位置,让别人冲突

(没错,就是这么强大,你打我啊)

如此一来,当数据越来越多时,哈希冲突的概率将会越来越大

哈希冲突多了,就会导致查找的成本越来越高

哈希表的优势也会越来越微弱

怎么办?

于是,哈希桶来了。

我这么存:

我现在无论是存12,22,32,42,52还是222222222222

还会和其他位置冲突吗?

不会。

哈希桶将冲突的值,放到同一个位置下,用单链表管理起来

哈希桶的结构,极大的优化了线性探测表无法处理哈希冲突的缺点

同时,单链表的访问,其时间复杂度也控制在O(n)的量级。

非常棒。

哈希桶也存在负载因子的问题,

和线性探测负载因子是一样的逻辑

同样,也是大于0.7就进行扩容

扩容是对数组的扩容

而扩容之后,单链表的长度也会变短

为什么?

例如空间从10变为100

原来2,12,22,32,42,52,62,72,82,92等值都只能挂在下标2的位置

但是当空间变为100

瞬间,所有的值都有了自己对应的下标位置

原本长度为10的哈希桶,直接变为1

优化效果相当棒

这,就是哈希桶

1.5闲散列与开散列

1、闲散列:开放定址法(线性探测/二次探测)

二次检测:当数据比较集中的时候,查找会比较慢,

为了更快的查找,下一个位置查找偏移量不为1,可以为2次方

思路:我的位置被别人占了,我就去占别人得位置

冲突越多,效率越低

因此,有人提出了开散列

2、开散列:哈希桶/拉链法

1.6总结

综上,我们来总结一下:

1、值很分散,因此哈希表也叫做散列表

2、有些值不好映射,比如string,结构体等

3、开空间问题,即哈希冲突

4、哈希表是用哈希思想实现的一种数据结构

hash更多的来说,是一种思想

例如编码表也是一种哈希编码的运用

例如经典的ASCII码表

同时,有时候你会发现你打开某些文件,会出现乱码

为什么?

因为码表对错了,原本这个文件要拿表A来映射

结果拿成表B来映射了

所以结果就是乱码

乱码的本质是编码表对乱了

以上就是关于哈希表的基础概念和知识。

下面博主要带大家设计出一个简易版哈希表

即unordered_set和unordered_map

使用c++实现,总体还是比较难的

涉及模板、多层嵌套封装、泛型编程、内外部类、友元等

需要有一定的c++基础。

其实简单理解就够了,不必跟着我写出一个。

二、设计hash表

1、哈希表的设计

  1)插入

如果位置不被占,插入;如果位置被占,遇到空才插入      

插入逻辑:

先是数据%size,为什么是size而不是capacity呢?

因为capacity后面的的值是没法访问的,end位置是size前一个位置

然后找到空/被删除的位置,插入,n++

n是记录哈希表个数的值,为什么不用size呢?

因为hash表是离散的表

如果hash表数值很多,就有很大的概论发生冲突

怎么办?

设置一个负载因子,用来记录hash表内的数据个数的占比

一般是0.7~0.8

如果hash表满了呢?在找空/被删除位置时,就会出现死循环的问题

满了就扩容

扩容之后,原有的值不能拷贝,需要重新映射,新的hash表的size是原有空间的2倍

处理数据冗余:插入前利用find

  2)查找

遇到空才停止,但是如果中间位置有空位置,就查不到后面的位置,如何解决?

设置一个位置状态:EMPTY、DELETE,EXIST

 3)删除

删除的值不必抹除,而是将状态设置为DELETE。

如果抹除,设置为什么值都不合适

因此,删除是一个伪删除,查找值时依旧能找到

在Find函数需要特殊判断,即存在才查找

如果数据不能取%怎么办?

例如string数据类型

用仿函数进行解决

对于整型、浮点型、指针都进行size_t强转为整型,就可以取%

但是string不可以转化为整型

怎么办?

单独写一个为string转换的仿函数

这个仿函数返回string【0】

4)字符串哈希算法

把字符串转型为整型

将每一个字符的ASCII值*某个数值,累计加

最后每一个字符串的结果一般都不会重复,但是依旧会重复

string很常见,那可以对仿函数使用特化

什么是特化?就是对某些类进行特殊化处理

就是不适用模板推广,而是直接使用

2、封装map和set

1、完成对hash表的基础功能

一个哈希表要有的功能:

插入、删除、遍历(迭代器)

2、完成封装

插入、删除、查找的接口封装

3、对应的迭代器

        1)首先是一个迭代器对象,完成对象的简单框架

        哈希表的迭代器,需要哈希表本身,以及哈希表对应位置的节点

        其内部的哈希表,需要外部调用对象哈希表来初始化

        2)++重载

                1、先算出当前所在位置

                2、当前位置的下一个位置不为空,那就是有直接返回

                3、当前位置为空,要继续找后面的位置

4、【】方括号重载

返回值是一个pair,第一个参数是迭代器,第二个参数是bool,判断是否添加成功

如果已经存在,返回已存在节点的迭代器,以及false

如果节点不存在,返回新插入节点的迭代器,以及true

需要修改insert的返回值

以及find的返回值

这样就可以直接对find和insert进行复用

方括号重载返回值为value

三、设计原码

1、HashTable

#pragma once
#include<iostream>
#include<vector>
using namespace std;//设计一个hash表
//哈希表是一个vector数组
//节点存储的是一个pair节点
//使用模板编程//如果插入的是string,就不能取%
//怎么办?字符串哈希算法
//使用仿函数enum State
{EXIST,DELETE,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& key){size_t ret = 0;for (auto ch : key){ret *= 131;ret += ch;}return ret;}
};namespace hash
{//哈希表节点template<class K, class V>struct HashData{pair<K, V> _kv;State _state = EMPTY;};//定义哈希表template<class K, class V, class  Hash = hashFunc<K>>class HashTable{public:typedef HashData<K, V> Node;HashTable(){_tables.resize(10);}//插入bool Insert(const pair<K, V>& kv){if (Find(kv.first)){return false;}//检查扩容if (_n / _tables.size() >= 0.7){size_t newSize = _tables.size() * 2;HashTable<K,V,Hash> newHashTable;newHashTable._tables.resize(newSize);//重新插入新空间for (size_t i = 0; i < _tables.size(); ++i){newHashTable.Insert(_tables[i]._kv);}}//1、找到插入的位置,取%Hash hs;size_t hashi = hs(kv.first) % _tables.size();while (_tables[hashi]._state != EMPTY){++hashi;}//3、插入,更新负载因子_tables[hashi]._kv = kv;_tables[hashi]._state = EXIST;_n++;return true;}//查找Node* Find(const K& key){Hash hs;size_t hashi = hs(key) % _tables.size();while (_tables[hashi]._state != EMPTY){if (_tables[hashi]._state == EXIST  &&_tables[hashi]._kv.first ==  key)  {return &_tables[hashi];}++hashi;hashi %= _tables.size();}return nullptr;}size_t size(){return _n;}//删除bool Erase(const K& key){Node* cur = Find(key);if (cur){cur->_state = DELETE;--_n;return true;}return false;}private:vector<HashData<K, V>> _tables;size_t _n = 0;};void HashTest(){HashTable<int, int> ht;ht.Insert({ 1,1 });ht.Insert({ 2,2 });ht.Insert({ 3,3 });ht.Insert({ 4,4 });ht.Insert({ 3,3 });ht.Insert({ 3,3 });cout << ht.Find(1) << endl;cout << ht.Find(2) << endl;cout << ht.Find(3) << endl;cout << ht.Find(4) << endl;cout << ht.size() << endl;ht.Erase(1);ht.Erase(2);cout << ht.size() << endl;}void HashTest1(){HashTable<string, int> ht;ht.Insert({ "abcd",1});ht.Insert({ "edasdfas",2});ht.Insert({ "kahkahdk",3});ht.Insert({ "ohjahsflhasf",4});cout << ht.size() << endl;size_t ret = hashFunc<string>()("dasldfhalf");size_t ret1 = hashFunc<string>()("sad");cout << ret << endl;cout << ret1 << endl;}
}//哈希桶实现
namespace hash_bucket
{//哈希节点,是一个链表template<class T>struct HashNode{T _data;HashNode* _next;HashNode(const T& data):_data(data), _next(nullptr){}};前置声明//template<class K, class T, class Hash, class KeyOfT>//class HashTable;实现迭代器//template<class K, class T, class Hash, class KeyOfT>//struct __HSIterator//这个是给hash表底层使用的迭代器对象//{//	typedef HashNode<T> Node;//	typedef  HashTable<K, T, Hash, KeyOfT> Self;//	HashTable<K, T, Hash, KeyOfT>* _pht;//	Node* _node;//	__HSIterator(const Node* node, const HashTable<K, T, Hash, KeyOfT>* pht)//		:_pht(pht)//		,_node(node)//	{//	}//	//++//	Self& operator++()//返回结构体对象//	{//		KeyOfT kot;//		Hash hs;//		size_t hashi = hs(kot(_node->_data)) % _pht->_tables.size();//		Node* cur = _pht->_tables[hashi];//		//		if (_node->_next)//		{//			_node = _node->_next;//		}//		else//该节点为空//		{//			while (hashi < _pht->_tables.size())//			{//				if (cur == nullptr)//				{//					++hashi;//				}//				else//				{//					break;//				}//			}//			if (hashi == _pht->_tables.size())//			{//				_node = nullptr;//			}//			else//			{//				_node = _pht->_tables[hashi];//			}//		}//		//		return *this;//	}//	//解引用*//	T& operator*()//	{//		return _node->_data;//	}//	T* operator->()//	{//		return &_node->_data;//	}//	bool operator!=(const Self& s)//	{//		return _node != s._node;//	}//};//哈希表template<class K, class T, class Hash , class KeyOfT>class HashTable{public:友元声明(但是这种方式的代码过于冗余)//template<class K, class T, class Hash, class KeyOfT>//friend struct __HSIterator;//内部类template<class Ref, class Ptr>struct __HSIterator//这个是给hash表底层使用的迭代器对象{typedef HashNode<T> Node;typedef  __HSIterator Self;const HashTable* _pht;Node* _node;__HSIterator( Node* node, const HashTable* pht):_pht(pht), _node(node){}//++Self& operator++()//返回结构体对象{if (_node->_next){_node = _node->_next;}else//该节点为空{KeyOfT kot;Hash hs;size_t hashi = hs(kot(_node->_data)) % _pht->_tables.size();++hashi;while (hashi < _pht->_tables.size()){if (_pht->_tables[hashi])break;++hashi;}if (hashi == _pht->_tables.size()){_node = nullptr;}else{_node = _pht->_tables[hashi];}}return *this;}//解引用*Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}};typedef __HSIterator<T&, T*> iterator;iterator begin(){//找到第一个非空节点for (int i = 1;i< _tables.size(); ++i){if (_tables[i]){return  iterator(_tables[i], this);}}return end();}iterator end(){//直接是空return  iterator(nullptr, this);}typedef HashNode<T> Node;public:HashTable(){_tables.resize(10,nullptr);}//插入pair<iterator, bool> Insert(const T& data){Hash hs;//取模仿函数KeyOfT kot;//取key仿函数iterator it = Find(kot(data));if (it._node != nullptr)//存在节点{return make_pair(it, false);}//扩容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 = hs(kot(cur->_data)) % newTable.size();//头插cur->_next = newTable[hashi];newTable[hashi] = cur;cur = next;}}_tables.swap(newTable);}size_t hashi = hs(kot(data)) % _tables.size();Node* newNode = new Node(data);newNode->_next = _tables[hashi];_tables[hashi] = newNode;++_n;return make_pair(iterator(newNode, this), true);}//查找iterator Find(const K& key){Hash hs;KeyOfT kot;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){return iterator(cur, this);}cur = cur->_next;}return  iterator(nullptr, this);}size_t size() {return _n;}//删除bool Erase(const K& key){KeyOfT kot;KeyOfT hs;Node* prev = nullptr;size_t hashi = hs(key) % _tables.size();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;return true;}else{prev = cur;cur = cur->_next;}}return false;}private: vector<Node*> _tables;size_t _n = 0;};//void test_hash_bucket1()//{//	HashTable<string, int> hb;//	hb.Insert({"asada",1});//	hb.Insert({"DASDAS",1});//	hb.Insert({"DASDAS",1});//	hb.Insert({"DASDAS",1});//	hb.Insert({"DASDAS",1});//	hb.Insert({"DASDAS",1});//	hb.Insert({"DASbAS",1});//	hb.Insert({"HHDH",1});//	//cout << hb.Erase("") << endl;//	cout << hb.size() << endl;//	cout << hb.Find("asada") << endl;//	cout << hb.Find("DASDAS") << endl;//	cout << hb.Find("HHDH") << endl;//	//}//void test_hash_bucket2()//{//	vector<int> v = {1,2,3,4,5,65,6,7,8,9,90,0,2,3,23,45,232};//	HashTable<int, int> hb;//	for (size_t i = 0; i<v.size(); ++i)//	{//		if (i  == 9)//		{//			int j = 0;//		}//		hb.Insert(make_pair(v[i],v[i]));//	}//	cout << hb.size() << endl;//}}

2、unordered_map

#pragma once
#pragma once
#include"HashTable.h"namespace my_unordered_map {template<class K, class V, class Hash = hashFunc<K> >class unordered_map{public://仿函数dstruct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};typedef typename hash_bucket::HashTable<K, pair<const K, V>, Hash, MapKeyOfT>::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);}iterator find(const K& key){return _ht.Find(key);}bool erase(const K& key){return _ht.Erase(key);}//方括号重载V& operator[](const K& key){pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));//V()为匿名对象return ret.first->second;}private:hash_bucket::HashTable<K,  pair<const K, V>, Hash, MapKeyOfT> _ht;};void test_unordered_map(){unordered_map<int,int> um;um.insert({1,1});um.insert({2,1});um.insert({3,1});um.insert({4,1});um.insert({5,1});um.insert({6,1});um.find(1);//cout << um.find(2) << endl;//cout << um.find(200) << endl;}void test_unordered_map1(){unordered_map<int, int> um;um.insert({ 1,1 });um.insert({ 2,1 });um.insert({ 3,1 });um.insert({ 4,1 });um.insert({ 5,1 });um.insert({ 6,1 });um[7];um[8];um[9];um[10];um[11]++;unordered_map<int, int>::iterator it = um.begin();while (it != um.end()){cout << it->first << " ";++it;}cout << endl;}void test_unordered_map2(){string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉","苹果","草莓", "苹果","草莓" };unordered_map<string, int> countMap;for (auto& e : arr){countMap[e]++;}for (auto e : countMap){cout << e.first << ":" << e.second << endl;}cout << endl;}};

3、unordered_set

#pragma once
#include"HashTable.h"namespace my_unorded_set {template<class K, class Hash = hashFunc<K> >class unorded_set {public:struct SetKeyOfT{const K& operator()(const K& key){return key;}};typedef typename hash_bucket::HashTable< K,const K, Hash, SetKeyOfT>::iterator iterator;iterator beigin(){return _ht.begin();}iterator end(){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, Hash, SetKeyOfT> _ht;};void test_unordered_set(){unorded_set<int> us;us.insert(1);us.insert(2);us.insert(3);us.insert(4);us.insert(6);us.insert(7);//cout << us.find(1) << endl;//cout << us.find(100) << endl;cout << "//" << endl;cout << us.erase(100) << endl;cout << us.erase(1) << endl;}void test_unordered_set1(){unorded_set<int> us;us.insert(99);us.insert(4);us.insert(6);us.insert(7);us.insert(9);us.insert(2);us.insert(3);us.insert(11);us.insert(1);us.insert(96);us.insert(16);us.insert(56);us.insert(50);us.insert(57);us.insert(58);us.erase(11);unorded_set<int>::iterator it = us.beigin();while (it != us.end()){cout << *it << " ";++it;}cout << endl;}};

4、test

#include"unordered_set.h"
#include"unordered_map.h"
int main()
{//hash_bucket::test_hash_bucket1();cout << "unordered_set" << ":";my_unorded_set::test_unordered_set1();cout << "unordered_map" << ":";my_unordered_map::test_unordered_map1();cout << "unordered_map方括号重载测试" << ":" << endl;;my_unordered_map::test_unordered_map2();return 0;
}

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

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

相关文章

Nginx实战:日志打印自定义请求头

nginx的日志可以打印很多内容&#xff0c;但是有时候自定义的请求头该怎么打印呢&#xff1f;像下面这种场景&#xff1a; 其实很简单&#xff0c;设置日志打印格式log_format的时候&#xff0c;自定义的请求头用 【$http_自定义请求头名】 的格式就可以打印出来 例如你的自定义…

如何掌握Spring事件发布和监听机制?

在软件设计和开发过程中&#xff0c;事件驱动是一种非常主流的架构模式&#xff0c;它的基本组成见下图&#xff0c;可以看到存在一个事件中心&#xff0c;而各个服务可以执行事件发布、订阅和消费等基本过程。 事件驱动架构代表的是一种架构设计风格&#xff0c;实现方法和工具…

[图解]企业应用架构模式2024新译本讲解05-表模块2

1 00:00:00,970 --> 00:00:03,440 接下来&#xff0c;我们就来看案例了 2 00:00:03,690 --> 00:00:09,260 案例跟上一次事务脚本案例是一样的 3 00:00:11,210 --> 00:00:13,130 也是收入确认这个案例 4 00:00:14,460 --> 00:00:15,580 这是表结构 5 00:00:15,7…

【Android】手动下载gradle插件包,解决gradle插件包下载不全问题。

问题描述 拉取别人的项目时&#xff0c;因为网络问题gradle插件包一直下载不全&#xff0c;一直build。 解决方案&#xff1a; 打开gradle>wrapper文件下gradle-wrapper.properties&#xff0c;查看需要下载gradle-7.2-bin.zip。 distributionBaseGRADLE_USER_HOME distr…

cmd窗口输出内容乱码问题

出现这样的问题是因为编码格式和解码格式不一样导致的&#xff0c;cmd窗口的默认解码格式为GBK&#xff0c;如想修改cmd默认编码格式可以按照下面步骤操作&#xff1a;打开cmd窗口输入&#xff1a;chcp 65001 65001指的是utf-8编码如果不清楚编码对应的 页面编码是是多少&#…

访问github加速方法续集dev-sidecar

访问github加速方法续集dev-sidecar dev-sidecar 重要提醒 ------------------------------重要提醒1--------------------------------- 注意&#xff1a;由于electron无法监听windows的关机事件&#xff0c;开着ds情况下直接重启电脑&#xff0c;会导致无法上网&#xff0c…

【网络层】IP地址基础 与 子网掩码

文章目录 IP地址基础IP地址概念IP地址分类公网地址和私网地址 子网掩码子网掩码作用默认子网掩码网络地址、主机地址、广播地址 IP地址基础 IP地址概念 IP地址&#xff1a;IP Address 在网络中&#xff0c;通信节点都需要有一个IP地址 IP地址以点分十进制表示&#xff0c;有…

建设人工智能平台,主流GPU卡选型分析

国内外主流GPU卡性能分析&#xff01;2024&#xff01; 大模型兴起助推算力需求激增 2024年&#xff0c;深度学习与人工智能技术飞速跃进&#xff0c;Transformer、GPT-3等大模型在自然语言处理、图像识别、语音合成等领域大放异彩&#xff0c;开启AI新纪元。其庞大的参数与数…

Matlab操作Excel筛选指定数据的对应数据

Matlab中在表格中寻找指定汉字&#xff0c;并返回其所在行数&#xff0c; 将该行数的另一列提取出来。 目录 一、前言 二、直接在命令行输出 三、保存筛选数据excel 一、前言 源数据excel&#xff1a; 指定汉子&#xff1a;买&#xff0c;得到下面数据&#xff1a; 二、直接…

微信小程序埋点监听方案

场景&#xff1a;运营小程序&#xff0c;需要根据系统访问情况来做决策时。 后台&#xff1a;开发可配置监听页面路径&#xff0c;参数&#xff0c;事件名称等&#xff0c;类似以下格式&#xff0c;进行json保存。 组装数据接口返回给前端缓存到本地&#xff0c;然后进行校验编…

MyBatis延迟加载缓存分页逆向工程

文章目录 延迟加载概述步骤 缓存一级缓存介绍原理 二级缓存介绍 设置缓存对象策略原理开启步骤属性解释是否使用一级缓存 分页插件使用步骤 逆向工程介绍搭建使用增删修改查 延迟加载 概述 延迟加载本身是依赖于多表查询的 延迟加载中返回值要选择resultMap返回的结果一定是D…

数学建模 —— 插值与拟合(1)

一、matlab画图 1.1 plot&#xff08;二维图形&#xff09; plot(x) —— 缺省自变量绘图格式 plot(x,y) —— 基本格式&#xff0c;以y(x)的函数关系作出直角坐标图&#xff0c;如果y为nm的矩阵&#xff0c;则以x为自变量&#xff0c;作出m条曲线 plot(x1,y1,x2,y2,…,xn,…

神经网络算法详解与前沿探索

神经网络算法详解与前沿探索 随着人工智能技术的迅猛发展&#xff0c;神经网络成为机器学习领域的重要组成部分&#xff0c;广泛应用于图像识别、自然语言处理和推荐系统等。本文将详细探讨神经网络的基本原理、结构、训练过程及其应用实例&#xff0c;并扩展至更多相关领域和…

基于标准库的STM32的外部中断EXTI

毕设已经告一段落了&#xff0c;接下来准备开始整理一下毕设中用到的知识与技术细节&#xff0c;今天整理的是STM32从编码器获取数据的方式-----外部中断&#xff08;EXTI&#xff09;&#xff1a; 外部中断分为四个硬件相关外设&#xff0c;GPIO/AFIO/EXTI/NVIC&#xff08;E…

戴尔向“数”而行,以“质”致远,做新质生产力的躬耕者

【全球存储观察 &#xff5c; 热点关注】 自1984年戴尔成立&#xff0c;一路走来&#xff0c;戴尔科技集团40年长期持续的技术创新&#xff0c;一直引领全球科技行业的技术趋势。 到如今&#xff0c;AIGC风行一时&#xff0c;在重塑千行百业的同时&#xff0c;也加速了科技行业…

选择算法之冒泡排序【图文详解】

P. S.&#xff1a;以下代码均在VS2019环境下测试&#xff0c;不代表所有编译器均可通过。 P. S.&#xff1a;测试代码均未展示头文件stdio.h的声明&#xff0c;使用时请自行添加。 博主主页&#xff1a;LiUEEEEE                        …

北斗应急救援终端如何做好汛期重点行业安全防控?

【安全提示】 汛期各地高温多雨、极端天气增多 防汛和安全生产形势严峻复杂如何做好汛期重点行业企业安全生产风险防控&#xff1f; 顶坚北斗短报文终端V1单北斗定位终端 北斗应急救援终端在汛期重点行业安全防控中扮演着关键角色&#xff0c;其高可靠性、稳定性和丰富的功能扩…

达摩院AI早癌筛查技术闪耀联合国,癌症早治时代来临?

全文预计1200字左右&#xff0c;预计阅读需要6分钟。 5月30日&#xff0c;在日内瓦举行的联合国AI大会上&#xff0c;各国的代表性企业拿出最优秀的AI成果进行分享。其中代表中国的是阿里巴巴集团的下属机构-达摩院&#xff0c;其在现场播放的一段影像&#xff0c;让在场的所有…

uniApp子组件监听数据的变化的方法之一

props:{//用来接收外界传递过来的数据swiperList:{type:Array,default:[]}}, swiperList&#xff1a;是父组件传递过来的值 通过 watch 监听&#xff08;在父组件中也同样可以使用&#xff0c;跟VUE的监听数据变化同理&#xff09; watch:{//监听组件中的数据变化swiperList(ol…

Diffusion Facial Forgery (DiFF) ——一个新的大规模人脸伪造检测数据集

1. 概述 近年来&#xff0c;条件扩散模型&#xff08;CDM&#xff09;在图像生成领域备受关注。它能够通过简单的输入&#xff08;如自然语言提示&#xff09;生成令人惊讶的忠实图像。然而&#xff0c;这一进步也引发了新的安全和隐私问题。例如&#xff0c;怀有恶意的个人现…