C++ 闭散式和开散式的模拟实现

1. 闭散式

仿函数

struct DefaultHashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
template<>
struct DefaultHashFunc<string>//模板的特化
{
	size_t operator()(const string& str)
	{
		size_t hash = 0;
		for (auto ch : str)
		{
			hash *= 131;//字符串哈希算法
			hash += ch;
		}
		return hash;
	}
};

因为我们并不知道存储的是字符串还是整数,所以我们需要对字符串进行处理,运用字符串哈希算法(可以减少冲突概率),转化为整数再进行后续的操作。

1.1 存储节点结构的定义

enum STATE
{
	EXIST,
	EMPTY,
	DELETE
};
template<class K, class V>
struct HashData
{
	std::pair<K, V> _kv;
	STATE _state = EMPTY;
};

用一个结构体来表示当前的存储状态,存在,空,还是删除过的。

1.2 存储结构的定义

std::vector<HashData<K, V>> _table;
size_t _n = 0;//记录有效值的个数

使用vector数组包含一个个HashData对象,_n来记录有效个数的值,为后续扩容做准备。

1.3 构造函数

HashTable()
{
	_table.resize(10);
}

vector是内置类型,会调用默认的析构函数,HashData会去调用它自己的析构函数,HashData的成员爷没有申请多态资源,也不需要手动释放,交给编译器即可,所以也就不需要写析构函数。

1.4 insert插入函数

bool insert(const std::pair<K, V>& kv)
{
	if (find(kv.first))
	{
		return false;
	}
	if (_n * 10 / _table.size() >= 7)
	{
		size_t newsize = _table.size() * 2;
		HashTable<K, V, HashFunc> newHT;
		newHT._table.resize(newsize);
		//遍历旧表放入新表
		for (size_t i = 0; i < _table.size(); i++)
		{
			if (_table[i]._state == EXIST)
			{
				newHT.insert(_table[i]._kv);
			}
		}
		_table.swap(newHT._table);
	}
	//线性探测
	HashFunc hf;
	size_t hashi = hf(kv.first) % _table.size();
	while (_table[hashi]._state == EXIST)
	{
		++hashi;
		hashi %= _table.size();//防止溢出
	}
	_table[hashi]._kv = kv;
	_table[hashi]._state = EXIST;
	++_n;
	return true;
}

先进行判断,插入的元素是否已经存在与数组中,避免数据冗余,判断是否已经超过负载因子的限定,超过了就需要进行扩容操作,遍历旧表,把已经存在的值重新插入一遍。

利用线性探测的方式,先计算出下标,判断是否已经被占用,被占用旧往下遍历,找到没有被占用的值,进行插入,有效数据个数++。

ps:当扩容后,数组的大小已经改变了,所以就应当需要进行重新映射,重新映射也有效数据个数是不变的,也不需要更新有效数据个数,同时对于数据聚集也可以有更好的缓解。

ps:如果想要降低数据的聚集问题,可以使用二次探测。

1.5 find查找函数

HashData<const K, V>* find(const K& key) //8(exist) (delete)  6,在8的位置后面找不会因为delete找不到6
{
	HashFunc hf;
	size_t hashi = hf(key) % _table.size();
	while (_table[hashi]._state != EMPTY)
	{
		if (_table[hashi]._state == EXIST && _table[hashi]._kv.first == key)
		{
			return (HashData<const K, V>*) & _table[hashi];//有一个隐式类型转化,权限的缩小,K->const K
		}
		++hashi;
		hashi %= _table.size();
	}
	return nullptr;
}

先计算下标,如果计算出来的下标不是为空的话,就需要进一步判断是被删除了还是存在的,如果当前下标的状态是存在并且存储的值也相符合的话,就返回当前下标的类指针。

1.6 erase删除函数

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

复用find函数,找到了就直接把状态设为删除,有效数据个数--。

完整模拟实现

