C++——哈希

目录

一、unordered系列关联式容器

1、unordered_map的介绍

2、unordered_map的接口说明

2.1 unordered_map的构造

2.2 unordered_map的容量

2.3 unordered_map的迭代器

2.4 unordered_map的元素访问

2.5 unordered_map的查询

2.6 unordered_map的修改操作

2.7 unordered_map的桶操作

二、底层机构

1、哈希概念

2、哈希冲突

3、哈希函数

4、哈希冲突的解决

4.1 闭散列

4.2 开散列

三、哈希表的模拟实现

1、开放定址法(K,V模型)

1.1哈希数据的结构体

1.2哈希表中的成员变量

1.3插入数据

1.4查找

1.5删除

2、哈希桶的实现

2.1哈希节点的结构体

2.2哈希表的成员变量

2.3迭代器的结构体

2.4begin()和end()迭代器

2.5析构函数

2.6查找函数

2.7删除函数

2.8插入函数

2.9获取最大的哈希桶

四、利用哈希表模拟实现unordered_map和unordered_set的封装

1、unordered_set

2、unordered_map


 

一、unordered系列关联式容器

1、unordered_map的介绍

        ·unordered_map是存储<keym value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value。

        ·在unordered_map中,键值通常用于唯一的表示元素而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。

        ·在内部,unordered_map没有对<key, value>按照任何特定的顺序排序,为了能在常数范围内找到key对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。

        ·unordered_map容器通过key访问单个元素比map要快,但它通常在遍历元素自己的范围迭代方面效率较低。

        ·unordered_map实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。

        ·它的迭代器至少是前向迭代器。

2、unordered_map的接口说明

2.1 unordered_map的构造

函数声明功能介绍
unordered_mapa构造不同格式的unordered_map对象

2.2 unordered_map的容量

函数声明功能介绍
bool empty() const检测unordered_map是否为空
size_t size() const获取unordered_map的有效元素个数

2.3 unordered_map的迭代器

函数声明功能介绍
begin返回unordered_map第一个元素的迭代器
end返回unordered_map最后一个元素下一个位置的迭代器
cbegin返回unordered_map第一个位置的const迭代器
cend返回unordered_map最后一个元素下一个位置的const迭代器

2.4 unordered_map的元素访问

函数声明功能介绍
operator[]返回与key对应的value,没有一个默认值

2.5 unordered_map的查询

函数声明功能介绍
iterator find(const K& key)返回key在哈希桶中的位置
size_t count(const K& key)返回哈希桶中关键码为key的键值对的个数

2.6 unordered_map的修改操作

函数声明功能介绍
insert向容器中插入键值对
erase删除容器中的键值对
void clear()清空容器中有效元素个数
void swap(unordered map&)交换两个容器中的元素

2.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系列的关联式容器之所以效率比较高,是因为其底层使用了哈希结构。

1、哈希概念

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

        如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

当向该结构中:

        ·插入元素

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

·搜索元素

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

        该方式即为哈希方法,哈希方法中使用的转换函数称为哈希函数,构造出来的结构称为哈希表。

        例如:数据集合{1, 7, 6, 4, 5, 9};

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

2、哈希冲突

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

3、哈希函数

哈希函数设计原则:

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

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

        ·哈希函数应该比较简单

常用的哈希函数

(1)直接定制法

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

        优点:简单,均匀

        缺点:需要事先知道关键字的分布情况

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

(2)除留余数法

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

4、哈希冲突的解决

4.1 闭散列

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

        线性探测的优点:实现简单

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

4.2 开散列

 开散列概念

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

三、哈希表的模拟实现

1、开放定址法(K,V模型)

        开放定址法每个格中有三种状态中的一种,EMPTY、EXIST、DELETE

1.1哈希数据的结构体

	template<class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		State _state = EMPTY;
	};

1.2哈希表中的成员变量

		vector<HashData<K, V>> _tables;
		size_t _n = 0; //存储的数据个数

1.3插入数据

		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
				return false;
			//负载因子超过0.7就扩容
			if (_tables.size() == 0 || _n * 10 / _tables.size() >= 7)
			{
				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				HashTable<K, V> newht;
				newht._tables.resize(newsize);

				//遍历旧表,重新映射到新表中
				for (auto& data : _tables)
				{
					if (data._state == EXIST)
					{
						newht.Insert(data._kv);
					}
				}

				_tables.swap(newht._tables);
			}

			size_t hashi = kv.first % _tables.size();

			//线性探测
			size_t i = 1;
			size_t index = hashi;
			while (_tables[index]._state == EXIST)
			{
				index = hashi + i;
				index %= _tables.size();
				++i;
			}

			_tables[hashi]._kv = kv;
			_tables[hashi]._state = EXIST;
			_n++;

			return true;
		}

        上面的负载因子是指表中存在的数据占表大小的比例。

        当负载因子到达一定时,需要将哈希表扩容,将旧表的数据重新映射到新表中。当发生哈希冲突时,向后移动并查看状态如果时EMPTY就可以插入并把状态改为EXIST。

1.4查找

		HashData<K, V>* Find(const K& key)
		{
			if (_tables.size() == 0)
				return nullptr;

			size_t hashi = key % _tables.size();

			//线性探测
			size_t i = 1;
			size_t index = hashi;
			while (_tables[hashi]._state != EMPTY)
			{
				if (_tables[index]._state == EXIST
					&& _tables[index]._kv.first == key)
				{
					return &_tables[index];
				}
				index = hashi + i;
				index %= _tables.size();
				++i;

				//如果已经查找一圈了,那么说明全是存在+删除
				if (index == hashi)
				{
					break;
				}
			}

			return nullptr;
		}

