当前位置: 首页 > news >正文

c++:封装哈希表实现unordered_map与unordered_set

1.前期准备与框架搭建

第一步:将哈希表(链地址法)的头文件加进项目,然后创建两个头文件Unordered_Map和Unorderd_Set。

第二步:unordered_set框架与unordered_map框架搭建

unordered_set:

#pragma once
#include"hashtable.h"
namespace myself
{
	template<class K, class V>
	class unordered_map
	{
	
	public:
	
	private:
		HashTable<K, pair<K, V>> _ht;
	};
}

unordered_map:

#pragma once
#include"hashtable.h"
namespace myself
{
	template<class K>
	class unordered_set
	{
	public:

	private:
		HashTable<K, K> _ht;
	};
}

这里我们传给哈希表的参数不再是key和value,而是单独的k和容器存储的数据类型(set中是key,map中是pair<K,V>),这样可以兼容set和map进哈希表

1.1支持insert

(1)解决find和erase中需要key查找,而哈希表中不确定存储数据类型是key还是key_value的问题

解法:使用仿函数KeyOfT,仿函数作用是返回容器的key值引用

首先我们要在unordered_set中写一个SetOfT,返回值类型为K,不过需要注意的是返回的是const类型的,否则会导致权限放大

struct SetOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

然后是unordered_map

	struct MapOfT
		{
			const K& operator()(const pair<K,V>& data)
			{
				return data.first;
			}
		};

然后在哈希表中加一个模板参数KeyOfT,用于接收unordered_set和unordered_map的仿函数

(2)将key类型转换为可以进行取模运算的类型

解法:使用仿函数Func,对于特殊类型如string就进行类模板特化

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

使用struct是因为这个仿函数是需要哈希表可以直接调用的,所以用struct就可以默认开放给类外面

(3)更改哈希表中的insert

bool Insert(const T& data)
	{
		KeyOfT kot;
		Func fc;
		//不允许冗余
		if (Find(kot(data))) return false;
		//负载因子为1就扩容
		if (_n == _tables.size())
		{
			vector<Node*> newtable (2 * _tables.size());
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					//将旧的表中节点挪到新表中
					Node* next = cur->_next;
					size_t hash0 = fc(kot(cur->_data)) % newtable.size();

					//头插
					cur->_next = newtable[hash0];
					newtable[hash0] = cur;
					//更新cur
					cur = next;
				}
				_tables[i] = nullptr;
			}
			newtable.swap(_tables);
		}
		//头插
		size_t hash0 = fc(kot(data)) % _tables.size();
		Node* newnode = new Node(data);
		newnode->_next = _tables[hash0];
		_tables[hash0] = newnode;
		_n++;
		return true;
	}

1.只有需要使用key的情况我们才用kot将key取出来,如果就是要使用容器存储的数据类型,比如节点构造,那么我们就需要直接用data

2.func主要用于进行取模运算的时候,此时需要key是一个可以取模运算的数据类型

(4)更改Find与Erase

	Node* Find(const K& key)
	{
		Func fc;
		KeyOfT  kot;
		size_t hash0 = fc(key) % _tables.size();
		Node* cur = _tables[hash0];
		while (cur)
		{
			if (kot(cur->_data) == key) return cur;
			cur = cur->_next;
		}
		return nullptr;
	}
	bool Erase(const K& key)
	{
		KeyOfT kot;
		Func fc;
		size_t hash0 = fc(key) % _tables.size();
		Node* cur = _tables[hash0];
		Node* prv = nullptr;
		while (cur)
		{
			if (kot(cur->_data) == key)//delete
			{
				if (prv == nullptr)
				{
					_tables[hash0] = cur->_next;
				}
				else
				{
					prv->_next = cur->_next;
				}
				delete cur;
				--_n;
				return true;
			}
			prv = cur;
			cur = cur->_next;
		}
		return false;
	}

(5)实现析构

~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;
		}
	}

