数据结构篇【5】——哈希表开散列实现(哈希桶)及封装

开散列

在上一篇文章中我们解决哈希冲突的方法是闭散列,在本篇介绍另一种方法——开散列。在闭散列中如果我们碰到哈希冲突的时候我们采用的办法是向后查找,并占用另一个空余的位置,这种方法不好的地方就在于在不断的占用中哈希表的位置看起来好像没什么逻辑,而且容易产生“堆积”问题,导致查找元素的效率较低,另外我们始终要保证已经占用的位置不多于哈希表总大小的70%,因此还会造成较大的空间浪费。
开散列又叫链地址发(开链法、哈希桶),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶元素中的元素通过一个单链表链接起来,各链表的头节点存储在哈希表中,插入方式如下图。
在这里插入图片描述
除了完成哈希表的开散列实现之外,本篇还将完成哈希表迭代器的设计以及unordered_map和unordered_set的封装。
本文将创建三个文件:HashTable.h,UnorderedMap.h,UnorderedSet.h,分别用来实现哈希表的实现,UnorderedMap.h和UnorderedSet.h的封装。

HashTable

这是本篇的核心代码,与上一篇文章的不同点在于本篇HashTable处理哈希冲突的方法是开散列,并且为HashTable设计了迭代器,下面笔者会从HashTable的框架开始尽量讲清楚代码的实现。

namespace LinkHash
{
	template<class T>
	struct HashNode
	{
		T _data;
		HashNode<T>* _next;
		HashNode(const T& data)
			: _data(data)
			, _next(nullptr)
		{}
	};
	template <class K, class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
	struct __HTIterator
	{
		Node* _node;
		HashTable<K, T, KeyOfT, HashFunc>* _pht;
		__HTIterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht);
		Ref operator*();
		Ptr operator->();
		Self& operator++();
		bool operator!=(const Self& s) const;
		bool operator==(const Self& s) const;
	};
	template<class K, class T, class KeyOfT, class HashFunc>
	class HashTable
	{
	public:
		HashTable() = default; // 默认构造
		HashTable(const Self& ht); // 拷贝构造
		Self& operator=(Self copy);
		~HashTable();
		iterator begin();
		iterator end();
		bool Erase(const K& key);
		iterator Find(const K& key);
		pair<iterator, bool> Insert(const T& data);
	private:
		vector<Node*> _tables;
		size_t _n = 0;
	};
}

以上就是我们这次要实现的全部接口,有些眼花缭乱吧?不过只要能够按顺序来分析,其实实现并不复杂。那么接下来就从迭代器开始正式的代码吧!

// __HTIterator一共有6个模板参数
// 分别代表key, data, data引用, data指针, 从data中提取key值的仿函数, hash函数
template <class K, class T, class Ref, class Ptr, class KeyOfT, class HashFunc>

为方便编写和查看我们把名字比较长的元素重命名一下

typedef HashNode<T> Node;
typedef __HTIterator<K, T, Ref, Ptr, KeyOfT, HashFunc> Self;

构造函数
构造函数中我们除了初始化一个节点(_node)之外,我们还初始化了一个哈希表的指针,原因在实现++运算符的代码中会分析。

__HTIterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht)
	: _node(node)
	, _pht(pht)
{}

提取数据
两个运算符:*,->,实现很简单,需要注意的是->运算符如果返回的是pair的地址,那么在提取其中元素时我们本应该在加上一个->但这样看起来很不合理,因此实际上编译器会自动给我们在加上一个->
在这里插入图片描述

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

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

++
这个操作是迭代器的一个难点,问题在于我们应该怎样去遍历哈希表。方法其实很直观,哈希表可以看成是一连串的桶,我们只要从第一列桶开始一次向后一串一串地遍历就行。
在这里插入图片描述
因此我们只需要考虑两种情况:

  1. 当节点在桶的底部
    解决方法:不断遍历下一个下标,直到该下标不为空就停止
  2. 当节点不在桶的底部
    解决方法:桶中的下一个节点就是目标节点
Self& operator++()
{
	// 情况2
	if (_node->_next)
	{
		_node = _node->_next;
	}
	// 情况1
	else
	{
		KeyOfT kot;
		HashFunc hf;
		// 寻找该迭代器所在下标
		size_t index = hf(kot(_node->_data)) % _pht->_tables.size();
		++index;
		// 找下一个不为空的桶
		// 若要寻找下一个下标,就需要使用到哈希表,这就是
		// 在迭代器中存储哈希表地址的原因
		while (index < _pht->_tables.size())
		{
			if (_pht->_tables[index])
			{
				break;
			}
			else
			{
				++index;
			}
		}
		if (index == _pht->_tables.size())
		{
			_node = nullptr;
		}
		else
		{
			_node = _pht->_tables[index];
		}
	}
	return *this;
}

