C++ 哈希

news2024/11/19 1:52:26

目录

1. 哈希概念

2. 哈希冲突

3. 哈希函数

4. 哈希冲突解决

4.1 闭散列

4.2 开散列

4.3 对于哈希表的补充

5. 开散列与闭散列比较

6. 哈希表的模拟实现以及unorder_set和unorder_map的封装


1. 哈希概念

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较顺序查找时间复杂度为O(N),平衡树中为树的高度,即 O(log_2 N),搜索的效率取决于搜索过程中元素的比较次数。

理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立 一一映射的关系,那么在查找时通过该函数可以很快找到该元素

插入元素时: 根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放

搜索元素时:对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功

该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)

哈希函数设置为:hash(key) = key % capacity; capacity为存储元素底层空间总的大小。

用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快


2. 哈希冲突

在上面的例子中hash(4) = 4%10 = 4,hash(14) = 14%10 = 4,hash(4) == hash(14)。

不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突哈希碰撞

把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”


3. 哈希函数

引起哈希冲突的一个原因可能是:哈希函数设计不够合理

哈希函数设计原则:

1. 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间

2. 哈希函数计算出来的地址能均匀分布在整个空间中

3. 哈希函数应该比较简单

常见哈希函数

1.. 直接定址法

取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B

优点:简单、均匀         缺点:需要事先知道关键字的分布情况

使用场景:适合查找比较小且连续的情况

2. 除留余数法

设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数, 按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址

注意:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突


4. 哈希冲突解决

解决哈希冲突两种常见的方法是:闭散列和开散列

4.1 闭散列

闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有 空位置,那么可以把key存放到冲突位置中的 “下一个” 空位置中去

1. 线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

插入:

通过哈希函数获取待插入元素在哈希表中的位置

如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突, 使用线性探测找到下一个空位置,插入新元素

删除:

采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。比如删除元素4,如果直接删除掉,14查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素

// 哈希表每个空间给个标记 EMPTY此位置空, EXIST此位置已经有元素, DELETE元素已经删除

enum State{EMPTY, EXIST, DELETE};

扩容:散列表的载荷因子:e = 填入表中的元素个数 / 散列表的长度

e的值越大,发生哈希冲突的可能性越大

线性探测优点:实现非常简单

线性探测缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低

2. 二次探测:从发生冲突的位置开始,向后探测,但不是依次探测,而是每次向后找i^2(i = 1,2,3……)位置

研究表明:当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任 何一个位置都不会被探查两次。

闭散列最大的缺陷就是空间利用率比较低,这也是哈希的缺陷。

闭散列的实现:

enum state
{
	EMPTY,
	EXITE,
	DELETE
};

template<class K, class V>
struct Hash_Ndoe
{
	//Hash_Ndoe(const pair<K, V>& kv)
	//	:_state(EMPTY),
	//	_kv(kv)
	//{}

	pair<K, V> _kv;
	state _state = EMPTY;

};

template<class K, class V>
class Hash
{
	typedef Hash_Ndoe<K, V> Node;
public:

	bool Insert(const pair<K, V>& kv)
	{
		//判断空间是否足够
		size_t _v_size = _v.size();
		if (_v_size == 0)
		{
			_v.resize(10);
		}
		else if (_n * 10 / _v_size > 7)
		{
			size_t newsize = _v_size * 2;
			Hash<K, V> newhash;
			newhash._v.resize(newsize);
			for (int i = 0; i < _v_size; ++i)
			{
				if (_v[i]._state == EXITE)
					newhash.Insert(_v[i]._kv);
			}
			_v.swap(newhash._v);
		}

		_v_size = _v.size();
		//找到对应位置
		size_t index = kv.first % _v_size;
		//线性探测
		int x = 1;
		while (_v[index]._state == EXITE)
		{
			if (_v[index]._kv.first == kv.first)
				return false;
			index = index + x;
			index %= _v_size;
		}
		_v[index]._kv = kv;
		_v[index]._state = EXITE;
		_n++;
		return true;

	}

	Node* Find(const K& key)
	{
		size_t _v_size = _v.size();
		if (_v_size == 0)
			return nullptr;
		size_t index = key % _v_size;
		size_t flag = index;
		int x = 1;
		while (_v[index]._state != EMPTY)
		{
			if (_v[index]._state == EXITE && _v[index]._kv.first == key)
				return &_v[index];
			index = index + x;
			index %= _v_size;
			if (flag == index)
				break;
		}
		return nullptr;
	}

