封装unordered_map和unordered_set

   先前用红黑树封装出了map和set,现在就要用哈希来封装unordered_map和unordered_set(为了简化名称,后面称u_map和u_set),u_map和u_set在学习map时曾了解过,只知道是无序,我还在想,不能排序有啥用呢?原来用于查找数据效率高得很。有了封装map和set的经历,下面的封装就好理解多了,难点基本上都是迭代器和模板参数。

一 模板参数

从u_map和u_set传的模板参数来理解

下图为哈希表接收的模板参数

   本文的哈希是开散列的,因为开散列可以更好地解决哈希冲突,所以哈希表中存的就是每个桶中第一个节点的指针,而T就是节点用于存各种数据类型的,也就是说u_map和u_set给哈希表模板传的第二个模板参数就是想要哈希表节点存的类型。接来下看看两个容器在节点存的类型。

 节点类内部实现

template<class T>//T为节点所存数据
	struct HashNode
	{
		HashNode(const T& data)
			:_data(data)
			, _next(nullptr)
		{
			;
		}
		T _data;
		HashNode<T>* _next;
	};

u_map实例化的哈希表变量

  如下图,u_map存在节点的是一个pair至于为什么要对K加上const,我想如果了解过map和set的封装的同学肯定能猜到(后面会再提及)。

u_set实例化的哈希表变量

  而u_set要节点存的就是一个K类型的毕竟u_set和set是类似的,只存一个key值为了让节点可以用一个变量,例如T _data,既可以存u_set的一个key值,又可以存u_map的key和value这两个值,只能让key和value绑在一个结构体上,总不能让节点内多加一个变量吧,u_set又用不了那么多,不就浪费了吗。

综上,传给哈希表的第二个模板参数是节点存的数据的类型,可能是一个pair,也可能是一个K类型的key值。但是哈希表还有其余的模板参数,这就要结合哈希表的实现才能说清楚,如下。

HashBucketTable其余模板参数介绍

  第一个就简单了,是专门提供给find这类参数是const K&key的,而T已经介绍过了。

  而剩下的两个模板参数都在下面一行代码里了

   由于我们前面将u_map的key和value统一为一个值,不仅方便了节点类内变量存储,insert也只要插入一个值就好了,这就说明插入的元素kv可能是个K类型的,也可能是一个pair。

  如果是一个pair,  我们要取出里面的key,然后%_table.size()求下标,但是如果u_set调用insert,就可以直接取模,因为这时候的kv就是key, 这就导致这里对不同类型要做不同的处理,借鉴封装map的经验,我们可以用一个仿函数GetKey将key返回,但这也不意味着可以直接取模,如果key是字符串呢,这就要用到第四个模板参数,它就是将一些自定义类型转为整数,然后用于取模,这个仿函数介绍哈希成员函数时会出代码,所以这两个仿函数是互相配合。

map的GetKey

set的GetKey

二 使key不可被修改

   当我们大致了解了哈希表的模板参数,还有个大问题前面提了却未解决,u_map和u_set的key都应该是不能修改的,那我们如何实现呢?

1 使u_set的key不可修改

   一般来说普通迭代器可以修改节点内的数据,也就是说u_set的普通迭代器可以修改节点内的数据,那不就意味着可以修改key?这样显然是不行的,所以我们要让外部取得iterator定义出的变量具有const_iterator的特性,还是老套路,用typedef重命名来欺骗使用者。

临场发问:typename的作用是什么?

  上图中我们自认为是从HashBucketTable这个类内取一个类型,可编译器编译的时候哪知道,假设有一个静态变量a,

  访问静态变量a的方式:HashBucketTable<K, pair<const K, V>, GainMapKey>::a,这个形式和前面取iterator不是一模一样吗,编译器哪分得清,而且你类又没实例化,编译器又没办法核实,所以会报错,只能我们写个关键字typename,告诉编译器这是个类型,等实例化了再检查,现在先别报错。

2 使u_map的key不可修改

  先前没说为什么要给K加上const,现在可以说了,就是使得K不可修改。

   仅仅只是我博客这些功能,u_set也可以通过传const K的方法去实现iterator有const_iterator的作用,但是库里大佬肯定比我想的细致,应该是我没考虑到这种方式对其余功能的阻碍。

三 迭代器实现

  这个迭代器的实现则与先前的有些不同,因为当我们++后,如果到空节点了,要去下一个不为空的桶,此时迭代器存的节点指针是没办法找到下一个桶的,这意味着我们需要访问哈希表,所以应该增加一个成员,哈希表指针。

   还有个问题,我们需要从深入迭代器实现才可以解决,就是_ht调用返回的pair<iterator,bool>中的iterator和外面u_set的iterator是不同的,因为外部的iterator是const_iterator重定义的,而内部iterator就是普通迭代器,类型已经不匹配了,这是我们自己定义的类型,编译器无法强转,除非我们写了对应的构造函数,编译器才有可能偷偷帮我们转换一下,接来下就带着问题看看迭代器如何支持的这个类型转换。

迭代器代码

template<class K, class T,class KeyofT, class Hashfunc>
    class HashBucketTable;