!=和=
这两个运算符的实现也很简单,只需要比较节点的地址是否相同即可

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

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

这就是迭代器的全部实现,看完了是不是觉得其实代码并不复杂?接下来我们继续实现HashTable类!
先来看看HashTable的模板参数,

HashTable一共有4个模板参数,key,data,提取key的仿函数,hash函数
template<class K, class T, class KeyOfT, class HashFunc>

同样的,为了编写方便我们给几个比较长的元素进行重命名,

	typedef HashNode<T> Node;
	typedef HashTable<K, T, KeyOfT, HashFunc> Self;

另外在这个HashTable中我们要增加一个迭代器,并把迭代器设为友元让他能够访问HashTable中的成员参数。

	template<class K, class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
	friend struct __HTIterator;
public:
	typedef __HTIterator<K, T, T&, T*, KeyOfT, HashFunc> iterator;

构造函数
不难发现HashTable中只有_tables和_n两个参数需要初始化,对于_tables编译器会自动调用vector的构造函数,而对于_n我们已经给它赋了初始值0,因此对于默认构造我们其实不需要对他另作处理,那么我们先来看看拷贝构造。

HashTable(const Self& ht)
{
	_tables.resize(ht._tables.size());
	// 与++的实现类似,从下标为0开始,依次向后遍历每一串桶
	for (size_t i = 0; i < ht._tables.size(); ++i)
	{
		Node* cur = ht._tables[i];
		while (cur)
		{
			// 使用头插提高插入效率
			Node* copy = new Node(cur->_data);
			copy->_next = _tables[i];
			_tables[i] = copy;

			cur = cur->_next;
		}
	}
}

在完成了拷贝构造后会意外地发现默认构造无法使用了,原因是编译器在程序员没有自己实现构造函数的时候会使用默认构造,但只要程序员写了构造函数就不会再提供默认的构造函数,而我们刚刚完成了拷贝构造,因此编译器在执行默认构造时将无法得到构造函数,为了解决这个问题我们有两种方法:

// 方法一
HashTable() {}
// 方法二
HashTable() = default;

=
赋值操作其实也非常简单,只需要灵活地使用一下拷贝构造就行

// 注意这里我没有使用引用传值,这样可以让编译器在传值
// 的时候自动调用拷贝构造,而且因为这个copy参数的作用
// 域只在函数之内,因此在跳出函数时编译器会自动调用它
// 的析构函数
Self& operator=(Self copy)
{
	swap(_n, copy._n);
	// 将原来的table和copy的table交换
	// 这样编译器在调用copy析构函数时会
	// 自动释放原来的table
	_tables.swap(copy._tables);
	return *this;
}

析构函数
看到这里大家应该能大概猜到析构函数是怎样执行的了,遍历顺序和拷贝构造相同,也是从0下标开始依次向后遍历每一串桶,并将节点删除

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

begin和end
这两个方法的实现也很简单,先从end开始吧

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

而对于begin来说,开始的位置一定是链表中的第一个,因此我们只需要从table的0下标开始依次向后查找第一个不为空的下标就行,如果没有节点就返回end

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

	return end();
}

Find
还没讲插入就开始说查找可能有些突兀,但因为在插入中需要使用查找方法,因此笔者就现在这里介绍一下插入的方法,而因为查找的方式与插入的方式是一样的,这样读者就可以很轻松地理解查找的代码。在这里笔者依旧采用除留余数法来确定节点所在下标,即
I n d e x = H a s h ( k e y ) % T a b l e . s i z e Index = Hash(key)\%Table.size Index=Hash(key)%Table.size
确定下标后我们依次在该链表下进行查询,若找不到此节点则返回end,实现如下

iterator Find(const K& key)
{
	if (_tables.empty())
	{
		return end();
	}

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

}

Insert
Insert在寻找下标的过程中和Find一模一样,只不过在找到下标后不需要再向下查找节点,只需对下标所在节点进行头插即可,不过在实现Insert时还需要注意一点,即同一个元素不能在同一个表中存在两次,因此我们在进行插入操作前还需用Find检查该元素是否已经存在于表中。

