C++ 哈希

14 篇文章 0 订阅

目录

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;
}

  • 7
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值