哈希表的认识与实现

哈希的概念

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

所以当我们设置这种数据结构时主要就是要找好存储数据插入的位置 建立好关系。但是我们插入数据的映射关系可能并不是一一对应的,也可能是多对一的关系也称为哈希冲突 。所以说我们就要想办法解决这些多个插入数据对应一个插入位置的数据(哈希冲突)的问题,因此我们就有对应的哈希函数 。哈希函数可以帮助我们将插入数据与插入位置关联起来,而合理的哈希函数可以有效缓解哈希冲突的问题。实际上解决哈希冲突是通过闭散列和开散列

哈希函数

  • 直接定址法
  • 除留余数法
  • 平方取中法
  • 折叠法
  • 随机数法
  • 数学分析法

以上常用的方法也就是直接定值法和除留余数法。

直接定值法:取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B(适合查找比较小且连续的情况)

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


除留余数法代码

namespace cr
{

	template<class K>
	struct Com_usual
	{
		size_t operator()(const K& key)
		{
			return key;
		}
	};
	template<>//模版特化
	struct Com_usual<string>//主要就是针对string类型
	{
		size_t operator()(const string& key)
		{
			 BKDR
			size_t ret = 0;
			for (auto e : key)
			{
				ret = ret * 31 + e;
			}
			return ret;
		}
	};

	enum state
	{
		empty,
		deleted,
		exit
	};

	template<class K, class V>
	struct Data
	{
		pair<K, V> _kv;//数据
		state _st;//空间对应的数据状态
	};

	template<class K, class V, class Com = Com_usual<K>>
	class HashTable
	{
	public:
		HashTable()
		{
			_table.resize(10);//保证size和capacity值一致
		}
		bool insert(const pair<K, V>& kv)
		{
			if (this->find(kv.first))
				return false;//找到就直接返回空

			if (_n * 10 / _table.size() == 7)
			{
				//扩容(不能直接扩,_table.size()改变了)
				HashTable<K, V, Com> newhash;
				newhash._table.resize(_table.size() * 2);
				for (int i = 0; i < _table.size(); i++)
				{
					if (_table[i]._st == exit)
					{
						newhash.insert(_table[i]._kv);
					}
				}
				_table.swap(newhash._table);//函数结束会释放交换后newtable的空间,也就是原来_table的空间
			}

			//插入数据
			Com co;
			int i = co(kv.first) % _table.size();
			while (_table[i]._st == exit)
			{
				i++;
				i %= _table.size();//防止越界
			}
			_table[i]._kv = kv;
			_table[i]._st = exit;
			++_n;//负载因子

			return true;
		}

		Data<K, V >* find(const K& key)
		{
			Com co;
			int i = co(key) % _table.size();
			while (_table[i]._st != empty)//因为设置的三个状态所以可以这以此判断
			{
				if (_table[i]._st == deleted);//删除只是调整了状态,数据没有清除
				else if (_table[i]._kv.first == key)
				{
					return &_table[i]; 
				}
				i++;
				i %= _table.size();//防止越界
			}
			return nullptr;//没找到
		}

		bool erase(const K& key)
		{
			Data<K, V >* ret = find(key);
			if (ret)
			{
				ret->_st = deleted;
				_n--;//有效数据个数

				//_table.erase(ret);//如果,不能仅仅调整数据状态
				//因为数据保留的话,find函数再次插入相同数据就插不进去
				//但是删除数据的话,顺序表的size会-1,所以也不能直接删除
				return 1;
			}
			return 0;
		}
		void Print()
		{
			for (int i = 0; i < _table.size(); i++)
			{
				if (_table[i]._st == exit)
				{
					//printf("[%d]->%d\n", i, _table[i]._kv.first);
					cout << "[" << i << "]->" << _table[i]._kv.first << ":" << _table[i]._kv.second << endl;
				}
				else if (_table[i]._st == empty)
				{
					printf("[%d]->\n", i);
				}
				else
				{
					printf("[%d]->D\n", i);
				}
			}

			cout << endl;
		}

	private:
		vector<Data<K, V>> _table;
		size_t _n = 0;//负载因子(保存着存储的关键字个数)用于计算空间利用率

	};
}

该方法的实质就是通过将插入的数据对应成整型,然后再模上空间的大小,存在对应映射的位置,如果该位置已经占了的话,就依次向后空的位置插入(线性探测) 。因此我们在查找数据的时候就可以凭借插入数据的方式,查找该数据本该存放的位置,但是该位置可能已经被其他数据占据了,所以我们就可以判断后面的位置,直到找到空为止。


其实以上我们通过除留余数法来进行数据的插入,但是当我们遇到不同数据映射到同一位置时,我们采用的是:闭散列

哈希冲突解决(散列表)

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

闭散列

闭散列也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有

空位置,那么可以把key存放到冲突位置中的"下一个" 空位置中去。

所以当我们查找数据时会采用线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止