	bool Erase(const K& key)
	{
		Node* ret = Find(key);
		if (ret == nullptr)
			return false;

		ret->_state = DELETE;
		_n--;
		return true;
	}


private:
	vector<Node> _v;
	size_t _n = 0;
};

4.2 开散列

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

从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素

3. 开散列增容 桶的个数是一定的,随着元素的不断插入,每个桶中元素的个数不断增多,极端情况下,可 能会导致一个桶中链表节点非常多,会影响的哈希表的性能,因此在一定条件下需要对哈希 表进行增容,那该条件怎么确认呢?

开散列最好的情况是:每个哈希桶中刚好挂一个节点, 再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,可以给哈希表增容。e == 1

4.3 对于哈希表的补充

1. key的类型只能是能被%的类型,那其他类型怎么办

开一个模板接口,传仿函数,仿函数的返回值为整形

2. 如果一个类型没有重载==,或者类型的==并不是你想要的,怎么找到对应的值

再开一个模板接口,传仿函数,仿函数的返回值为整形

3. 除留余数法,最好模一个素数

template<class K, class V>
struct HashTableNode
{
	pair<K, V> _kv;
	HashTableNode<K, V>* _next;

	HashTableNode(const pair<K, V>& kv)
		:_kv(kv),
		_next(nullptr)
	{}
};
	
template<class T>
struct HashFcn
{
	size_t operator()(const T& x)
	{
		return x;
	}
};

template<>
struct HashFcn<string>
{
	size_t operator()(const string& s)
	{
		size_t ret = 0;
		for (auto ch : s)
		{
			ret += ch;
			ret *= 31;
		}
		return ret;
	}
};

template<class K, class V, class HashFcn = HashFcn<K>>
class HashTable
{
	typedef HashTableNode<K, V> Node;
public:
	HashTable() {}

	HashTable(const HashTable<K, V>& hs)
		:_n(hs._n)
	{
		size_t _v_size = hs._v.size();
		_v.resize(_v_size);

		for (size_t i = 0; i < _v_size; ++i)
		{
			Node* cur1 = hs._v[i];
			Node* prev = nullptr;
			while (cur1)
			{
				Node* newnode = new Node(cur1->_kv);
				if (prev)
				{
					prev->_next = newnode;
				}
				else
				{
					_v[i] = newnode;
				}
					
				cur1 = cur1->_next;
				prev = newnode;
			}
		}
	}

	~HashTable()
	{
		for (auto& cur : _v)
		{
			while (cur)
			{
				Node* next = cur->_next;
				delete cur;
				cur = next;
			}
		}
	}

		

	size_t GetNextSize(size_t num)//质数
	{
		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
		};
		size_t i = 0;
		for (; i < __stl_num_primes; ++i)
		{
			if (__stl_prime_list[i] > num)
				return __stl_prime_list[i];
		}

		return __stl_prime_list[i];
	}

	bool Insert(const pair<K, V>& kv)
	{
		if (Find(kv.first))
			return false;


		HashFcn hashfcn;
		//扩容
		size_t _v_size = _v.size();
		if (_v_size == _n)
		{
			size_t newsize = GetNextSize(_v_size);
			vector<Node*> newtable(newsize, nullptr);
				
			for (auto& cur : _v)//cur类型 Node*&
			{
				while (cur)
				{
					Node* next = cur->_next;//记录当前节点的下一个

					size_t newHashTablei = hashfcn(cur->_kv.first) % newsize;//计算新表的插入位置
					//头插
					cur->_next = newtable[newHashTablei];//连尾
					newtable[newHashTablei] = cur;//改头

					cur = next;//cur更新,连带更改cur的值****
				}
			}
			_v.swap(newtable);
		}


		//头插
		_v_size = _v.size();
		size_t HashTablei = hashfcn(kv.first) % _v_size;
		Node* newnode = new Node(kv);
		newnode->_next = _v[HashTablei];
		_v[HashTablei] = newnode;
		_n++;
		return true;
	}

	Node* Find(const K& key)
	{
		HashFcn hashfcn;
		size_t _v_size = _v.size();
		if (_v_size == 0 || _n == 0)
			return nullptr;

		size_t HashTablei = hashfcn(key) % _v_size;
		Node* cur = _v[HashTablei];
		while (cur)
		{
			if (cur->_kv.first == key)
				return cur;
			else
				cur = cur->_next;
		}
		return nullptr;
	}

	bool Erase(const K& key)
	{
		HashFcn hashfcn;
		size_t _v_size = _v.size();
		if (_v_size == 0 || _n == 0)
			return false;

		size_t HashTablei = hashfcn(key) % _v_size;
		Node* cur = _v[HashTablei];
		Node* prev = nullptr;
		while (cur)
		{
			if (cur->_kv.first == key)
			{
				if (prev == nullptr)
				{
					_v[HashTablei] = cur->_next;
				}
				else
				{
					prev->_next = cur->_next;
				}
				delete cur;
				_n--;
				return true;
			}
			else
			{
				prev = cur;
				cur = cur->_next;
			}
		}
		return false;

	}


