【知识讲解】 链式哈希表的实现与unordered_map和unordered_set的封装


目录

前言

[Part1. 哈希函数](#Part1. 哈希函数)

[Part2. 插入操作](#Part2. 插入操作)

[Part3. 查找操作](#Part3. 查找操作)

[Part4. 删除操作](#Part4. 删除操作)

[Part5. unordered_map和unordered_set的封装](#Part5. unordered_map和unordered_set的封装)

[Part6. 链式哈希表的封装实现](#Part6. 链式哈希表的封装实现)

结语


前言

上篇文章:【知识讲解】 哈希表的介绍与实现-CSDN博客


在上篇文章我们讲述了哈希表的开放寻址法,也谈到了他的一些缺点,今天我们来讲一讲链式哈希表的实现,他相比较于开放寻址法有着许多的优势,我们来看看吧。


let's go!!!!!!!!


Part1. 哈希函数

由于除留余数法要用到模运算,其只适用于无符号整形类型(因为负号模后为负数),但是我们在实际运用时,会用到string还有自己设计的一些类型,所以我们需要一个函数让各种类型转化为无符号整型,我们来看代码:


cpp 复制代码
template<class K>
struct Hash
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
template<>//其他无法直接转化为无符号整型的用特化来转化为无符号整型
struct Hash<std::string>
{
	size_t operator()(const std::string& key)
	{
		size_t hash0 = 0;
		for (int i = 0; i < key.size(); i++)
		{
			hash0 = hash0 * 131 + key[i];//乘以131来使得分散
		}
		return hash0;
	}
};

Part2. 插入操作

对于链式哈希表我们怎么进行插入呢?我们来看:



我们根据x的key计算出来他在数组中的映射下标,再使用头插这样就做到了O(1)的时间复杂度,关键在于扩容,按道理来说这个是用链表不会超过容量,但是当每个节点下面挂的链表节点更多,在查询时花费时间就会多,所以当负载因子大于一时,我们就要扩容,让数组更多,自然下面挂的链表的节点就会少,增加查询的效率。我们来看扩容:


cpp 复制代码
bool insert(const std::pair<K, V> kv)
{
	if (find(kv.first) != nullptr)
	{
		return false;
	}
	if (_n >=_tables.size())//扩容
	{
		std::vector<Node*> newtable(__stl_next_prime(_tables.size() + 1));//新的数组 用素数表扩容
		for (int i = 0; i < _tables.size(); i++)
		{
			Node* tem= _tables[i];
			while (tem != nullptr)/
			{
				Node* nextp = tem->_next;
				size_t hash0 = HashFunc()(tem->_kv.first) % newtable.size();//计算出每一个节点在新数组的映射位置
				tem->_next = newtable[hash0];//用头插插入
				newtable[hash0] = tem;
				tem = nextp;
			}
			_tables[i] = nullptr;
		}
		_tables.swap(newtable);//交换
	}
	size_t hash0 = HashFunc()(kv.first) % _tables.size();//插入
	Node* newnode = new Node(kv);
	newnode->_next = _tables[hash0];
	_tables[hash0] = newnode;
	++_n;
	return true;
}

Part3. 查找操作

查找就比较简单了,我们来看:


cpp 复制代码
Node* find(const K& key)
{
	size_t hash0 = HashFunc()(key)% _tables.size();
	Node* tem = _tables[hash0];//开始在对应的地方查找
	while (tem != nullptr)
	{
		if (tem->_kv.first == key)
		{
			return tem;
		}
		tem = tem->_next;
	}
	return nullptr;
}

Part4. 删除操作

删除的操作就和链表的删除相似,就是删除链表的头节点和中间节点这两个情况,我们来看:


cpp 复制代码
bool erase(const K& key)
{
	size_t hash0 = HashFunc()(key) % _tables.size();
	Node* tem = _tables[hash0];
	Node* prev = nullptr;
	while (tem != nullptr)
	{
		if (tem->_kv.first == key)
		{
			if (prev == nullptr)//分为头节点和中间节点的两个情况
			{
				_tables[hash0] = tem->_next;
			}
			else
			{
				prev->_next = tem->_next;
			}
			delete tem;
			_n--;
			return true;
		}
		else
		{
			prev = tem;
			tem = tem->_next;
		}
	}
	return false;
}

Part5. unordered_map和unordered_set的封装

上面我们完成了链式哈希表的实现,接下来我们就可以使用这个来完成对于unordered_map和unordered_set的封装。我们来看:


这个的封装关键在于迭代器的实现,这迭代器的实现与map和set的迭代器实现相似。我们来都看看吧。我们以unordered_map来举例。


cpp 复制代码
template<class K,class V>
class unordered_map
{
	struct MapKeyOfT
	{
		const K& operator()(const std::pair<const K,V>& key)//萃取器 萃取出元素方便后续的使用
		{
			return key.first;
		}
	};
public:
	typedef typename HashTable< K, std::pair<const K, V>, MapKeyOfT>::iterator iterator;//封装迭代器
	typedef typename HashTable< K, std::pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;

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

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

	const_iterator end()const
	{
		return _ht.end();
	}

	const_iterator begin()const
	{
		return _ht.begin();
	}

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

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

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

	V& operator[](const K& key)
	{
		std::pair<iterator, bool> ret = insert({ key,V() });//unordered_map特有的[] 当这个key不存在时 就自动插入
		return ret.first->second;
	}

	void printf_all_node()const 
	{
		const_iterator it = begin();
		while (it != end())
		{
			std::cout << it->first << " ";
			++it;
		}
		std::cout << std::endl;
	}
private:
	HashTable< K, std::pair<const K,V>, MapKeyOfT> _ht;//成员

};

Part6. 链式哈希表的封装实现

我们来看代码:


cpp 复制代码
template<class K>
struct Hash
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
template<>
struct Hash<std::string>
{
	size_t operator()(const std::string& key)
	{
		size_t hash0 = 0;
		for (int i = 0; i < key.size(); i++)
		{
			hash0 = hash0 * 131 + key[i];
		}
		return hash0;
	}
};


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

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

template<class K, class Ref, class Ptr, class T, class KeyOfT, class HashFunc = Hash<K>>
class HashIterator;

template<class K, class T, class KeyOfT,class HashFunc = Hash<K>>
class HashTable
{
	typedef HashNode<T> Node;
public:
	typedef HashIterator<K, T&, T*, T, KeyOfT, HashFunc> iterator;
	typedef HashIterator<K, const T&, const T*, T, KeyOfT, HashFunc> const_iterator;

	HashTable()
		:_tables(__stl_next_prime(0))
		, _n(0)
	{
	}

	~HashTable()
	{
		for (int i = 0; i < _tables.size(); i++)
		{
			while (_tables[i] != nullptr)
			{
				erase(KeyOfT()(_tables[i]->_data)); //析构函数 复用代码
			}
		}
	}

	HashTable(const HashTable<K,T, HashFunc>& hash)
	{
		for (int i = 0; i < hash._tables.size(); i++)
		{
			Node* cur = hash._tables[i];
			while (cur != nullptr)
			{
				this->insert(cur->_kv);//复用代码
				cur = cur->_next;
			}
		}
	}

	HashTable<K, T, HashFunc>& operator=(HashTable<K, T, HashFunc> hash)
	{
		swap(hash);
		return *this;
	}

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

	iterator begin()
	{
		if (_n == 0)
		{
			return end();
		}
		for (int i = 0; i < _tables.size(); i++)
		{
			if (_tables[i] != nullptr)
			{
				return iterator(_tables[i],this);
			}
		}
		return end();
	}

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

	const_iterator begin()const
	{
		if (_n == 0)
		{
			return end();
		}
		for (int i = 0; i < _tables.size(); i++)
		{
			if (_tables[i] != nullptr)
			{
				return const_iterator(_tables[i], this);
			}
		}
		return end();
	}

	void swap(HashTable<K,T, HashFunc>& hash)
	{
		std::swap(_tables, hash._tables);
		std::swap(_n, hash._n);
	}

	std::pair<iterator, bool> insert(const T& kv)
	{
		if (find(KeyOfT()(kv)) != end())
		{
			return { find(KeyOfT()(kv)),false };
		}
		if (_n >= _tables.size())
		{
			std::vector<Node*> newtable(__stl_next_prime(_tables.size() + 1));
			for (int i = 0; i < _tables.size(); i++)
			{
				Node* tem = _tables[i];
				while (tem != nullptr)
				{
					Node* nextp = tem->_next;
					size_t hash0 = HashFunc()(KeyOfT()(tem->_data)) % _tables.size();
					tem->_next = newtable[hash0];
					_tables[hash0] = tem;
					tem = nextp;
				}
				_tables[i] = nullptr;;
			}
			_tables.swap(newtable);
		}
		size_t hash0 = HashFunc()(KeyOfT()(kv)) % _tables.size();
		Node* newnode = new Node(kv);
		newnode->_next = _tables[hash0];
		_tables[hash0] = newnode;
		++_n;
		return { iterator(newnode,this),true };
	}

	iterator find(const K& key)
	{
		size_t hash0 = HashFunc()(key) % _tables.size();
		Node* tem = _tables[hash0];
		while (tem != nullptr)
		{
			if (KeyOfT()(tem->_data) == key)
			{
				return iterator(tem,this);
			}
			tem = tem->_next;
		}
		return end();
	}
	bool erase(const K& key)
	{
		size_t hash0 = HashFunc()(key) % _tables.size();
		Node* tem = _tables[hash0];
		Node* prev = nullptr;
		while (tem != nullptr)
		{
			if (KeyOfT()(tem->_data) == key)
			{
				if (prev == nullptr)
				{
					_tables[hash0] = tem->_next;
				}
				else
				{
					prev->_next = tem->_next;
				}
				delete tem;
				_n--;
				return true;
			}
			else
			{
				prev = tem;
				tem = tem->_next;
			}
		}
		return false;
	}

	size_t size()const 
	{
		return _tables.size();
	}

	Node* head_node(size_t i)const//由于private修饰我们多加接口来用于迭代器的使用
	{
		return _tables[i];
	}
private:
	inline unsigned long __stl_next_prime(unsigned long n)
	{
		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
		};
		const unsigned long* first = __stl_prime_list;
		const unsigned long* last = __stl_prime_list + __stl_num_primes;
		const unsigned long* pos = std::lower_bound(first, last, n);
		return pos == last ? *(last - 1) : *pos;
	}

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


template<class K,class Ref,class Ptr ,class T, class KeyOfT, class HashFunc>
class HashIterator
{
	typedef HashNode<T> Node;
	typedef HashTable<K, T, KeyOfT, HashFunc> HT;
	typedef HashIterator<K, Ref, Ptr, T, KeyOfT, HashFunc> Self;
public:
	HashIterator(Node* node, const HT* ht)//这里需要加上const上面在const_iterator时 会传过来const修饰的this 如果不加上const 权限缩小报错 因此我们需要加上const 权限平级
		:_node(node)
		,_ht(ht)
	{
	}

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

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

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

	Self& operator++()//++的实现 要是到了一条链的终点 则跳转到下一个下标的链表开头
	{
		Node* old = _node;
		_node = _node->_next;
		if (_node == nullptr)
		{
			size_t hash0 = HashFunc()(KeyOfT()(old->_data)) % _ht->size();
			hash0++;
			while (hash0 < _ht->size())
			{
				if (_ht->head_node(hash0) != nullptr)
				{
					_node = _ht->head_node(hash0);
					break;
				}
				hash0++;
			}
		}
		return *this;
	}
private:
	Node* _node;
	const HT* _ht;
};

结语

这篇文章我们认识到了unordered_map和unordered_set的封装,接下来,小编还会带来C++11的知识,敬请期待~
最后,祝大家可以:春风得意马蹄疾,一日看尽长安花!

最后的最后,要是觉得本文还可以的话,可以点点赞,关注小编一波,谢谢大家!~

相关推荐
haolin123.8 小时前
数据结构--二叉树
数据结构
不如语冰8 小时前
AI大模型入门-参数的传递
数据结构·人工智能·pytorch·python
流浪0019 小时前
数据结构篇(四):线性表——链表——双链表
数据结构·链表
小七在进步10 小时前
数据结构:堆排序
数据结构
2401_841495641 天前
【数据结构】B+树
数据结构·数据库·c++·b+树·概念·结构·操作原理
皓月斯语1 天前
【C++基础】三目运算符
开发语言·数据结构·c++
剑锋所指,所向披靡!1 天前
数据结构之拓扑排序
数据结构
夜不会漫长1 天前
数据结构:链表
数据结构·链表
剑锋所指,所向披靡!1 天前
数据结构之关键路径
数据结构·算法