而上面的代码也正是采用闭散列来存储数据,而查找数据也正是线性探测的方法。

开散列

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地

址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链

接起来,各链表的头结点存储在哈希表中。

实现开散列

namespace cr
{
	//对数据取模操作
	template<class K>
	struct Com_usual
	{
		size_t operator()(const K& key)//(不能重载string)
		{
			return key;
		}
	};
	template<>//模版特化
	struct Com_usual<string>
	{
		size_t operator()(const string& key)
		{
			 BKDR
			size_t ret = 0;
			for (auto e : key)
			{
				ret = ret * 31 + e;
			}
			return ret;
		}
	};

	template<class T>
	struct hash_Node
	{
		hash_Node(const T& data=T())
			:_data(data)
			,_next(nullptr)
		{}
		T _data;
		hash_Node* _next;

	};

	///迭代器实现
	template<class K, class T, class keyofT, class Com>//前置声明(不需要缺省参数)
	class hash_bucket;

	template<class K,class T, class ref,class ptr,class keyofT, class Com = Com_usual<K>>//
	struct hash_iterator
	{
		typedef hash_Node<T> Node;//类型重命名,带上模版参数
		typedef hash_iterator<K,T,ref,ptr,keyofT,Com> self;

		Node* _cur;
		const hash_bucket<K, T, keyofT, Com>* _hb;//哈希表指针,迭代器会修改数据,所以不能仅存表
		//哈希表与迭代器构成相互依赖关系
		//1.直接不传哈希表,传vector<Node*>
		//2.前置声明

		hash_iterator(Node* cur, hash_bucket<K, T, keyofT, Com>* hb)
			:_cur(cur)
			,_hb(hb)
		{}
		hash_iterator(Node* cur, const hash_bucket<K, T, keyofT, Com>* hb)
			:_cur(cur)
			, _hb(hb)//重载参数为const的拷贝构造函数
		{}

		bool operator!=(const self& s)
		{
			return _cur != s._cur;
		}
		ref operator*()
		{
			return _cur->_data;
		}
		ptr operator->()
		{
			return &_cur->_data;
		}
		const self& operator++()
		{
			keyofT my_key;Com co;
			int i = co(my_key(_cur->_data)) % (_hb->_hash.size());//求出当前迭代器指向位置
			
			if (_cur->_next)
				_cur = _cur->_next;
			else
			{
				i++;
				while (i<_hb->_hash.size())
				{
					if (_hb->_hash[i])
					{
						_cur = _hb->_hash[i];
						break;
					}
					i++;
				}
				//后面的数据都为空||已经找到后面的数据了
				if (i == _hb->_hash.size())
					_cur = nullptr;
			}
			return *this;
		}
		//const self& operator--()
		//{

		//}

	};

    //哈希桶
	template<class K,class T, class keyofT,class Com = Com_usual<K>>//
	class hash_bucket
	{
		template<class K, class T, class ref, class ptr, class KeyOfT, class Hash>
		friend struct hash_iterator;//由于迭代器部分中会访问哈希中的私有成员变量

	public:
		typedef hash_Node<T> Node;//类型重命名,带上模版参数
		typedef hash_iterator<K, T, T&, T*, keyofT, Com> iterator;
		typedef hash_iterator<K, T, const T&, const T*, keyofT, Com> const_iterator;

		iterator begin()
		{
			for (int i = 0; i < _hash.size(); i++)
			{
				if (_hash[i])
					return iterator(_hash[i], this);
			}
			return end();
		}
		iterator end()
		{
			return iterator(nullptr, this);
		}
		const_iterator cbegin()const//const修饰this
		{
			for (int i = 0; i < _hash.size(); i++)
			{
				if (_hash[i])
					return const_iterator(_hash[i], this);//迭代器构造时会传const this参数
			}
			return cend();
		}
		const_iterator cend()const
		{
			return const_iterator(nullptr, this);
		}

		hash_bucket()
		{
			_hash.resize(10);
		}
		~hash_bucket()//析构函数需要释放掉哈希桶里的节点
		{
			for (int i = 0; i < _hash.size(); i++)
			{
				while (_hash[i] != nullptr)
				{
					Node* tmp = _hash[i];
					_hash[i] = _hash[i]->_next;
					delete tmp;
				}
				_hash[i] = nullptr;
			}
		}