private:
	vector<Node*> _v;
	size_t _n = 0;
};

5. 开散列与闭散列比较

应用链地址法处理溢出,需要增设链接指针,似乎增加了存储开销。事实上: 由于开地址法必须保持大量的空闲空间以确保搜索效率,如二次探查法要求装载因子a <= 0.7,而表项所占空间又比指针大的多,所以使用链地址法反而比开地址法节省存储空间


6. 哈希表的模拟实现以及unorder_set和unorder_map的封装

hashtable.h

#pragma once
#include<assert.h>

template<class T>
struct HashFcn
{
	size_t operator()(const T& x)
	{
		return x;
	}
};

template<>
struct HashFcn<string>
{
	size_t operator()(const string& s)
	{
		size_t ret = 0;
		for (auto ch : s)
		{
			ret += ch;
			ret *= 31;
		}
		return ret;
	}
};

template<class T>
struct Equal_Key
{
	bool operator()(const T& key1, const T& key2)
	{
		return key1 == key2;
	}
};

namespace HashOpen
{
	template<class V>
	struct HashTableNode
	{
		typedef V value_type;

		value_type _data;
		HashTableNode<V>* _next;

		HashTableNode(const value_type& data)
			:_data(data),
			_next(nullptr)
		{}
	};

	template<class K, class V, class KeyOfValue, class HashFcn, class Pred>
	class HashTable;

	template<class K, class V, class Ptr, class Ref, class KeyOfValue, class HashFcn, class Pred>
	struct __hash_iterator
	{
		typedef HashTableNode<V> Node;
		typedef HashTable<K, V, KeyOfValue, HashFcn, Pred> Hash;
		typedef __hash_iterator<K, V, Ptr, Ref, KeyOfValue, HashFcn, Pred> Self;
		typedef __hash_iterator<K, V, V*, V&, KeyOfValue, HashFcn, Pred> iterator;

		Node* _cur;
		const Hash* _hs;

		__hash_iterator(Node* cur, const Hash* hs)
			:_cur(cur),
			_hs(hs)
		{}

		__hash_iterator(const iterator& it)
			:_cur(it._cur),
			_hs(it._hs)
		{}

		Ref operator*()
		{
			return _cur->_data;
		}
		Ptr operator->()
		{
			return &operator*();
		}
		bool operator!=(const Self& t)
		{
			return _cur != t._cur;
		}

		Self& operator++()
		{
			KeyOfValue kov;
			HashFcn hash;
			if (_cur == nullptr)
			{
				assert(1);
				return *this;
			}

			Node* next = _cur->_next;
			if (next == nullptr)
			{
				size_t _v_size = _hs->_v.size();
				size_t hashi = hash(kov(_cur->_data)) % _v_size;
				++hashi;
				_cur = nullptr;
				for (size_t i = hashi; hashi < _v_size; ++hashi)
				{
					if (_hs->_v[hashi])
					{
						_cur = _hs->_v[hashi];
						break;
					}
				}
			}
			else
			{
				_cur = next;
			}
			return *this;
		}

	};