// 返回值是pair类型,第二个元素代表插入是否成功,即该元素
// 是否已经存在
pair<iterator, bool> Insert(const T& data)
{
	KeyOfT kot;
	iterator ret = Find(kot(data));
	// 若已经存在,返回已经存在节点的迭代器和false
	if (ret != end()) return make_pair(ret, false); // 已存在
	HashFunc hf;
	// 若已存在的节点数等于表的大小就扩容
	if (_n == _tables.size() && _n != 4294967291)
	{
		// 研究表明当表的大小为指数时可以减小哈希冲突的可能性
		// 因此在这里设计一个GetNextPrime来得到一个更大的质数
		size_t newSize = GetNextPrime(_tables.size());
		if (newSize == _tables.size())
		{
			// 容量已到最大,不再扩容
		}
		vector<Node*> newTables;
		newTables.resize(newSize);
		for (size_t i = 0; i < _tables.size(); ++i)
		{
			Node* cur = _tables[i];
			while (cur)
			{
				Node* next = cur->_next;
				size_t index = hf(kot(cur->_data)) % newTables.size();
				cur->_next = newTables[index];
				newTables[index] = cur;

				cur = next;
			}
			_tables[i] = nullptr;
		}
		_tables.swap(newTables);
	}
	size_t index = hf(kot(data)) % _tables.size();
	Node* newnode = new Node(data);
	newnode->_next = _tables[index];
	_tables[index] = newnode;
	++_n;
	return make_pair(iterator(newnode, this), true);
}

GetNextPrime

size_t GetNextPrime(size_t num)
{
static const unsigned long __stl_prime_list[28] =
{
	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
};

for (size_t i = 0; i < 28; ++i)
{
	if (__stl_prime_list[i] > num)
	{
		return __stl_prime_list[i];
	}
}
return __stl_prime_list[27];
}

Erase

Erase的查找目标节点的方式跟Find和Insert一样,笔者就不再赘述了,具体流程就是找到需要删除的元素,并将他的上下两个节点连接,然后删除目标节点。

bool Erase(const K& key)
{
	if (_tables.empty())
	{
		return false;
	}

	HashFunc hf;
	size_t index = hf(key) % _tables.size();
	Node* prev = nullptr;
	Node* cur = _tables[index];
	KeyOfT kot;
	while (cur)
	{
		if (kot(cur->_data) == key)
		{
			if (prev == nullptr) // 头删
			{
				_tables[index] = cur->_next;
			}
			else // 中间删除
			{
				prev->_next = cur->_next;
			}
			--_n;
			delete cur;
			return true;
		}
		else
		{
			prev = cur;
			cur = cur->_next;
		}
	}
	return false;
}

到这里本篇最核心的代码就讲完了,接下来我们只需要完成UnorderedMap和UnorderedSet的封装就大功告成!

UnorderedMap

代码框架

namespace ssj
{
	// 三个模板参数,key,value,hash函数
	template<class K, class V, class hash = Hash<K>>
		class unordered_map
		{
			struct MapKeyOfT
			{
				const K& operator()(const pair<K, V>& kv)
				{
					return kv.first;
				}
			};
		public:
			// 因为HashTable是一个类名而不是对象名,因此在
			// 前面加typename
			typedef typename LinkHash::HashTable<K, pair<K, V>, MapKeyOfT, hash>::iterator iterator;
			iterator begin();
			iterator end();
			V& operator[](const K& key);
			pair<iterator, bool> insert(const pair<K, V>& kv);
		private:
		LinkHash::HashTable<K, pair<K, V>, MapKeyOfT, hash> _ht;
		};
}

begin,end,insert
这三个方法的实现很简单,直接调用HashTable的函数就行了

iterator begin()
{
	return _ht.begin();
}

iterator end()
{
	return _ht.end();
}

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

[]

V& operator[](const K& key)
{
	auto ret = _ht.Insert(make_pair(key, V()));
	return ret.first->second;
}

UnorderedSet

框架

namespace ssj
{
	// 两个模板变量,key,hash函数
	template <class K, class hash = Hash<K>>
	class unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename LinkHash::HashTable<K, K, SetKeyOfT, hash>::iterator iterator;
		iterator begin();
		iterator end();
		pair<iterator, bool> insert(const K& key);
private:
		LinkHash::HashTable<K, K, SetKeyOfT, hash> _ht;		

begin

iterator begin()
{
	return _ht.begin();
}

end

iterator end()
{
	return _ht.end();
}

insert

pair<iterator, bool> insert(const K& key)
{
	return _ht.Insert(key);
}

到这里对本篇的内容就结束了!接下来请看完整的代码。

完整代码

HashTable.h

template<class K>
struct Hash
{
	size_t operator()(const K& key)
	{
		return key;
	}
};

// 特化
template<>
struct Hash < string >
{
	size_t operator()(const string& s)
	{
		// BKDR
		size_t value = 0;
		for (auto ch : s)
		{
			value *= 31;
			value += ch;
		}
		return value;
	}
};


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

	template <class K, class T, class KeyOfT, class HashFunc>
	class HashTable;

	template <class K, class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
	struct __HTIterator
	{
		typedef HashNode<T> Node;
		typedef __HTIterator<K, T, Ref, Ptr, KeyOfT, HashFunc> Self;