#pragma once
#include<vector>
#include <string>
#include <map>
template<class K>
struct DefaultHashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
template<>
struct DefaultHashFunc<string>//模板的特化
{
	size_t operator()(const string& str)
	{
		size_t hash = 0;
		for (auto ch : str)
		{
			hash *= 131;//字符串哈希算法
			hash += ch;
		}
		return hash;
	}
};
namespace open_address
{
	enum STATE
	{
		EXIST,
		EMPTY,
		DELETE
	};
	template<class K, class V>
	struct HashData
	{
		std::pair<K, V> _kv;
		STATE _state = EMPTY;
	};

	template<class K, class V, class HashFunc = DefaultHashFunc<K>>//加一个仿函数来分别解决整型和string的find函数的%问题。
	class HashTable
	{
	public:
		HashTable()
		{
			_table.resize(10);
		}
		bool insert(const std::pair<K, V>& kv)
		{
			if (find(kv.first))
			{
				return false;
			}
			if (_n * 10 / _table.size() >= 7)
			{
				size_t newsize = _table.size() * 2;
				HashTable<K, V, HashFunc> newHT;
				newHT._table.resize(newsize);
				//遍历旧表放入新表
				for (size_t i = 0; i < _table.size(); i++)
				{
					if (_table[i]._state == EXIST)
					{
						newHT.insert(_table[i]._kv);
					}
				}
				_table.swap(newHT._table);
			}
			//线性探测
			HashFunc hf;
			size_t hashi = hf(kv.first) % _table.size();
			while (_table[hashi]._state == EXIST)
			{
				++hashi;
				hashi %= _table.size();//防止溢出
			}
			_table[hashi]._kv = kv;
			_table[hashi]._state = EXIST;
			++_n;
			return true;
		}
		HashData<const K, V>* find(const K& key) //8(exist) (delete)  6,在8的位置后面找不会因为delete找不到6
		{
			HashFunc hf;
			size_t hashi = hf(key) % _table.size();
			while (_table[hashi]._state != EMPTY)
			{
				if (_table[hashi]._state == EXIST && _table[hashi]._kv.first == key)
				{
					return (HashData<const K, V>*) & _table[hashi];//有一个隐式类型转化,权限的缩小,K->const K
				}
				++hashi;
				hashi %= _table.size();
			}
			return nullptr;
		}
		bool erase(const K& key)
		{
			HashData<const K, V>* ret = find(key);
			if (ret)
			{
				ret->_state = DELETE;
				--_n;
				return true;
			}
			return false;
		}
	private:
		std::vector<HashData<K, V>> _table;
		size_t _n = 0;//记录有效值的个数
	};
}

2. 开散式

同样开散式也会面临着不知道存储的是字符串还是整数,为了处理计算映射位置的问题,也需要仿函数。

哈希表(开散式)又称为哈希桶。

存储的值被一个个的挂在下面,所以又称哈希桶

2.1 存储节点结构的定义

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

哈希表需要维护一个单链表,需要把一个个的节点串起来,所以需要一个指针来指向下一个节点。

2.2 存储结构的定义

typedef HashNode<T> Node;
vector<Node*> _table;
size_t _n = 0;//存储有效数字个数

因此数组里面的存储类型也就变成了节点指针。

2.3 构造函数和析构函数

HashTable()
{
	_table.resize(10, nullptr);
}
~HashTable()
{
	for (size_t i = 0; i < _table.size(); i++)
	{
		Node* cur = _table[i];
		while (cur)
		{
			Node* next = cur->_next;
			delete cur;
			cur = next;
		}
		_table[i] = nullptr;
	}
}

因为是单链表,有申请动态空间,所以在析构时,需要手动遍历其中的所有节点,将其释放,避免内存泄漏。

2.4 迭代器

//前置声明
template<class K, class T, class keyofT, class HashFunc > class HashTable;//因为迭代器中需要用到哈希表
//HTiterator<K, T, const T*, const T&, keyofT, HashFunc>
template<class K, class T,class Ref,class Ptr, class keyofT, class HashFunc = DefaultHashFunc<K>>
struct HTiterator
{
	typedef HashNode<T> Node;
	typedef HTiterator<K, T, Ref, Ptr, keyofT, HashFunc> self; 
	typedef HTiterator<K, T, T*, T&, keyofT, HashFunc> iterator;
	Node* _node;
	const HashTable<K, T, keyofT, HashFunc>* _pht;
	HTiterator(Node* node, const HashTable<K, T, keyofT, HashFunc>* pht) 