	template<class K, class V, class KeyOfValue, class HashFcn, class Pred>
	class HashTable
	{
		template<class K, class V, class Ptr, class Ref, class KeyOfValue, class HashFcn, class Pred>
		friend struct __hash_iterator;
		typedef HashTableNode<V> Node;
		typedef HashTable<K, V, KeyOfValue, HashFcn, Pred> Self;
		typedef K key_type;
		typedef V value_type;
	public:
		typedef __hash_iterator<K, V, V*, V&, KeyOfValue, HashFcn, Pred> iterator;
		typedef __hash_iterator<K, V, const V*, const V&, KeyOfValue, HashFcn, Pred> const_iterator;

		HashTable() {}

		HashTable(const Self& hs)
			:_n(hs._n)
		{
			size_t _v_size = hs._v.size();
			_v.resize(_v_size);

			for (size_t i = 0; i < _v_size; ++i)
			{
				Node* cur1 = hs._v[i];
				Node* prev = nullptr;
				while (cur1)
				{
					Node* newnode = new Node(cur1->_data);
					if (prev)
					{
						prev->_next = newnode;
					}
					else
					{
						_v[i] = newnode;
					}
					
					cur1 = cur1->_next;
					prev = newnode;
				}
			}
		}

		~HashTable()
		{
			for (auto& cur : _v)
			{
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
			}
		}

		iterator begin()
		{
			size_t _v_size = _v.size();
			for (size_t i = 0; i < _v_size; ++i)
			{
				if (_v[i])
					return iterator(_v[i], this);
			}
			return end();
		}

		iterator end()
		{
			return iterator(nullptr, this);
		}

		const_iterator begin() const
		{
			size_t _v_size = _v.size();
			for (size_t i = 0; i < _v_size; ++i)
			{
				if (_v[i])
					return const_iterator(_v[i], this);
			}
			return end();
		}

		const_iterator end() const
		{
			return const_iterator(nullptr, this);//const this
		}

		size_t GetNextSize(size_t num)//质数
		{
			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
			};
			size_t i = 0;
			for (; i < __stl_num_primes; ++i)
			{
				if (__stl_prime_list[i] > num)
					return __stl_prime_list[i];
			}

			return __stl_prime_list[i];
		}

		pair<iterator, bool> Insert(const value_type& data)
		{
			KeyOfValue kov;
			iterator it = Find(kov(data));
			if (it._cur)
				return make_pair(it, false);


			HashFcn hashfcn;
			//扩容
			size_t _v_size = _v.size();
			if (_v_size == _n)
			{
				//size_t newsize = GetNextSize(_v_size);
				size_t newsize = _v_size == 0 ? 10 : _v_size * 2;
				vector<Node*> newtable(newsize, nullptr);
				
				for (auto& cur : _v)//cur类型 Node*&
				{
					while (cur)
					{
						Node* next = cur->_next;//记录当前节点的下一个

						size_t newHashTablei = hashfcn(kov(cur->_data)) % newsize;//计算新表的插入位置
						//头插
						cur->_next = newtable[newHashTablei];//连尾
						newtable[newHashTablei] = cur;//改头

						cur = next;//cur更新,连带更改cur的值****
					}
				}
				_v.swap(newtable);
			}


			//头插
			_v_size = _v.size();
			size_t HashTablei = hashfcn(kov(data)) % _v_size;
			Node* newnode = new Node(data);
			newnode->_next = _v[HashTablei];
			_v[HashTablei] = newnode;
			_n++;
			return make_pair(iterator(newnode, this), true);
		}

		iterator Find(const key_type& key)
		{
			KeyOfValue kov;
			HashFcn hashfcn;
			Pred pred;
			size_t _v_size = _v.size();
			if (_v_size == 0 || _n == 0)
				return iterator(nullptr, this);

			size_t HashTablei = hashfcn(key) % _v_size;
			Node* cur = _v[HashTablei];
			while (cur)
			{
				if (pred(kov(cur->_data), key))
					return iterator(cur, this);
				else
					cur = cur->_next;
			}
			return iterator(nullptr, this);
		}

