使用哈希表封装myunordered_set和myunordered_map

我们今天又见面啦,给生活加点impetus!!开启今天的编程之路!!
在这里插入图片描述
今天我们使用前面已经实现过的哈希表来实现myunordered_set和unordered_map
作者:٩( ‘ω’ )و260
我的专栏:C++进阶C++初阶数据结构初阶题海探骊c语言
欢迎点赞,关注!!

使用哈希表封装myunordered_set和myunordered_map

实现出复用哈希表框架,并支持insert

与map和set相比而言,unordered系列的实现更加复杂。
首先,unoedered_set是key类型,unordered_map是key,value类型,要实现一个底层实现两种数据结构,我们必须使用模版,这里我们传递三个参数,第一个是K,第二个是T,第三个是KeyOfT
三个模版的作用

第一个:传递K是为了find和erase接口,因为find和erase接口只能够使用key作为实参
第二个:代表哈希表中结点的存储类型
第三个:因为我们来寻找映射位置的时候需要取模,key一般可以直接取,但是pair必须需要取出其中的key

接下来我们来看代码:

template<class K>
class myunordered_set
{
	struct SetOfT
	{
		const K& operator(const K&key)
		{
			return key;
		}
	}
public:
	
private:
	HashTable<K,K,SetOfT> _tables;//底层结构是哈希桶
}
template<class K,class V>
class myunordered_map
{
	struct MapOfT
	{
		K& operator()(const pair<K,V>&kv)
		{
			return kv.first;
		}
	}
private:
	HashTable<K,pair<K,V>,MapOfT> _tables;底层结构是哈希桶
}

因为存储的数值不确定,所以哈希结点也需要修改:

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

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

接下来我们来写insert函数,insert是重点,我们主要还写写的是伪代码。
Insert操作还是主题思路不变。
1:先求key映射的偏移位置(细节:key可能需要取,其次,可能需要转换成整形)这里会用到两层仿函数
2:不断头插
3:检查是否需要扩容(细节:定义的是哈希数组,而非一个哈希表,直接把结点给他拿下来就行)
4:注意返回值:我们直接模仿库中的insert函数返回pair<iterator,bool>类型(为什么我们实现这个insert函数重载?因为为了实现unordered_map支持operator[ ]做准备)
在这里插入图片描述
来看代码实现
注意:此时迭代器里面有两个成员变量,一个是_node,一个是哈希表,原因会在下面说.

pair<iterator,bool> Insert()(const T&data)
{
	KeyOfT kot;//取key
	Hash hs;//转整形,只有在取模的时候才用
	iterator it=Find(kot(data));
	if(it!=End()) return {it,false};//插入失败+去重

		if (_n == _tables.size())
	{
		
		//两种逻辑,优先第二种,第一种是拷贝结点,删除旧结点,第二种是直接把结点给拿下来
		//第一种:创建新的哈希表,第二种:创建新的数组
		//HashTable<K, V, Hash> newht(__stl_next_prime(_tables.size() + 1));//加1才能够继续取到更到的素数
		遍历旧表,将旧表的数据全部重新映射到新表
		//for (int i = 0;i < _tables.size();i++)
		//{
		//	Node* cur = _tables[i];
		//	while (cur)
		//	{
		//		newht.Insert(cur->_kv);
		//		cur = cur->_next;
		//	}
		//}
		//_tables.swap(newht._tables);
		//为什么第一种方法会重新再拷贝构造,因为复用的时候会调用new这个关键字

		vector<Node*> newtables(__stl_next_prime(_tables.size() + 1),nullptr);//数组初始化为n个nullptr
		for (size_t i = 0;i < _tables.size();i++)
		{
			Node* cur = _tables[i];
			while (cur)
			{
				Node* next = cur->_next;//这里我们存储一下cur的下一个位置,因为当我们将cur下一个位置放下来的时候,_next被修改,所以需要提前记录一下
				//重新计算映射位置
				size_t hashi = hs(kot(cur->_data)) % newtables.size();
				cur->_next = newtables[hashi];
				newtables[hashi] = cur;

				cur = next;
			}
			//因为原来的旧链表的值已经被使用过了,所以我只直接将其置为nullptr
			_tables[i] = nullptr;
		}
		//底层是数组,例如vector或者string,调用库中的交换函数
		_tables.swap(newtables);
	}
	size_t hashi = hs(kot(data)) % _tables.size();
	
	Node* newnode = new Node(data);//这里不是new Node(kot(data));//pair类型结点里面存的是pair,不是key
	//优先选择头插进入链表,尾插需要向后遍历,头插分两种情况,映射位置为空或有数据,下面的代码两者都可以适用
	newnode->_next = _tables[hashi];
	_tables[hashi] = newnode;
	_n++;
	return {Iterator(newnode,this),true};
}