要前置声明,不然迭代器内会不认识哈希表指针

 template<class K, class T, class Ptr, class Ref,class KeyofT, class Hashfunc>
	struct HashIterator    K,KeyofT, Hashfunc是给哈希表的模板参数
	{
		typedef HashNode<T> Node;
		typedef HashIterator<K, T, Ptr, Ref, KeyofT, Hashfunc> Self; 
     重命名为Self, 方便下面使用,模板参数变量也只需要改这里

		typedef HashIterator<K, T, T*,T&,KeyofT, Hashfunc> iterator;

HashIterator(Node* node, const HashBucketTable<K, T, KeyofT, Hashfunc>* pht)
			:_node(node)
			, _pht(pht)
		{
			;
		}
		HashIterator(const iterator& it)  
			:_node(it._node)
			, _pht(it._pht)
		{
			;
		}
		Self& operator++()
		{
			KeyofT kot;
			Hashfunc hf;
			Node* cur = _node;
			if (cur->_next)  下一个节点不为空
			{
				_node = _node->_next;
			}
			else  去下一个桶找
			{
				size_t hashi = (hf(kot(cur->_data)) % _pht->_table.size()) + 1;
				while (hashi<_pht->_table.size())  防止往后找不为空的桶时越界
				{
					if(!_pht->_table[hashi])
					  hashi++;
					else
					{
						_node = _pht->_table[hashi];
						return *this;
					}
				}
				_node = nullptr;  先前还没返回,说明全是空桶,达到end位置
			}
			return *this;
		}
		Ref operator*()  通过控制Ptr和Ref来达到const迭代器的作用
		{
			return _node->_data;
		}
		Ptr operator->()
		{
			return &_node->_data;
		}
		bool operator!=(const Self& it)
		{
			return _node != it._node;
		}
		bool operator==(const Self& it)
		{
			return _node == it._node;
		}
		Node* _node;
		const HashBucketTable<K, T, KeyofT, Hashfunc>* _pht; 哈希表指针
	};

 

   当HashIterator被实例化为const迭代器, 这个就可以给u_set的insert做类型转换使用,所以我们前面需要定义一个普通迭代器,就是为了这一步 

   有没有注意到这个成员加了const,首先加上const绝对是可以的,因为限制我们通过_pht这个指针修改哈希表也是合理的。

或许你会说,不对,是因为构造函数是用const的指针接收的。

  那就看看下面的,哈希表指针是用this指针初始化的,而this指针的类型是 T*const,这个const不限制赋值,限制了感觉反而不好,举个例子,int* const p1=&a; int*p2=p1;你p1不能修改自己的值,总不能让p2也不能修改自己的值吧,好像不太合理但如果是const迭代器,那this指针就是const T*const,所以构造函数需要加上const,否则无法匹配,也就是使得哈希表指针这个成员变量要加const。

 

最后我说一下我之前的一个问题,我想it是const类型的,它的成员也是const类型的,为什么就能赋值给Node* _node, 现在我想明白了,it._node这个的类型是Node*const的,由前面的,这个const不限制,所以可以赋值。

u_map封装代码

u_map的成员函数都是复用_ht变量的函数,比较有意思的就是[]的实现了。

template<class K,class V>
class Unordered_map
{
public:
	struct GainMapKey//用来返回pair中的key
	{
		const K& operator()(const pair<K, V>& data)
		{
			return data.first;
		}
	};
	typedef HashNode<pair<const K, V>> Node;
	typedef typename  HashBucketTable<K, pair<const K, V>, GainMapKey>::iterator iterator;
	typedef typename  HashBucketTable<K, pair<const K, V>, GainMapKey>::const_iterator const_iterator;
	
	pair<iterator,bool> insert(const pair<K,V>& p1)
	{
		return _ht.insert(p1);
	}
	bool Erase(const K& k1)
	{
		return _ht.Erase(k1);
	}
	iterator find(const K& key) 这里find和_ht.find()返回类型和下面的insert一样
                                类型是不匹配的,但是构造函数支持了这种类型转换。
	{
		return _ht.find(key);
	}
	iterator begin()
	{
		return _ht.begin();
	}
	iterator end()
	{
		return _ht.end();
	}
	const_iterator begin()const
	{
		return _ht.begin();
	}
	const_iterator end()const
	{
		return _ht.end();
	}
	V& operator[](const K& p1)
	{
		pair<iterator,bool> ret=_ht.insert(make_pair(p1,V()));
		return (*ret.first).second;
       
     ret.first取得是iterator,*解引用就返回_node->_data的引用,
     这个_data对于u_map而言就是一个pair,我们再取second就是[]需要返回的value  
	}
private:
	HashBucketTable<K, pair<const K,V>, GainMapKey> _ht; 偏特化
};

u_set封装代码

template<class K>
class Unordered_set
{
public:
	struct GainSetKey  为了map和set传模板参数保持一致,set也要传一个GainSetKey
	{
		const K& operator()(const K& data)
		{
			return data;
		}
	};
	typedef HashNode<K> Node;
	typedef	typename HashBucketTable<K,K, GainSetKey>::const_iterator iterator;
	typedef	typename HashBucketTable<K, K, GainSetKey>::const_iterator const_iterator;

	
	pair<iterator,bool> insert(const K& k1)
	{
		return _ht.insert(k1);
	}
	bool Erase(const K& k1)
	{
		return _ht.Erase(k1);
	}
	iterator find(const K& key)
	{
		return _ht.find(key);
	}
    