		bool Erase(const key_type& key)
		{
			HashFcn hashfcn;
			KeyOfValue kov;
			size_t _v_size = _v.size();
			if (_v_size == 0 || _n == 0)
				return false;

			size_t HashTablei = hashfcn(key) % _v_size;
			Node* cur = _v[HashTablei];
			Node* prev = nullptr;
			while (cur)
			{
				if (pred(kov(cur->_data), key))
				{
					if (prev == nullptr)
					{
						_v[HashTablei] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					_n--;
					return true;
				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;

		}


	private:
		vector<Node*> _v;
		size_t _n = 0;
	};
}

unorder_set

#pragma once

#include"hashtable.h"

namespace kele
{

	template<class K, class Hash = HashFcn<K>, class Pred = Equal_Key<K>>
	class unorder_set
	{
		typedef K key_type;
		typedef K value_type;
	public:

		struct _keyofvalue
		{
			const key_type& operator()(const value_type& value)
			{
				return value;
			}
		};

		typedef typename HashOpen::HashTable<key_type, value_type, _keyofvalue, Hash, Pred>::const_iterator iterator;
		typedef typename HashOpen::HashTable<key_type, value_type, _keyofvalue, Hash, Pred>::const_iterator 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 value_type& value)
		{
			return ht.Insert(value);
		}

		iterator find(const key_type& key)
		{
			return ht.Find(key);
		}

		bool erase(const key_type& key)
		{
			return ht.Erase(key);
		}


	private:
		HashOpen::HashTable<key_type, value_type, _keyofvalue, Hash, Pred> ht;
	};
}

unorder_map

#pragma once

#include"hashtable.h"

namespace kele
{

	template<class K, class V, class Hash = HashFcn<K>, class Pred = Equal_Key<K>>
	class unorder_map
	{
		typedef K key_type;
		typedef V data_type;
		typedef pair<const K, V> value_type;
	public:
		struct _keyofvalue
		{
			const key_type& operator()(const value_type& value)
			{
				return value.first;
			}
		};

		typedef typename HashOpen::HashTable<key_type, value_type, _keyofvalue, Hash, Pred>::iterator iterator;
		typedef typename HashOpen::HashTable<key_type, value_type, _keyofvalue, Hash, Pred>::const_iterator 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 value_type& value)
		{
			return ht.Insert(value);
		}

		iterator find(const key_type& key)
		{
			return ht.Find(key);
		}

		bool erase(const key_type& key)
		{
			return ht.Erase(key);
		}

		data_type& operator[](const key_type& key)
		{
			return (ht.Insert(make_pair(key, data_type())).first)->second;
		}

	private:
		HashOpen::HashTable<key_type, value_type, _keyofvalue, Hash, Pred> ht;
	};
}

test.cpp

#include<iostream>
#include<vector>
#include<string>
using namespace std;
#include"hashtable.h"
#include"unorderedset.h"
#include"unorderedmap.h"


void test_map1()
{
	kele::unorder_map<int, int> m;
	int arr[] = { 1, 2, 3, 4,5,55,15,46,66 ,1,22,2,2,55};
	for (auto e : arr)
	{
		//m.insert(make_pair(e,e));
		m[e]++;
	}

	//kele::unorder_map<int, int>::iterator it = m.begin();
	//while (it != m.end())
	//{
	//	cout << it->first << ":" << it->second << endl;
	//	++it;
	//}

	for (auto it : m)
	{
		cout << it.first << ":" << it.second << endl;
	}

}

class Date
{
	friend struct HashFunc;
	friend ostream& operator<<(ostream& out, const Date& x);
public:

	Date(int year, int month, int day)
		:_year(year),
		_month(month),
		_day(day)
	{}

	bool operator==(const Date x) const
	{
		return _year == x._year
			&& _month == x._month
			&& _day == x._day;
	}


private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& out, const Date& x)
{
	out << x._year << "/" << x._month << "/" << x._day;
	return out;
}

struct HashFunc
{
	size_t operator()(const Date* date)
	{
		size_t ret = 0;
		ret = (ret + date->_year) * 31;
		ret = (ret + date->_month) * 31;
		ret = (ret + date->_day) * 31;

		return ret;
	}
};

struct Equal
{
	bool operator()(const Date* date1, const Date* date2)
	{
		return *date1 == *date2;
	}
};

void test_map2()
{
	kele::unorder_map<Date*, int, HashFunc, Equal> m;
	Date d1(2024, 3, 10);
	Date d2(2024, 3, 11);
	Date d3(2024, 3, 10);
	Date d4(2024, 3, 12);
	Date d5(2024, 3, 12);

	Date* a[] = { &d1,&d2,&d3,&d4,&d5 };
	for (auto e : a)
	{
		m[e]++;
	}

	for (auto e : m)
	{
		cout << *(e.first) << ":" << e.second << endl;
	}
}