支持迭代器的实现

首先,因为哈希表中成员是一个单链表,所以我的的迭代器肯定是单向迭代器,即我们的迭代器只能支持++,不支持- -,同样也不支持随机访问。

迭代器的成员变量肯定是有一个_node。
当我们需要返回下一个位置的迭代器的时候,如果下一个位置不为空,那么迭代器中的指针直接向后走一步就可以了,如果下一个位置为空,就需要遍历到下一个不为空的位置的桶,返回桶的第一个位置即可。
此时我的cur都在遍历这个单链表了,我们根本找不到哈希表中下一个不为空的位置,那我们应该怎样找到下一个不为空的桶呢?我们必须传递一个哈希表过去!!
来看代码实现:
细节:这里我直接加上普通迭代器和const迭代器一起实现了,不然等下解释的太绕了
实现const迭代器的话,就会多2个模版参数,主要是返回值operator*和operator->。
而且,在迭代器内部有this指针,我们只用操作_node的指向即可

template<class K,class T,class Ref,class Ptr,class KeyOfT,class Hash>
struct HTIterator
{
	typedef HashNode<T> Node;
	typedef HashTable<K,T,KeyOfT,Hash> HT;
	typedef HTIterator<K,T,Ref,Ptr,keyofT,Hash>  Self;
	Node*_node;//成员变量
	HT*_pht;//成员变量

	Self& operator++()
	{
		if(_node->_next)//指向的下一个不为空
		{
			_node=_node->_next;
		}else{//下一个结点为空,需要向后找到不为空的桶
			KeyOfT kot;
			Hash hs;
			size_t hashi=hs(kot(_node->_data))%_pht->_tables.size();
			hashi++;//需要跳过当前桶
			while(hashi<_pht->_tables.size())
			{
				if(_pht->_tables[hashi])
				{
					_node=_pht->_tables[hashi];
					break;
				}
				++hashi;
			}
			if(hashi == _pht->_tables.size())//哈希表遍历遍历完了都还没有找到
			{
				_node=nullptr;
			}
		}
		return *this;
	}
}

接下来我再来实现迭代器中不重要的操作:
直接看代码即可:

Ref operator*()
{
	return _node->_data;
}
Ptr operator->()
{
	return &_node->_data;//箭头会返回对应结点的指针,编译器为了优化,自己会再添加一个箭头,这个箭头就是*.操作的结合
}
bool operator==(const Self& s)
{
	return _node == s._node;
}
bool operator!=(const Self& s)
{
	return _node != s._node;
}

const

const迭代器中迭代器部分我们已经完成了,接下来我们来看HashTable的部分。
这里需要解释Begin(),begin肯定是返回桶中链表的第一个结点即可

因为比较简单,直接来看代码即可:

Iterator Begin()//返回第一个桶的第一个结点
{
	if (_n == 0)
		return End();//此时没有结点,Begin()就是End()
	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);
}

//错误积累:如果报错不能将initializer list转换成啥,大概率编译器是将{}识别成这个作用了,但是我的目的是多参数来进行隐式类型转换
Const_Iterator Begin() const//返回第一个桶的第一个结点
{
	if (_n == 0)
		return End();
	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);
}

Key不能被修改

这个也比较简单,只要将第二个模版参数对应的K修改为const K即可,随后对应的typedef部分也需要修改一下。

unordered_map支持[ ]

因为前面已经实现过,直接来看代码:

V& operator[](const K& key)
{
	pair<iterator, bool> ret = Insert({key,V()});//直接复用上面的Insert,本质上还是调用的底层
	return ret.first->second;
}

结语

感谢大家阅读我的博客,不足之处欢迎指正,感谢大家的支持
逆水行舟,楫摧而志愈坚;破茧成蝶,翼湿而心更炽!!加油!
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值