		pair<iterator,bool> insert(const T& data)
		{
			keyofT my_key;
			Com co;

			iterator rit = find(my_key(data));
			if (rit._cur)//为空就是找到了
				return make_pair(rit, false);
			//扩容
			if (_n == _hash.size())
			{
				vector<Node*> newhash;
				newhash.resize(_hash.size() * 2);//会自动调用自定义类型的构造函数
				//遍历旧表,将旧表的节点移到新表上
				for (int i = 0; i < _hash.size(); i++)
				{
					Node* cur = _hash[i];
					while (cur != nullptr)
					{
						Node* next = cur->_next;//提前保存好链表的下一个节点
						//链接
						int hashi = co(my_key(cur->_data)) % newhash.size();//找到新的表的映射位置处
						cur->_next = newhash[hashi];
						newhash[hashi] = cur;
						
						cur = next;
					}
					//将原旧表的vector的_hash[i]的指向依旧还是有指向的
					_hash[i] = nullptr;//置空以后再析构
					
				}
				_hash.swap(newhash);//交换,释放旧表
			}
			int i = co(my_key(data)) % _hash.size();
			Node* newnode = new Node(data);
			
			//头插
			newnode->_next = _hash[i];
			_hash[i] = newnode;
			++_n;

			return make_pair(iterator(newnode,this),true);
		}

		iterator find(const K& key)//找到了就返回找到的节点,没找到就新建节点
		{
			Com co; keyofT my_key;
			int i = co(key) % _hash.size();
			Node* cur = _hash[i];
			while (cur)
			{
				if (my_key(cur->_data) == key)
					return iterator(cur,this);
				cur = cur->_next;
			}
			return end();
		}

		bool erase(const K& key)
		{
			Com co; keyofT my_key;
			int i = co(key) % _hash.size();
			Node* cur = _hash[i];
			Node* pre = nullptr;
			while (cur)
			{
				if (my_key(cur->_data) == key)
				{
					if (pre == nullptr)//防止删除的正好是头结点
						_hash[i] = cur->_next;
					pre->_next = cur->_next;

					delete cur;
					return true;
				}
				pre = cur;
				cur = cur->_next;
			}
			return false;
		}

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

开散列适配unordered_map

#include"hash.h"

namespace cr
{
	template<class K,class V>
	class unordered_map
	{
		struct keyofT_map//仿函数取得key
		{
			const K& operator()(const pair<K,V>& kv)
			{
				return kv.first;
			}
		};
		
	public:
		typedef typename hash_bucket<K, pair<const K, V>, keyofT_map>::iterator iterator;
		typedef typename hash_bucket<K, pair<const K, V>, keyofT_map>::const_iterator const_iterator;

		iterator begin()
		{
			return _hb.begin();
		}
		iterator end()
		{
			return _hb.end();
		}
		const_iterator cbegin()const 
		{
			return _hb.cbegin();
		}
		const_iterator cend()const
		{
			return _hb.cend();
		}

		pair<iterator,bool> insert(const pair<K,V>& kv)
		{
			return _hb.insert(kv);
		}

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

		bool erase(const K& key)
		{
			return _hb.erase(key);
		}
		iterator find(const K& key)
		{
			return _hb.find(key);
		}

	private:
		hash_bucket<K, pair<const K, V>,keyofT_map> _hb;
	};
}

开散列实现unordered_set

#include"hash.h"

namespace cr
{
	template<class K>
	class unordered_set
	{
		struct keyofT_set//仿函数取得key
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

	public:
		typedef typename hash_bucket<K, K, keyofT_set>::const_iterator iterator;
		typedef typename hash_bucket<K, K, keyofT_set>::const_iterator const_iterator;

		iterator begin()
		{
			return _hb.begin();
		}
		iterator end()
		{
			return _hb.end();
		}
		const_iterator cbegin()const
		{
			return _hb.cbegin();
		}
		const_iterator cend()const
		{
			return _hb.cend();
		}

		pair<iterator, bool> insert(const K& key)
		{
			pair<typename hash_bucket<K, K, keyofT_set>::iterator, bool> ret = _hb.insert(key);
			//直接构造匿名对象		
			return pair<iterator, bool>(iterator(ret.first._cur, ret.first._hb), ret.second);
		}

		bool erase(const K& key)
		{
			return _hb.erase(key);
		}
		iterator find(const K& key)
		{
			return _hb.find(key);
		}

	private:
		hash_bucket<K, K,keyofT_set> _hb;
	};

}
相关推荐
别NULL3 小时前
机试题——疯长的草
数据结构·c++·算法
ZSYP-S5 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
唐叔在学习5 小时前
【唐叔学算法】第21天:超越比较-计数排序、桶排序与基数排序的Java实践及性能剖析
数据结构·算法·排序算法
ALISHENGYA5 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(switch语句)
数据结构·算法
武昌库里写JAVA8 小时前
浅谈怎样系统的准备前端面试
数据结构·vue.js·spring boot·算法·课程设计
S-X-S8 小时前
代码随想录刷题-数组
数据结构·算法
l138494274518 小时前
每日一题(4)
java·数据结构·算法
kyrie_sakura8 小时前
c++数据结构算法复习基础--13--基数算法
数据结构·c++·算法
XWXnb69 小时前
数据结构:顺序表
数据结构·算法
橘颂TA9 小时前
【C++】数据结构 顺序表的实现(详解)
开发语言·数据结构·c++·算法