		:_node(node)
		,_pht(pht)
	{}
	HTiterator(const iterator&it)
		:_node(it._node)
		,_pht(it._pht)
	{}
	Ptr operator*()
	{
		return _node->_data;
	}
	Ref operator->()
	{
		return &_node->_data;
	}
	self& operator++()
	{
		if (_node->_next)
		{
			_node = _node->_next;
		}
		else
		{
			HashFunc hf;
			keyofT kot;
			size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();
			++hashi;
			while (hashi < _pht->_table.size())
			{
				if (_pht->_table[hashi])//当前桶不为空
				{
					_node = _pht->_table[hashi];//指向当前不为空的桶
					return *this;
				}
				else
				{
					hashi++;//寻找下一个不为空的桶
				}
			}
			_node = nullptr;
		}
		return *this;
	}
	bool operator!=(const self& s)
	{
		return _node != s._node;
	}
	bool operator==(const self& s)
	{
		return _node == s._node;
	}
};

迭代器部分重点介绍迭代器的移动,如果当前节点的_next指针不为空,就直接移动到下一个节点的指针,如果为空,就需要去寻找下一个不为空的节点的指针,先计算出当前节点的下标,在保证不越界的情况下,判断当前节点的指针是否为空,为空,就++下标。

2.5 迭代器和存储结构类的注意

因为迭代器中需要使用到HashTable,在HashTable里面也需要使用到迭代器,编译器是会向上去寻找的,在迭代器中会找不到HashTable,所以可以在前面做一个前置声明,又来一个新的问题就是迭代器里面使用不了HashTable的私有成员,在HashTable把迭代器设为友员,你想要使用我,就把你变成我的朋友,还要一个注意的点,就是在前置声明和设置友员的时候,是不可以在加缺省值的。

因为迭代器和HashTable也就设置了HashFunc的缺省值,无论在前置声明还是在友员的设置中就不能对HashFunc在设置缺省值了。

不然就会出现重定义默认参数,就算设置默认参数一样也不可以。

2.6 迭代器的begin和end实现(包含const)

iterator begin()
{
	//找第一个桶
	for (size_t i = 0; i < _table.size(); i++)
	{
		Node* cur = _table[i];
		if (cur)
		{
			return iterator(cur,this);
		}
	}
	return iterator(nullptr, this);
}
iterator end()
{
	return iterator(nullptr, this);
}
const_iterator begin()const
{
	for (size_t i = 0; i < _table.size(); i++)
	{
		Node* cur = _table[i];
		if (cur)
		{
			return const_iterator(cur, this);
		}
	}
	return const_iterator(nullptr, this);
}
const_iterator end()const
{
	return const_iterator(nullptr, this);
}

找begin也就是找第一个不为空的桶,遍历数组,如果不为空就返回。

2.7 insert插入函数

pair<iterator,bool> insert(const T& data)
{
	keyofT kot;
	HashFunc hf;
	iterator it = find(kot(data));
	if (it != end())
	{
		return make_pair(it, false);
	}
	if (_n == _table.size())//扩容,也就是负载因子为1的时候,相对于每个桶挂一个
	{
		size_t newsize = _table.size() * 2;
		vector<Node*> newTable;
		newTable.resize(newsize, nullptr);//这里的扩容没有像开放定址法一样去复用insert因为复用了就需要重新开节点的空间
		for (size_t i = 0; i < _table.size(); i++)
		{
			Node* cur = _table[i];
			while (cur)
			{
				Node* next = cur->_next;
				//头插到新表
				size_t hashi = hf(kot(cur->_data)) % newsize;
				cur->_next = newTable[hashi];
				newTable[hashi] = cur;
				cur = next;
			}
			_table[i] = nullptr;
		}
		_table.swap(newTable);
	}
	size_t hashi = hf(kot(data)) % _table.size();
	Node* newnode = new Node(data);
	newnode->_next = _table[hashi];
	_table[hashi] = newnode;
	++_n;
	return make_pair(iterator(newnode,this),true);
}