		Node* _node;
		HashTable<K, T, KeyOfT, HashFunc>* _pht;

		__HTIterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht)
			: _node(node)
			, _pht(pht)
		{}

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

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

		Self& operator++()
		{
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else
			{
				KeyOfT kot;
				HashFunc hf;
				size_t index = hf(kot(_node->_data)) % _pht->_tables.size();
				++index;
				// 找下一个不为空的桶
				while (index < _pht->_tables.size())
				{
					if (_pht->_tables[index])
					{
						break;
					}
					else
					{
						++index;
					}
				}
				if (index == _pht->_tables.size())
				{
					_node = nullptr;
				}
				else
				{
					_node = _pht->_tables[index];
				}
			}
			return *this;
		}

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

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

	};

	template<class K, class T, class KeyOfT, class HashFunc>
	class HashTable
	{
		typedef HashNode<T> Node;
		template<class K, class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
		friend struct __HTIterator;
		typedef HashTable<K, T, KeyOfT, HashFunc> Self;
	public:
		typedef __HTIterator<K, T, T&, T*, KeyOfT, HashFunc> iterator;

		// 显示指定编译器去生成默认构造函数
		HashTable() = default;

		HashTable(const Self& ht)
		{
			_tables.resize(ht._tables.size());
			for (size_t i = 0; i < ht._tables.size(); ++i)
			{
				Node* cur = ht._tables[i];
				while (cur)
				{
					Node* copy = new Node(cur->_data);
					copy->_next = _tables[i];
					_tables[i] = copy;

					cur = cur->_next;
				}
			}
		}

		Self& operator=(Self copy)
		{
			swap(_n, copy._n);
			_tables.swap(copy._tables);
			return *this;
		}

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

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

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

		bool Erase(const K& key)
		{
			if (_tables.empty())
			{
				return false;
			}

			HashFunc hf;
			size_t index = hf(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[index];
			KeyOfT kot;
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					if (prev == nullptr) // 头删
					{
						_tables[index] = cur->_next;
					}
					else // 中间删除
					{
						prev->_next = cur->_next;
					}
					--_n;
					delete cur;
					return true;
				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;
		}

		iterator Find(const K& key)
		{
			if (_tables.empty())
			{
				return end();
			}

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

		}

		size_t GetNextPrime(size_t num)
		{
			static const unsigned long __stl_prime_list[28] =
			{
				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
			};

			for (size_t i = 0; i < 28; ++i)
			{
				if (__stl_prime_list[i] > num)
				{
					return __stl_prime_list[i];
				}
			}
			return __stl_prime_list[27];
		}

		pair<iterator, bool> Insert(const T& data)
		{
			KeyOfT kot;
			iterator ret = Find(kot(data));
			if (ret != end()) return make_pair(ret, false); // 已存在
			HashFunc hf;
			if (_n == _tables.size())
			{
				size_t newSize = GetNextPrime(_tables.size());
				if (newSize == _tables.size())
				{
					// break;
				}
				vector<Node*> newTables;
				newTables.resize(newSize);
				for (size_t i = 0; i < _tables.size(); ++i)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;
						size_t index = hf(kot(cur->_data)) % newTables.size();
						cur->_next = newTables[index];
						newTables[index] = cur;

						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newTables);
			}
			size_t index = hf(kot(data)) % _tables.size();
			Node* newnode = new Node(data);
			newnode->_next = _tables[index];
			_tables[index] = newnode;
			++_n;
			return make_pair(iterator(newnode, this), true);
		}

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

}

UnorderedMap.h

namespace ssj
{
	template<class K, class V, class hash = Hash<K>>
	class unordered_map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename LinkHash::HashTable<K, pair<K, V>, MapKeyOfT, hash>::iterator iterator;
		iterator begin()
		{
			return _ht.begin();
		}

		iterator end()
		{
			return _ht.end();
		}

		V& operator[](const K& key)
		{
			auto ret = _ht.Insert(make_pair(key, V()));
			return ret.first->second;
		}

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

	private:
		LinkHash::HashTable<K, pair<K, V>, MapKeyOfT, hash> _ht;
	};
}

UnorderedSet.h

namespace ssj
{
	template <class K, class hash = Hash<K>>
	class unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename LinkHash::HashTable<K, K, SetKeyOfT, hash>::iterator iterator;
		iterator begin()
		{
			return _ht.begin();
		}

		iterator end()
		{
			return _ht.end();
		}

		pair<iterator, bool> insert(const K& key)
		{
			return _ht.Insert(key);
		}
	private:
		LinkHash::HashTable<K, K, SetKeyOfT, hash> _ht;
	};
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JayceSun449

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值