1.5删除

		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);
			if (ret)
			{
				ret->_state = DELETE;
				--_n;
				return true;
			}
			else
			{
				return false;
			}
		}

        只需要查找到要删除的元素,把该元素的状态设置为DELETE,数据个数--即可。

2、哈希桶的实现

2.1哈希节点的结构体

	template<class T>
	struct HashNode
	{
		HashNode<T>* _next;
		T _data;

		HashNode(const T& data)
			:_next(nullptr)
			, _data(data)
		{}
	};

        因为不确定节点的类型所以用模板T。

2.2哈希表的成员变量

		vector<Node*> _tables;
		size_t _n = 0;

2.3迭代器的结构体

	template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
	struct __HashIterator
	{
		typedef HashNode<T> Node;
		typedef HashTable<K, T, KeyOfT, Hash> HT;
		typedef __HashIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;

		typedef __HashIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;

		Node* _node;
		const HT* _ht;

		__HashIterator(Node* node, const HT* ht)
			:_node(node)
			,_ht(ht)
		{}

		__HashIterator(const Iterator& it)
			:_node(it._node)
			,_ht(it._ht)
		{}

		Ref operator*()
		{
			return _node->_data;
		}

		Ptr operator->()
		{
			return &_node->_data;
		}

		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}

		Self& operator++()
		{
			if (_node->_next != nullptr)
			{
				_node = _node->_next;
			}
			else
			{
				//找到下一个不为空的桶
				KeyOfT kot;
				Hash hash;
				//算出当前桶的位置
				size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size();
				++hashi;
				while (hashi < _ht->_tables.size())
				{
					if (_ht->_tables[hashi])
					{
						_node = _ht->_tables[hashi];
						break;
					}
					else
					{
						++hashi;
					}
				}

				//没有找到不为空的桶
				if (hashi == _ht->_tables.size())
				{
					_node = nullptr;
				}
			}

			return *this;
		}
	};

2.4begin()和end()迭代器

		iterator begin()
		{
			Node* cur = nullptr;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				cur = _tables[i];
				if (cur)
				{
					break;
				}
			}
			return iterator(cur, this);
		}

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

		const_iterator begin()const
		{
			Node* cur = nullptr;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				cur = _tables[i];
				if (cur)
				{
					break;
				}
			}
			return const_iterator(cur, this);
		}

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

2.5析构函数

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

2.6查找函数

		iterator Find(const K& key)
		{
			if (_tables.size() == 0)
				return end();

			KeyOfT kot;
			Hash hash;
			size_t hashi = hash(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return iterator(cur, this);
				}
				cur = cur->_next;
			}
			return end();
		}

2.7删除函数

		bool Erase(const K& key)
		{
			KeyOfT kot;
			Hash hash;
			size_t hashi = hash(key) % _tables.size();
			Node* prev = nullptr;
			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;
		}

2.8插入函数

		pair<iterator, bool> Insert(const T& data)
		{
			KeyOfT kot;
			iterator it = Find(kot(data));
			if (it != end())
			{
				return make_pair(it, false);
			}

			Hash hash;

			//负载因子等于1时扩容
			if (_n == _tables.size())
			{
				size_t newsize = GetNextPrime(_tables.size());
				vector<Node*> newtables(newsize, nullptr);
				for (auto& cur : _tables)
				{
					while (cur)
					{
						Node* next = cur->_next;

						size_t hashi = hash(kot(cur->_data)) % newtables.size();

						//头插到新表
						cur->_next = newtables[hashi];
						newtables[hashi] = cur;
						cur = next;
					}
				}
				_tables.swap(newtables);
			}

			size_t hashi = hash(kot(data)) % _tables.size();

			//头插
			Node* newnode = new Node(data);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;

			++_n;
			return make_pair(iterator(newnode, this), false);
		}

2.9获取最大的哈希桶

		size_t MaxBucketSize()
		{
			size_t max = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				auto cur = _tables[i];
				size_t size = 0;
				while (cur)
				{
					++size;
					cur = cur->_next;
				}

				if (size > max)
					max = size;
				return max;
			}
		}

四、利用哈希表模拟实现unordered_map和unordered_set的封装

1、unordered_set

	template<class K, class Hash = HashFunc<K>>
	class unordered_set
	{
	public:
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename HashBucket::HashTable<K, K, SetKeyOfT, Hash>::const_iterator iterator;
		typedef typename HashBucket::HashTable<K, K, SetKeyOfT, Hash>::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 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:
		HashBucket::HashTable<K, K, SetKeyOfT, Hash> _ht;
	};

2、unordered_map

	template<class K, class V, class Hash = HashFunc<K>>
	class unordered_map
	{
	public:
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::iterator iterator;
		typedef typename HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::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 pair<K, V>& kv)
		{
			return _ht.Insert(kv);
		}

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert(make_pair(key, V()));
			return ret.first->second;
		}

		iterator find(const K& key)
		{
			return _ht.Find(key);
		}

		bool erase(const K& key)
		{
			return _ht.Erase(key);
		}

	private:
		HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
	};

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值