在正常插入时,复用insert函数,没有找到就创建,我们使用的头插,因为不用在意排序的问题,头尾插都是可以的。

扩容问题:这边负载因子设为1,也就是最好的情况下一个节点挂一个,这边遍历找到不为空的桶,但是不需要再像闭散式一样去复用insert,不然就需要重新开空间,重新插入,这边的实现方法也很优雅,遍历不为空的桶,重新计算映射规则,把不为空的桶里面的节点顺手牵羊,直接挂接到新表中即可。

2.8 find查找函数

iterator find(const K& key)
{
	HashFunc hf;
	keyofT kot;
	size_t hashi = hf(key) % _table.size();
	Node* cur = _table[hashi];
	while (cur)
	{
		if (kot(cur->_data) == key)
		{
			return iterator(cur, this);
		}
		cur = cur->_next;
	}
	return end();
}

计算下标,再当前桶去遍历,如果找到了就返回当前节点的迭代器,如果没有就返回nullptr。

2.9 eraser删除函数

bool erase(const K& key)
{
	HashFunc hf;
	keyofT kot;
	size_t hashi = hf(key) % _table.size();
	Node* cur = _table[hashi];
	Node* prev = nullptr;
	while (cur)
	{
		if (kot(cur->_data) == key)
		{
			if (prev == nullptr)
			{
				_table[hashi] = cur->_next;
			}
			else
			{
				prev->_next = cur->_next;
			}
			delete cur;
			return true;
		}
		prev = cur;
		cur = cur->_next;
	}
	return false;
}

因为是单链表,只有指向下一个节点的指针,所以我们还需要记录上一个节点,当找到了要删除的节点,要建立要删除节点的上一个节点和下一个节点的联系。

完整模拟实现