template<class K>
void print(const kele::unorder_set<int>& s)
{
	for (auto& e : s)
	{
		//e++;
		cout << e << " ";
	}
}

void test_set1()
{
	kele::unorder_set<int> s;
	int arr[] = { 1, 2, 3, 4,5,55,15,46,66 ,1,22,2,2,55 };
	for (auto e : arr)
	{
		s.insert(e);
	}
	print<int>(s);
}

template<class K, class V>
void print(const kele::unorder_map<int, int>& m)
{
	for (auto& e : m)
	{
		//e.second++;
		cout << e.first << ":" << e.second << " ";
	}
}

int main()
{
	//test_map1();
	//test_map2();
	test_set1();
	return 0;
}

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

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

相关文章

代码随想录刷题笔记 DAY 43 | 完全背包基础 | 零钱兑换 II No.518 | 组合总和 IV No.377

文章目录 Day 4401. 完全背包基础<1> 完全背包的区别<2> 案例 02. 零钱兑换 II&#xff08;No. 518&#xff09;<1> 题目<2> 笔记<3> 代码 03. 组合总和 IV&#xff08;No. 377&#xff09;<1> 题目<2> 笔记<3> 代码 Day 44 …

three.js 相机跟着玩家走(第三人称漫游)

<template><div><el-container><el-main><div class"box-card-left"><div id"threejs"></div></div></el-main></el-container></div> </template>s <script> // 引入轨道…

实在TARS大模型斩获多项重磅大奖,AI领域实力认可

近日&#xff0c;实在智能TARS&#xff08;塔斯&#xff09;大模型凭借在多个垂直行业场景的优秀落地应用案例&#xff0c;以及AIGC领域的深耕和技术积累&#xff0c;荣获多项重磅大奖。 TARS大模型是是实在智能基于在自然语言处理&#xff08;NLP&#xff09;领域深厚的技术积…

高等数学常用公式

高等数学常用公式 文章目录 内容大纲 内容 大纲 感谢观看 期待关注 有问题的小伙伴请在下方留言&#xff0c;喜欢就点个赞吧

区块链基础知识(下):共识机制 附带图解、超详细教学!看不懂你打死我

苏泽 大家好 这里是苏泽 一个钟爱区块链技术的后端开发者 本篇专栏 ←持续记录本人自学两年走过无数弯路的智能合约学习笔记和经验总结 如果喜欢拜托三连支持~ 专栏的前面几篇详细了介绍了区块链的核心基础知识 有兴趣学习的小伙伴可以看看→http://t.csdnimg.cn/CstOy 关于区…

RK3588-PCIe

1. 简介 PCIe&#xff08;Peripheral Component Interconnect Express&#xff09;是一种用于连接主板和外部设备的高速串行接口标准。它是 PCI 技术的后继者&#xff0c;旨在提供更高的带宽和更好的性能。 高速传输&#xff1a; PCIe接口提供了高速的数据传输通道&#xff0…

【wine】WINEDEBUG 分析mame模拟器不能加载roms下面的游戏 可以调整参数,快速启动其中一个游戏kof98

故障现象&#xff0c;MAME启动后&#xff0c;游戏都没有识别 添加日志输出&#xff0c;重新启动wine #!/bin/bashexport WINEPREFIX$(pwd)/.wine export WINESERVER$(pwd)/bin/wineserver export WINELOADER$(pwd)/bin/wine export WINEDEBUG"file,mame,warn,err"…

CCF-C推荐会议 IEEE CLOUD‘24 3月24日截稿!深圳开启全球云计算新纪元!

会议之眼 快讯 IEEE CLOUD(IEEE International Conference on Cloud Computing)即IEEE云计算国际会议将于 2024 年7月7日至13日在中国深圳举行&#xff01;IEEE CLOUD由lEEE Computer Society主办&#xff0c;CCF服务计算专委会、北京大学、IBM Research承办。CLOUD一直是研究人…

Linux---多线程(上)

一、线程概念 线程是比进程更加轻量化的一种执行流 / 线程是在进程内部执行的一种执行流线程是CPU调度的基本单位&#xff0c;进程是承担系统资源的基本实体 在说线程之前我们来回顾一下进程的创建过程&#xff0c;如下图 那么以进程为参考&#xff0c;我们该如何去设计创建一个…

