C++进阶-->封装unordered_map和unordered_set

1、unordered_map和unordered_set的源码分析

源码如下图可见:

由上图可以看出,其实unordered_map和unordered_set的key和key_Value结构也是通过泛型思想进行实现的,通过第一个模版参数value通过传入不同的值,实现key和key_value结构的。

我们观察unordered_set.h实例化hashtable那里的第一个参数传入的是Value,而unordered_map.h实例化hashtable第一个模板参数传入的是pair<const Key, T>,所以我们就知道,unordered_map和unordered_set的实现和前面map和set的实现很相似的,主要的实现都是在hash.h里面实现,unordered_map和unordered_set本质上就是一个壳。

注意一下,就是底层源码的命名风格有点乱,我们这里只是提了个大概,就是他们传入的参数不同从而实现不同的结构这一点。


2、unordered_set和unordered_map的实现

2.1 两个容器大框架的实现

unordered_set和unordered_map的大框架和前面的map和set的实现很相似,两个头文件里面都需要包含不同的一个仿函数对key和key_value进行取值,对于key结构来说就直接返回他的值,而对于key_value来说,存储的是一个pair结构,我们要取到他的key就需要取pair<K, V>.first才可以。

unordered_set.h

#pragma once
#include"hash.h"
namespace lwt
{
	template<class K, class Hash = HashFunc<K>>
	class unordered_set
	{
		//实现一个仿函数进行返回对应的值,key结构就返回key,key_value结构就返回kv.first
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

	private:
		Hash_Bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht	;
    };
}

unordered_map.h

#pragma once
#include"hash.h"


namespace lwt
{
	template<class K, class V, class Hash = HashFunc<K>>
	class unordered_map
	{
		//首先map传入的肯定是一个key_value值
		struct KeyOfMap
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	private:
		Hash_Bucket::HashTable<K, pair<const K, V>, KeyOfMap, Hash> _ht;
	};
}

2.2 哈希表结点的实现

我们这里使用前面一篇文章里讲的链地址法,也就是哈希桶进行实现,那么我们知道哈希桶就是一个顺序表的每个位置下挂着一个链表,如下图可见,那么我们就需要一个指向下一个结点的指针,和存储数据的变量,但是这个变量的类型我们不确定,因为我们实现的是泛型结构的,不知道是key结构还是key_value结构,那么我们就可以用一个模板参数T进行代替。

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


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

2.3 哈希表的迭代器的实现

哈希表的迭代器和前面map和set的迭代器的实现也是类似,只不过他们在一些细节方面,例如++的实现这些有些差异,因为他们的底层结构不同,其他的大差不差,例如实现泛型方面,都是需要加多几个模板参数class Ptr, class Ref, class KeyOfT, class Hash等等,Ptr参数是指定指针的类型,Ref参数是指定引用的类型。

为什么要加这两个,前面我们也提过了,这里再提一遍,就是stl容器里一般都有两种迭代器,一种是iterator的,如果我们分开实现的话代码就过于冗余,所以我们加多几个模板参数进行修改。

然后是KeyOfT和Hash参数,第一个是把key或者key_value结构的值取出来,而Hash则是将关键字的值进行转换成无符号整形进行比较。

// 前置声明
    template<class K, class T, class KeyOfT, class Hash>
    class HashTable;

    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;

		const HT* _ht;
		Node* _node;

		HTIterator(Node* node, const HT* ht)
			:_node(node)
			,_ht(ht)
		{}

		//对迭代器解引用返回数据,就像对一个指针解引用得到指针指向的值
		Ref operator* ()
		{
			return _node->_data;
		}

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

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

		Self& operator++()
		{
			//这个就是当前桶还有数据
			if (_node->_next)
			{
				_node = _node->_next;
			}
			//桶没数据了,就需要找到当前数据的位置,然后从当前位置去找不为空的桶
			else
			{
				//kot就是去取出T的key,如果是pair就取first,如果不是就取Key
				KeyOfT kot;
				//hash就是为了转成size_t进行比较,如果出现字符串啥的就没法直接比较
				Hash hash;
				size_t hashi = hash(kot(_node->_data)) % _ht->_table.size();
				++hashi;
				while (hashi < _ht->_table.size())
				{
					_node = _ht->_table[hashi];
					if (_node)
					{
						break;
					}
					else
					{
						hashi++;
					}
				}

				//所有桶都走完了
				if (hashi == _ht->_table.size())
				{
					_node = nullptr;
				}
			}
			return *this;
		}
	};