#pragma once
#include<vector>
#include <string>
#include <map>
template<class K>
struct DefaultHashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
template<>
struct DefaultHashFunc<string>//模板的特化
{
	size_t operator()(const string& str)
	{
		size_t hash = 0;
		for (auto ch : str)
		{
			hash *= 131;//字符串哈希算法
			hash += ch;
		}
		return hash;
	}
};
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 keyofT, class HashFunc > class HashTable;//因为迭代器中需要用到哈希表
	//HTiterator<K, T, const T*, const T&, keyofT, HashFunc>
	template<class K, class T,class Ref,class Ptr, class keyofT, class HashFunc = DefaultHashFunc<K>>
	struct HTiterator
	{
		typedef HashNode<T> Node;
		typedef HTiterator<K, T, Ref, Ptr, keyofT, HashFunc> self; 
		typedef HTiterator<K, T, T*, T&, keyofT, HashFunc> iterator;
		Node* _node;
		const HashTable<K, T, keyofT, HashFunc>* _pht;
		HTiterator(Node* node, const HashTable<K, T, keyofT, HashFunc>* pht) 

			:_node(node)
			,_pht(pht)
		{}
		HTiterator(const iterator&it)
			:_node(it._node)
			,_pht(it._pht)
		{}
		Ptr operator*()
		{
			return _node->_data;
		}
		Ref operator->()
		{
			return &_node->_data;
		}
		self& operator++()
		{
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else
			{
				HashFunc hf;
				keyofT kot;
				size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();
				++hashi;
				while (hashi < _pht->_table.size())
				{
					if (_pht->_table[hashi])//当前桶不为空
					{
						_node = _pht->_table[hashi];//指向当前不为空的桶
						return *this;
					}
					else
					{
						hashi++;//寻找下一个不为空的桶
					}
				}
				_node = nullptr;
			}
			return *this;
		}
		bool operator!=(const self& s)
		{
			return _node != s._node;
		}
		bool operator==(const self& s)
		{
			return _node == s._node;
		}
	};
	template<class K,class T,class keyofT,class HashFunc= DefaultHashFunc<K>>
	class HashTable
	{
		typedef HashNode<T> Node;
	public:
		typedef HTiterator<K, T, T*, T&, keyofT, HashFunc> iterator;
		typedef HTiterator<K, T, const T*, const T&, keyofT, HashFunc> const_iterator;
		//友员声明
		template<class K, class T, class Ref,class Ptr,class keyofT, class HashFunc > friend struct HTiterator;
		HashTable()
		{
			_table.resize(10, nullptr);
		}
		~HashTable()
		{
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_table[i] = nullptr;
			}
		}
		iterator begin()
		{
			//找第一个桶
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				if (cur)
				{
					return iterator(cur,this);
				}
			}
			return iterator(nullptr, this);
		}
		iterator end()
		{
			return iterator(nullptr, this);
		}
		const_iterator begin()const
		{
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				if (cur)
				{
					return const_iterator(cur, this);
				}
			}
			return const_iterator(nullptr, this);
		}
		const_iterator end()const
		{
			return const_iterator(nullptr, this);
		}
		pair<iterator,bool> insert(const T& data)
		{
			keyofT kot;
			HashFunc hf;
			iterator it = find(kot(data));
			if (it != end())
			{
				return make_pair(it, false);
			}
			if (_n == _table.size())//扩容,也就是负载因子为1的时候,相对于每个桶挂一个
			{
				size_t newsize = _table.size() * 2;
				vector<Node*> newTable;
				newTable.resize(newsize, nullptr);//这里的扩容没有像开放定址法一样去复用insert因为复用了就需要重新开节点的空间
				for (size_t i = 0; i < _table.size(); i++)
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;
						//头插到新表
						size_t hashi = hf(kot(cur->_data)) % newsize;
						cur->_next = newTable[hashi];
						newTable[hashi] = cur;
						cur = next;
					}
					_table[i] = nullptr;
				}
				_table.swap(newTable);
			}
			size_t hashi = hf(kot(data)) % _table.size();
			Node* newnode = new Node(data);
			newnode->_next = _table[hashi];
			_table[hashi] = newnode;
			++_n;
			return make_pair(iterator(newnode,this),true);
		}
		iterator find(const K& key)
		{
			HashFunc hf;
			keyofT kot;
			size_t hashi = hf(key) % _table.size();
			Node* cur = _table[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return iterator(cur, this);
				}
				cur = cur->_next;
			}
			return end();
		}
		bool erase(const K& key)
		{
			HashFunc hf;
			keyofT kot;
			size_t hashi = hf(key) % _table.size();
			Node* cur = _table[hashi];
			Node* prev = nullptr;
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					if (prev == nullptr)
					{
						_table[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}
	private:
		vector<Node*> _table;
		size_t _n = 0;//存储有效数字个数
	};
}

unorder_map

#pragma once
#include"hashtable.h"
#include <map>
namespace bit
{
	template<class K, class V>
	class unordered_map
	{
		struct mapkeyofT
		{
			const K& operator()(const std::pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename hash_bucket::HashTable<K, pair<const K, V>, mapkeyofT>::iterator iterator; //这里类型写错了
		typedef typename hash_bucket::HashTable<K, pair<const K, V>, mapkeyofT>::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);
		}
		bool erase(const K& kv)
		{
			return _ht.erase(kv);
		}
		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _ht.insert(make_pair(key, V()));
			return ret.first->second;
		}
	private:
		hash_bucket::HashTable<K, pair<const K, V>, mapkeyofT> _ht;
	};
}

unordered_set

#pragma once
#include"hashtable.h"
namespace bit
{
	template<class K>
	class unordered_set
	{
		struct setkeyofT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename hash_bucket::HashTable<K, K, setkeyofT>::const_iterator iterator;
		typedef typename hash_bucket::HashTable<K, K, setkeyofT>::const_iterator const_iterator;
		const_iterator begin() const
		{
			return _ht.begin();
		}
		const_iterator end() const
		{
			return _ht.end();
		}
		pair<iterator,bool> insert(const K& key)//这里的iterator是const iterator,hashtable里面的insert的是普通的迭代器(注意,这里是类型不匹配,跟权限的放大缩小没关系)
		{
			return _ht.insert(key);
		}
	private:
		hash_bucket::HashTable<K, K, setkeyofT> _ht;
	};
}

这里的unordered_map和unordered_set几乎没有什么区别就不做讲解了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值