STM32串口:DMA空闲中断实现接收不定长数据(基于HAL库)

STM32串口&#xff1a;DMA空闲中断实现接收不定长数据&#xff08;基于HAL库&#xff09;&#xff1a; 第一步&#xff1a;设置rcc&#xff0c;时钟频率&#xff0c;下载方式 设置system core->RCC如图所示&#xff1a;&#xff08;即High Speed Clock和Low Speed Clock都选…

EasyNVR级联EasyCVR,在EasyCVR播放视频会导致EasyNVR崩溃的原因排查与解决

视频综合管理平台EasyCVR视频监控系统支持多协议接入、兼容多类型设备&#xff0c;平台可以将监控区域内所有部署的监控设备进行统一接入与集中汇聚管理&#xff0c;实现对监控区域的实时视频监控、录像与存储、设备管理、云台控制、语音对讲、级联共享等&#xff0c;在监控中心…

跨境账号养号怎么做?Facebook、亚马逊运营必看

之前我们讨论过很多关于代理器的问题。它们的工作原理是什么?在不同的软件中要使用那些代理服务器?这些代理服务器之间的区别是什么?什么是反检测浏览器等等。 除了这些问题&#xff0c;相信很多人也会关心在使用不同平台的时代理器的选择问题。比如&#xff0c;为什么最好…

深入理解React中的useState:函数组件状态管理的利器

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

别再盲目推广!用Xinstall第三方统计,精准衡量广告ROI

在移动互联网时代&#xff0c;App推广已经成为各大广告主和开发者的必修课。然而&#xff0c;面对复杂的推广环境和多变的用户需求&#xff0c;如何提升推广效率、洞悉推广效果、衡量广告ROI&#xff0c;一直是困扰着广大广告主和开发者的难题。今天&#xff0c;我们就来聊聊一…

ChatGPT怎么用 ChatGPT小白能掌握的技巧原理

ChatGPT(Chat Generative Pre-training Transformer) 是一个 AI 模型,属于自然语言处理( Natural Language Processing , NLP ) 领域,NLP 是人工智能的一个分支。所谓自然语言,就是人们日常生活中接触和使用的英语、汉语、德语等等。自然语言处理是指,让计算机来理解并…

#LT8711V适用于Type-C/DP1.2/EDP转VGA应用方案,分辨率高达1080P。

1. 概述 LT8711V是一款高性能 Type-C/DP1.2 转 VGA 转换器&#xff0c;设计用于将 USB Type-C 源或 DP1.2 源连接到 VGA 接收器。 该LT8711V集成了一个符合DP1.2标准的接收器和一个高速三通道视频DAC。此外&#xff0c;还包括两个用于 CC 通信的 CC 控制器&#xff0c;以实现 …

揭秘PostgreSQL:超越传统数据库的无限可能!

介绍&#xff1a;PostgreSQL是一个功能强大的开源对象关系数据库系统。以下是对PostgreSQL的详细介绍&#xff1a; 开源性&#xff1a;PostgreSQL是完全开源的&#xff0c;这意味着任何人都可以自由地获取、使用和修改它的源代码。 可定制性&#xff1a;它具有高度可定制性&…

gitee分支管理,合并冲突

1、gitee展示分支 git branch 2、展示远程分支 git branch -r 3、新建分支 git branch base 4、切换分支 git checkout base 合并冲突 当代码在服务器上被提交了&#xff0c;再在本地提交会提示报错 点击merge

《互联网的世界》第六讲-去中心化和安全

互联网构建于开放互联的中立原则之上&#xff0c;公平接入&#xff0c;数据互联互通&#xff0c;流量被无差别对待&#xff0c;这意味着互联网本质上是匿名&#xff0c;去中心的&#xff0c;这与我们的现实世界完全不同。 但互联网上的主流业务却是 c/s 产销模式&#xff0c;试…

ansible基础与基础命令模块

一Ansible 1. ansible 的概念 Ansible是一个基于Python开发的配置管理和应用部署工具&#xff0c;现在也在自动化管理领域大放异彩。它融合了众多老牌运维工具的优点&#xff0c;Pubbet和Saltstack能实现的功能&#xff0c;Ansible基本上都可以实现。 Ansible能批量配置、部署、…