至于为什么要前置声明HashTable,那是因为在下面实现迭代器++的时候需要哈希表的数据,而对对象成员变量的查找是往上查找的,我们如果不在上面前置声明一下的话就无法调用到HashTable的数据。


2.4 哈希表的实现

哈希表的基础结构

由于前面哈希表的迭代器需要使用哈希表内的数据,那就需要添加友元,因为哈希表的_table和_n都是私有成员,这里我们给出哈希表的基本结构出来:

	template<class K, class T,class KeyOfT, class Hash>
	class HashTable
	{
		// 友元声明
		template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
		friend struct HTIterator;

		typedef HashNode<T> Node;
	public:
		typedef HTIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
		typedef HTIterator<K, T, const T&, const T*, KeyOfT, Hash> ConstIterator;

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

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


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

其实大致都和前面哈希表的实现一样,只不过这里是封装了key和key_Value结构,然后添加了迭代器。


Begin和End的实现

begin的实现:首先我们要判断当前的_n是否为0,_n是用来记录里面数据的有多少的,为的是方便后面计算负载因子,如果_n为0的话我们就直接返回end()即可,因为end()的返回肯定是一个nullptr,如果_n不为0,那就需要返回一个存在数据的位置的结点,然后用该结点的地址传给Iterator构造一个匿名对象进行返回即可。

		Iterator Begin()
		{
			if (_n == 0)
			{
				return End();
			}
			for (size_t i = 0; i < _table.size();i++)
			{
				Node* cur = _table[i];
				if (cur)
				{
					return Iterator(cur, this);
				}
			}
			return End();
		}

		Iterator End()
		{
			return Iterator(nullptr, this);
		}

		ConstIterator Begin()const
		{
			if (_n == 0)
			{
				return End();
			}
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node* cur = _table[i];
				if (cur)
				{
					return ConstIterator(cur, this);
				}
			}
			return End();
		}

		ConstIterator End() const
		{
			return ConstIterator(nullptr, this);
		}

Insert和Find的改造

insert和find的实现和前面类似,只不过需要修改返回值,通过查看文档我们发现,Insert返回的是一个pair<iterator, bool>,如果插入成功则会返回当前插入数据的迭代器然后返回pair<cur, true>,如果插入失败说明里面已经有插入的数据了则会返回pair<cur, false>。而在这里面,扩容操作和前面有所不同,前面的是实例化多一个哈希表的类,然后还要开空间,还需要申请新结点,但我们这里可以直接创建一个扩容后的vector,然后把所有的结点放到对应的vecotr的位置处,这样不需要释放那些结点的数据,也不需要重新申请新结点就实现了扩容。如下图可见:

但是也没有那么简单,因为扩容之后容量变了,而我们的哈希函数找数据的位置的公式是hashi = key% M,M为容器的大小,那么容器大小变了,就需要重新计算他们的位置,所以在图里我们看的很简单,但是在代码里还需要计算找出他们的位置在哪里,然后把数据放到newVector之后就让他们两个容器进行交换,那么newVector的容器就走到上面去了,而旧的就走到newVctor处了。

这样就实现了扩容操作。

而Find的实现也是和前面几乎类似,只不过前面哈希表的实现返回的是结点的地址,这里返回迭代器即可。如下代码所示:

		pair<Iterator, bool> Insert(const T& data)
		{
			KeyOfT kot;
			Hash hash;
			Iterator it = Find(kot(data));

			//这里如果it!=end()也就意味着插入的数据已经存在了,所以返回false
			if (it != End())
				return { it, false };
			//负载因子为1的时候扩容
			//负载因子就是M/N,M就是当前的空间,N就是容器的空间
			if (_n == _table.size())
			{
				//HashTable<K, V> newht;
				newht._tables.resize(__stl_next_prime(_tables.size() + 1));
				这里直接*2方便观察
				//newht._table.resize(_table.size() * 2);
				//for (size_t i = 0; i < _table.size(); i++)
				//{
				//	Node* cur = _table[i];
				//	while (cur)
				//	{
				//		newht.Insert(cur->_kv);
				//		cur = cur->_next;
				//	}
				//}
				//_table.swap(newht._table);

				//如果按上面来进行扩容的话需要写一个析构进行释放,然后还要申请新结点
				//这里的扩容和前面的不太一样,我们这里可以直接定义一个扩容后容量的vector容器
				//然后把那些元素全部连在扩容后的容器内,这样就不需要重新申请空间了。

				vector<Node*> newTable(__stl_next_prime(_table.size())+1);
				for (size_t i = 0; i < _table.size(); i++)
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;
						//头插到新表里面,但是我们也还要找到他的值才可以
						size_t hashi = hash( kot(cur->_data)) % newTable.size();
						cur->_next = newTable[hashi];
						newTable[hashi] = cur;

						cur = next;
					}
					_table[i] = nullptr;
				}
				_table.swap(newTable);
			}
			size_t hashi = hash(kot(data)) % _table.size();
			Node* newnode = new Node(data);
			newnode->_next = _table[hashi]; 
			_table[hashi] = newnode;
			++_n;
			return {Iterator(newnode, this), false};
		}

		Iterator Find(const K& key)
		{
			KeyOfT kot;
			Hash hash;
			size_t hashi = hash(key) % _table.size();
			Node* cur = _table[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return Iterator(cur, this);
				}
				cur = cur->_next;
			}
			return End();
		}