   只提供const版本的begin和end函数,就是为了_ht调用的begin和end函数返回的是const_iterator
	const_iterator begin()const
	{
		return _ht.begin();  
	}
	const_iterator end()const 
	{
		return _ht.end();
	}
private:
	HashBucketTable<K, K, GainSetKey> _ht; 偏特化
};

四  哈希类成员函数介绍

template<class K>
struct DefaultHashfunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
template<>
struct DefaultHashfunc<string>    将string转为整数的仿函数
{   
	size_t operator()(const string& s)
	{
		int hash = 1;
		for (auto e : s)
			hash = hash * 131 + e;  BKDR
		return (size_t)hash;
	}
};
	
	
template<class K, class T, class KeyofT, class Hashfunc = DefaultHashfunc<K>>
  class HashBucketTable
	{
	public:
		template<class K, class T, class Ptr, class Ref,class KeyofT, class Hashfunc>//友元类的声明
		friend	struct HashIterator;  
     HashIterator要访问HashBucketTable的私有成员,所以是
		   在HashBucketTable对HashIterator友元声明

         typedef HashNode<T> Node;
		typedef HashIterator<K, T, T*, T&, KeyofT, Hashfunc> iterator;
		typedef HashIterator<K, T,const T*,const T&, KeyofT, Hashfunc> const_iterator;
         通过传const T*,const T&来达到const迭代器的效果		

        HashBucketTable()
		{
			_table.resize(5);
		}

		  析构函数函数
		~HashBucketTable()
		{
			for (size_t i = 0; i < _table.size(); i++) 
			{
				Node* cur = _table[i]; 
				while (cur)       找到一个非空桶,遍历链表,delete释放节点
				{
					Node* next = cur->_next;
					_table[i] = next;
					delete cur;
					cur = next;
				}
			}
		}
		iterator begin()
		{
			for (size_t i = 0; i < _table.size(); i++)//遍历哈希表ht
			{
				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++)//遍历哈希表ht
			{
				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);
		}

         拷贝构造函数
		HashBucketTable(const HashBucketTable<K, T, KeyofT, Hashfunc>& ht)
		{
			KeyofT kot;
			Hashfunc hf;
			_table.resize(ht._table.size());
			for (size_t i = 0; i < ht._table.size(); i++)//遍历哈希表ht
			{
				Node* cur = ht._table[i];
				while (cur)//遍历该桶
				{
					Node* newnode = new Node(cur->_data);
					size_t hashi = hf(kot(cur->_data)) % _table.size();
					newnode->_next = _table[hashi];
					_table[hashi] = newnode;
					cur = cur->_next;//去桶的下一个节点
				}
			}
		}
	
		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 iterator(nullptr,this);
		}
		bool Erase(const K& key)
		{
			KeyofT kot;
			Hashfunc hf;
            iterator ret=find(key);
            if(ret._node==nullptr)  没找到节点,返回false
             return false;
			size_t hashi = hf(key) % _table.size();
			Node* pre = nullptr;
			Node* cur = _table[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					if (pre == nullptr)//头删
					{
						_table[hashi] = cur->_next;//链接
					}
					else
					{
						pre->_next = cur->_next;
					}
					delete cur;
					--_size;//负载因子要--
					return true;
				}
				pre = cur;
				cur = cur->_next;
			}
			return false;  没找到,erase失败
		}
		pair<iterator,bool> insert(const T& kv)
		{
			KeyofT kot;
			Hashfunc hf;
			查找该节点是否已经存在
			iterator it = find(kot(kv));
			if (it._node)//该节点存在,返回该节点迭代器,并且插入失败
				return make_pair(it, false);
			//扩容
			if (_size >= _table.size())
			{
				size_t newsize = _table.size() * 2;
                
				vector<Node*> newtable;
				newtable.resize(newsize);
				for (size_t i = 0; i < _table.size(); i++)
				{
					Node* cur = _table[i]; 搬运旧表上的节点,省得自己new和delete
					while (cur)
					{
						Node* next = cur->_next;
						_table[i] = next;
						size_t hashi = hf(kot(cur->_data)) % newtable.size();//计算新表映射下标
						搬到新表
						cur->_next = newtable[hashi];
						newtable[hashi] = cur;
						cur = next;//搬下个节点
					}
				}
                新旧表交换
				_table.swap(newtable);
			}
         
            正常插入节点
			size_t hasi = hf(kot(kv)) % _table.size();
			Node* newnode = new Node(kv);
			newnode->_next = _table[hasi];
			_table[hasi] = newnode;
			_size++;
			return make_pair(iterator(newnode,this),true);
		}
	private:
		vector<Node*> _table;
		size_t _size = 0;
	};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小何只露尖尖角

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

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

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

打赏作者

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

抵扣说明:

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

余额充值