1.2支持iterator

 //哈希表前置声明
 template<class K, class T, class KeyOfT, class Func >
 class HashTable;
 //迭代器实现
 template<class K, class T, class KeyOfT, class Func >
 struct HTIterator
 {
	 typedef HashTable<K, T, KeyOfT, Func> HT;
	 typedef HashNode<T> Node;
	 typedef HTIterator<K, T, KeyOfT, Func> self;
	 //迭代器存储内容
	 HT* _ht;
	 Node* _node;
	 //构造
	 HTIterator(Node* node,HT* ht)
		 :_ht(ht)
		 ,_node(node)
	 {
	 }
	 //运算符重载
	 T* operator->()
	 {
		 return &(_node->_data);
	 }
	 T& operator*()
	 {
		 return _node->_data;
	 }
	 self& operator++()
	 {
		 Func fc;
		 KeyOfT kot;
		 //当前桶没走完
		 Node* cur = _node;
		 if (cur->_next)
		 {
			 _node = _node->_next; 
		 }
		 else//当前桶走完了,寻找下一个桶的头结点
		 {
			 size_t hashi = fc(kot(_node->_data)) % _ht->_tables.size();
			 hashi++;
			 while (hashi < _ht->_tables.size())//没有走完哈希表
			 {
				 if (_ht->_tables[hashi])
				 {
					 _node = _ht->_tables[hashi];
					 break;
				 }
				 hashi++;
			 }
			 if (hashi == _ht->_tables.size())
			 {
				 _node = nullptr;
			 }
		 }
		 return *this;
	 }
 bool operator!=(const self& s)
	 {
		 return _node != s._node;
	 }
	 bool operator ==(const self& s)
	 {
		 return _node == s._node;
	 }
 };

1.模板参数的缺省参数只能给一次,不能在多个地方重复给,这样会可能导致缺省参数混乱,所以在语法层面禁止了

2.需要在迭代器前面前置声明一下HashTable,告诉编译器他是一个类

3.迭代器++的逻辑:

(1)当前桶没有走完,那么就直接让迭代器指向下一个node,然后返回迭代器

(2)当前桶走完了,需要寻找下一个有数据的桶,若找到了就指向新桶的第一个数据,没有找到就指向nullptr。

最后返回迭代器本身

4.由于迭代器++的逻辑中使用了哈希表的private成员变量,所以我们需要让迭代器成为哈希表的友元函数


接下来我们实现哈希表中的迭代器

//实现begin和end
	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);
	}

begin:寻找哈希表中第一个节点,然后用该节点地址和哈希表本身地址构造迭代器

end:我们以nullptr表示没有节点了,所以用nullptr和哈希表本身地址构造迭代器

然后我们对unordered_set和unordered_map进行封装迭代器

注意要写在public修饰符内部

unordered_set:

		typedef typename HashTable<K, K, SetOfT>::Iterator iterator;
		iterator begin()
		{
			return _ht.Begin();
		}
		iterator end()
		{
			return _ht.End();
		}
	

unordered_map:

typedef typename HashTable<K, pair<K, V>, MapOfT>::Iterator iterator;
		iterator begin()
		{
			return _ht.Begin();
		}
		iterator end()
		{
			return _ht.End();
		}

1.3支持const_iterator

 支持const迭代器我们只需要在迭代器的模板参数中增加两个参数,Ref和Ptr即可,因为增加他们就可以控制运算符->和*的返回值类型

 //迭代器实现
 template<class K, class T, class Ref, class Ptr,class KeyOfT, class Func >
 struct HTIterator
 {
	 typedef HashTable<K, T, KeyOfT, Func> HT;
	 typedef HashNode<T> Node;
	 typedef HTIterator<K, T,Ref,Ptr,KeyOfT, Func> self;
	 //迭代器存储内容
	 const HT* _ht;
	 Node* _node;
	 //构造
	 HTIterator(Node* node,const HT* ht)
		 :_ht(ht)
		 ,_node(node)
	 {
	 }
	 //运算符重载
	 Ptr operator->()
	 {
		 return &(_node->_data);
	 }
	 Ref operator*()
	 {
		 return _node->_data;
	 }
	 self& operator++()
	 {
		 Func fc;
		 KeyOfT kot;
		 //当前桶没走完
		 Node* cur = _node;
		 if (cur->_next)
		 {
			 _node = _node->_next; 
		 }
		 else//当前桶走完了,寻找下一个桶的头结点
		 {
			 size_t hashi = fc(kot(_node->_data)) % _ht->_tables.size();
			 hashi++;
			 while (hashi < _ht->_tables.size())//没有走完哈希表
			 {
				 if (_ht->_tables[hashi])
				 {
					 _node = _ht->_tables[hashi];
					 break;
				 }
				 hashi++;
			 }
			 if (hashi == _ht->_tables.size())
			 {
				 _node = nullptr;
			 }
		 }
		 return *this;
	 }
	 bool operator!=(const self& s)
	 {
		 return _node != s._node;
	 }
	 bool operator ==(const self& s)
	 {
		 return _node == s._node;
	 }
 };