Erase的实现:

也是和哈希表的实现几乎类似,只不过我们要加一个KeyOfT,对他们的值进行提取,因为不知道是Key结构还是Key_value结构。

		bool Erase(const K& key)
		{
			KeyOfT kot;
			Hash hash;
			size_t hashi = hash(key) % _table.size();
			Node* prev = nullptr;
			Node* cur = _table[hashi];

			while (cur)
			{
				if (hash(kot(cur->_data)) == key)
				{
					if (prev = nullptr)
					{
						_table[hashi] = cur->_next;

					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					--_n;
					return true;
				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;
		}

2.5 套壳操作(完成unordered_map和unordered_set)

unordered_set的套壳实现:

#pragma once
#include"hash.h"
namespace lwt
{
	template<class K, class Hash = HashFunc<K>>
	class unordered_set
	{
		//实现一个仿函数进行返回对应的值,key结构就返回key,key_value结构就返回kv.first
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

	public:
		//然后就开始实现迭代器
		typedef typename Hash_Bucket::HashTable < K, const K, SetKeyOfT, Hash>::Iterator iterator;
		typedef typename Hash_Bucket::HashTable < K, const K, SetKeyOfT, Hash>::ConstIterator const_iterator;

		iterator beign()
		{
			return _ht.Begin();
		}

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

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

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

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

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

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




	private:
		Hash_Bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht	;
	};

	void print(const unordered_set<int>& s)
	{
		unordered_set<int>::const_iterator it = s.begin();
		while (it != s.end())
		{
			//*it = 1;
			cout << *it << " ";
			++it;
		}
		cout << endl;

		for (auto e : s)
		{
			cout << e << " ";
		}
		cout << endl;
	}
}

unordered_map的套壳实现:

#pragma once
#include"hash.h"


namespace lwt
{
	template<class K, class V, class Hash = HashFunc<K>>
	class unordered_map
	{
		//首先map传入的肯定是一个key_value值
		struct KeyOfMap
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};

	public:
		typedef typename  Hash_Bucket::HashTable<K, pair<const K, V>, KeyOfMap, Hash>::Iterator iterator;
		typedef typename  Hash_Bucket::HashTable<K, pair<const K, V>, KeyOfMap, Hash>::ConstIterator const_iterator;

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

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

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

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

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

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

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

		bool erase(const K& key)
		{
			return _ht.Erase(key);
		}
	private:
		Hash_Bucket::HashTable<K, pair<const K, V>, KeyOfMap, Hash> _ht;
	};


	void test_map1()
	{
		unordered_map<string, string> dict;
		dict.insert({ "sort", "排序" });
		dict.insert({ "字符串", "string" });

		dict.insert({ "sort", "排序" });
		dict.insert({ "left", "左边" });
		dict.insert({ "right", "右边" });

		dict["left"] = "左边,剩余";
		dict["insert"] = "插入";
		dict["string"];

		for (auto& kv : dict)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
		cout << endl;

		unordered_map<string, string>::iterator it = dict.begin();
		while (it != dict.end())
		{
			// 不能修改first,可以修改second
			//it->first += 'x';
			it->second += 'x';
			cout << it->first << ":" << it->second << endl;
			++it;
		}
		cout << endl;
	}
}

END!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值