注意:这里我们修改了构造函数的参数,将HT*变为const属性,这是因为后续支持cosnt_iterator的时候会传递的对象是const属性,如果这里参数不是const属性会导致权限被放大。且这里我们即使加上了const也不会影响迭代器逻辑,因为迭代器中没有修改哈希表的部分

然后我们通过控制模板参数重命名一个const迭代器,并对unordered_set和unordered_map套上const迭代器

(1)在HashTable加上const迭代器

template<class K, class T, class KeyOfT,class Func = HashFunc<K>>
class HashTable
{
	//友元声明
	template<class K, class T,class Ref,class Ptr ,class KeyOfT, class Func >
	friend struct HTIterator;
	typedef HashNode<T> Node;
public:
	typedef HTIterator<K, T,T&,T*,KeyOfT, Func> Iterator;
	typedef HTIterator<K, T, const T&, const T*, KeyOfT, Func> Const_Iterator;
	HashTable(size_t size = 53)
		:_tables(size, nullptr)
		,_n(0)
	{
	}
	//实现begin和end
	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);
	}
	//实现const的begin和end
	Const_Iterator Begin() const
	{
		//找第一个节点
		for (size_t i = 0; i < _tables.size(); i++)
		{
			if (_tables[i])
			{
				return Const_Iterator(_tables[i], this);
			}
		}
		return End();
	}
	Const_Iterator End() const
	{
		return Const_Iterator(nullptr, this);
	}
	bool Insert(const T& data)
	{
		KeyOfT kot;
		Func fc;
		//不允许冗余
		if (Find(kot(data))) return false;
		//负载因子为1就扩容
		if (_n == _tables.size())
		{
			vector<Node*> newtable (2 * _tables.size());
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					//将旧的表中节点挪到新表中
					Node* next = cur->_next;
					size_t hash0 = fc(kot(cur->_data)) % newtable.size();

					//头插
					cur->_next = newtable[hash0];
					newtable[hash0] = cur;
					//更新cur
					cur = next;
				}
				_tables[i] = nullptr;
			}
			newtable.swap(_tables);
		}
		//头插
		size_t hash0 = fc(kot(data)) % _tables.size();
		Node* newnode = new Node(data);
		newnode->_next = _tables[hash0];
		_tables[hash0] = newnode;
		_n++;
		return true;
	}

(2)给unordered_set和unordered_map加上const迭代器

unordered_map:

typedef typename HashTable<K, pair<K, V>, MapOfT>::Iterator iterator;
		typedef typename HashTable<K, pair<K, V>, MapOfT>::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();
		}

unordered_set:

typedef typename HashTable<K, K, SetOfT>::Iterator iterator;
		typedef typename HashTable<K, K, SetOfT>::Const_Iterator const_iterator;
		const_iterator begin() const
		{
			return _ht.Begin();
		}
		const_iterator end() const
		{
			return _ht.End();
		}
		iterator begin()
		{
			return _ht.Begin();
		}
		iterator end()
		{
			return _ht.End();
		}

 

1.4将key现在可以修改的情况改为不可修改

 只需要在unordered_set和unordered_map中把容器存储类型中的key变为const属性即可

在private部分,typedef部分修改

typedef typename HashTable<K, const K, SetOfT>::Iterator iterator;
		typedef typename HashTable<K, const K, SetOfT>::Const_Iterator const_iterator;
		const_iterator begin() const
		{
			return _ht.Begin();
		}
		const_iterator end() const
		{
			return _ht.End();
		}
		iterator begin()
		{
			return _ht.Begin();
		}
		iterator end()
		{
			return _ht.End();
		}
		bool insert(const K& key)
		{
			return _ht.Insert(key);
		}
		void print(const unordered_set<int>& s)
		{
			const_iterator it = s.begin();
			while (it != s.end())
			{
				cout << *it << " ";
				it++;
			}
			cout << endl;
		}
		void testset()
		{
			unordered_set<int> s;
			s.insert(1);
			s.insert(2);
			print(s);
		}
	private:
		HashTable<K, const K,SetOfT> _ht;
typedef typename HashTable<K, pair<const K, V>, MapOfT>::Iterator iterator;
		typedef typename HashTable<K, pair<const K, V>, MapOfT>::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();
		}
		bool insert(const pair<const K,V>& kv)
		{
			return _ht.Insert(kv);
		}
		
	
	private:
		HashTable<K, pair<const K, V>,MapOfT> _ht;
	};

1.5实现operator[]

 operator[]的实现依赖于insert,而我们现在的insert还不是最终版本,接下来我们进行修改

第一步:修改Find()

Iterator Find(const K& key)
	{
		Func fc;
		KeyOfT  kot;
		size_t hash0 = fc(key) % _tables.size();
		Node* cur = _tables[hash0];
		while (cur)
		{
			if (kot(cur->_data) == key) return {cur,nullptr};
			cur = cur->_next;
		}
		return End();
	}

若找到了就用当前的cur构建迭代器返回,没有找到就返回end迭代器

第二步:修改insert

pair<Iterator,bool> Insert(const T& data)
	{
		KeyOfT kot;
		Func fc;
		//不允许冗余
		Iterator it = Find(kot(data));
		if (it != End()) return {it,false};
		//负载因子为1就扩容
		if (_n == _tables.size())
		{
			vector<Node*> newtable (2 * _tables.size());
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					//将旧的表中节点挪到新表中
					Node* next = cur->_next;
					size_t hash0 = fc(kot(cur->_data)) % newtable.size();

					//头插
					cur->_next = newtable[hash0];
					newtable[hash0] = cur;
					//更新cur
					cur = next;
				}
				_tables[i] = nullptr;
			}
			newtable.swap(_tables);
		}
		//头插
		size_t hash0 = fc(kot(data)) % _tables.size();
		Node* newnode = new Node(data);
		newnode->_next = _tables[hash0];
		_tables[hash0] = newnode;
		_n++;
		return { {newnode,this},true };
	}

第三步:实现operatoar[]

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

1.6在unordered_set和unordered_map中增加一个模板参数

 增加一个Func参数,主要是方便我们写仿函数控制key的数据类型可以转换为可以取模的类型

unordered_map的控制:

template<class K, class V, class Func = HashFunc<K>>
	class unordered_map
	{
		struct MapOfT
		{
			const K& operator()(const pair<const K,V>& data)
			{
				return data.first;
			}
		};

unordered_set的控制:

template<class K, class Func = HashFunc<K>>
	class unordered_set
	{
		struct SetOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

http://www.dtcms.com/a/107644.html

相关文章:

  • [dp_1] 使用最小花费爬楼梯 | 解码方法 | 虚拟dp[0]=0
  • 【输入某年某日,判断这是这一年的第几天】
  • 中小企业商标管理新选择:启服云。
  • Conmon lisp Demo
  • 如何在服务器里备份文件或系统
  • 基于NebulaGraph构建省市区乡镇街道知识图谱(二)
  • Bugku-眼见非实
  • 5.模型训练-毕设篇
  • HTML5手写签名板项目实战教程
  • linux -- php 扩展之xlswriter
  • DAY46 动态规划Ⅸ 股票问题Ⅱ
  • 机构数据服务
  • 搜索工具Everything下载安装使用教程(附安装包)
  • 网络安全的挑战与防护策略
  • Excel时间类型函数(包括today、date、eomonth、year、month、day、weekday、weeknum、datedif)
  • 大模型-提示词(Prompt)最佳实践
  • 【零基础入门unity游戏开发——2D篇】SpriteEditor图片编辑器
  • Unity 渲染流水线
  • 什么是编译和反编译
  • 【Python】Python 环境 + Pycharm 编译器 官网免费下载安装(图文教程,新手安装,Windows 10 系统)
  • 智能矢量化(地质类栅格图像)
  • python实战案例:销售数据BI动态分析仪表板
  • 今日行情明日机会——20250402
  • 任务堆积导致 OOM(内存溢出)
  • 08-MySQL InnoDB锁的基本类型
  • 【前端】电脑初始安装软件工具
  • 【Linux】内核驱动学习笔记(一)
  • 【论文笔记】DeepSeek-R1 技术报告
  • java虚拟机---JVM
  • python实战案例:财务凭证数据分析和生成报告