【C++深度探索】unordered_set、unordered_map封装

🔥 个人主页:大耳朵土土垚
🔥 所属专栏:C++从入门至进阶

这里将会不定期更新有关C/C++的内容,欢迎大家点赞,收藏,评论🥳🥳🎉🎉🎉

前言

  前面我们学习过红黑树实现map、set的封装,而unordered_setunordered_map的功能与map和set类似,所不同的是其存储元素是无序的,底层是使用哈希表,所以今天我们就可以利用之前学习过的哈希表的实现,来对C++STL库中的unordered_setunordered_map进行模拟实现。


1. unordered_set和unordered_map介绍


  在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,例如:map、set。在查询时效率可达到 l o g 2 N log_2 N log2N,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同。

✨unordered_map介绍

  介绍文档,点击跳转

  1. unordered_map是存储<key, value>键值对的关联式容器,其允许通过key快速的索引到与其对应的value。
  2. 在unordered_map中,键值通常用于惟一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
  3. 在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序, 为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。
  4. unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
  5. unordered_maps实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。

unordered_map和map核心功能重复90%,它们区别在于:

  1. 对键值对中key要求不同:
  • map:key要支持比较大小
  • unordered_map:key要支持转换成整型+比较相等
  1. map遍历有序,unordered_map遍历无序
  2. map有双向迭代器,unordered_map单向迭代器
  3. 它们之间性能有差异

unordered_map常见接口:

函数声明功能介绍
unordered_map()构造不同格式的unordered_map对象
bool empty() const检测unordered_map是否为空
size_t size() const获取unordered_map的有效元素个数
begin返回unordered_map第一个元素的迭代器
end返回unordered_map最后一个元素下一个位置的迭代器
cbegin返回unordered_map第一个元素的const迭代器
cend返回unordered_map最后一个元素下一个位置的const迭代器
operator[]返回与key对应的value
iterator find(const K& key)返回key在哈希桶中的位置
size_t count(const K& key)返回哈希桶中关键码为key的键值对的个数
insert向容器中插入键值对
erase删除容器中的键值对
void clear()清空容器中有效元素个数
void swap(unordered_map&)交换两个容器中的元素
size_t bucket_count()const返回哈希桶中桶的总个数
size_t bucket_size(size_t n)const返回n号桶中有效元素的总个数
size_t bucket(const K& key)返回元素key所在的桶号

注意:operator[]函数中实际调用哈希桶的插入操作,用参数key与V()构造一个默认值往底层哈希桶中插入,如果key不在哈希桶中,插入成功,返回V(),插入失败,说明key已经在哈希桶中,将key对应的value返回。这与之前的map类似,插入函数返回一个键值对,键存放指针,对存放bool值,用来判断是否插入成功。


✨unordered_set介绍

  文档介绍,点击跳转
  unordered_set与unordered_map类似,不同在于前者储存单个数据,后者储存键值对,这里就不过多介绍。


2. 修改哈希表


  因为我们要使用哈希表来实现对unordered_setunordered_map的封装,之前实现的哈希表都是插入键值对,是没办法很好封装unordered_set的,所以我们先得对哈希表进行改造,改造类似于使用红黑树封装map和set对红黑树的改造,具体实现如下:


我们之前模拟实现过哈希表,插入的节点是键值对pair类型,而如果要使用哈希表来对unordered_setunordered_map封装的话,unordered_set存储的应该是单个值,而不是键值对,所以我们就需要对哈希表进行修改,使得unordered_setunordered_map都能适用:

  1. 首先哈希表存储节点的类需要从只能存储键值对改为能够存储任意数据
  • 修改前:
template<class K,class V>
struct HashNode
{
	pair<K, V> _kv;
	HashNode<K, V>* _next;

	HashNode(const pair<K, V>& kv)
		:_kv(kv)
		, _next(nullptr)
	{}
};
  • 修改后:
template<class T>
struct HashNode
{
	T _data;
	HashNode<T>* _next;

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

  1. 相应的,哈希表的模板参数也需要修改:
  • 修改前:
//哈希表类
template<class K,class V, class Hash = HashFunc<K>>
class HashTable {
public:
	typedef HashNode<K,V> Node;
	Node* Find(const K& key);//查找函数
	
private:
	vector<Node*> _tables;
	size_t _n;//记录存储数据个数
};

 因为节点类只有一个模板参数了,所以哈希表前面两个模板参数有点多余,但是如果将哈希表的模板参数也改为一个,如下面代码所示:

//哈希表
template<class T,class Hash = HashFunc<T>>
class HashTable 
{
public:
	typedef HashNode<T> Node;
	Node* Find(const T& key);//?查找函数
	
private:
	vector<Node*> _tables;
	size_t _n;//记录存储数据个数
};

 那么对于class Hash这个模板参数不可能也传入一个键值对,此外在实现查找上面代码中的Find函数时,对于unordered_map查找时Find函数参数就得传一个完整的键值对,我们不可能把完整的键值对全部传过去查找,这样查找就没有意义,我们只需要获得键值对的键查找到相应的键值对即可,所以我们还应该传一个模板参数用于传给class HashFindErase函数:

//哈希表
template<class K,class T,class Hash = HashFunc<K>>//K存储键,T存储键值对
class HashTable 
{
public:
	typedef HashNode<T> Node;//传入键值对
	Node* Find(const K& key);//查找函数,只传键
	pair<Node*,bool> Insert(const T& data);//插入,传入T
	bool Erase(const K& key);//删除,只传键
private:
	vector<Node*> _tables;
	size_t _n;//记录存储数据个数
};

这样对于不同函数的需求就可以传入不同的模板参数了🥳🥳

 如果是unordered_map存储的是键值对,我们就可以往哈希表的两个模板参数中传入一个键和一个键值对:

//unordered_map类
template<class K,class V>
class unordered_map {
public:
private:
	HashTable<K, pair<K,V>> _ht;
};

这样哈希表的第一个模板参数帮助我们解决仿函数传参、查找和删除函数传参的问题,第二个则是必须的,除了存储数据外,插入等函数也需要第二个模板参数

 如果是unordered_set存储的不是键值对,我们也可以复用上面的代码,传入两个一样的参数即可:

//unordered_set类
template<class K>
class unordered_set {
public:
private:
	HashTable<K, K> _ht;
};

虽然unordered_set看起来传了两个一模一样的参数是无意义的,但是这样就可以实现对哈希表的复用,不用单独为了unordered_set再写一个哈希表了,如下图所示:


  1. 插入函数的参数也得从键值对改为任意数据:
bool Insert(const T& data)//之前:bool Insert(const pair<K, V>& data)

 除此以外,我们在寻找插入的位置时不可避免需要通过节点中存储的_data使用哈希函数来获取插入的位置:

//通过Hash函数找到插入位置
Hash hs;
size_t addr = hs(data.first) % _tables.size();

 我们发现之前插入键值对都是使用键值对的键来传给哈希函数获取哈希值,当我们将哈希表改成可以存储任意数据后,就不支持上述获取哈希值的方式了。
 因此为了实现代码的复用,我们需要传入一个新的模板参数,以便在不同情况下都能按照我们需要的方式进行获取哈希值,因为如果是unordered_map需要通过键来获取,unordered_set则直接通过数据进行获取,所以我们可以在set类和map类中各定义一个类来获取传给哈希函数的数据,再传给哈希表:

// unordered_map类
template<class K,class V>
class unordered_map {
//定义一个类获取键值对里面的键
struct MapKeyOfT {
	const K& operator()(const pair<K, V>& data)
	{
		return data.first;
	}
};

private:
		HashTable<K, pair<K,V>, MapKeyOfT> _ht;//传给哈希表

};
//unordered_set类
template<class K>
class unordered_set{
//也定义一个类返回data即可,虽然有点多此一举,但是可以实现代码复用
struct SetKeyOfT {
	const K& operator()(const K& data)
	{
		return data;
	}
};

private:
	HashTable<K, K, SetKeyOfT> _ht;//传给哈希表
};

看起来unordered_set定义的类似乎做了无用功,但是这一切都是为了能够实现哈希表的复用,unordered_map多传了一个参数,unordered_set也要多传。


 那么哈希表的模板参数也要修改一下:

template<class K, class T, class KeyOfT, class Hash = HashFunc<K>>
class HashTable {
public:
	typedef HashNode<T> Node;
	
private:
	vector<Node*> _tables;
	size_t _n;//记录存储数据个数
};

 这样我们在插入函数进行比较时,就可以通过类模板KeyOfT定义一个对象然后使用括号来获取需要进行比较的数了:

bool Insert(const T& data)
{
	KeyOfT kot;//使用类模板,定义一个对象
	Hash hs;
	//1.先找是否已经插入过相同的值
	if (Find(kot(data)))
		return false;
	//2.判断是否需要扩容
	...
	//3.通过Hash函数找到插入位置
	size_t addr = hs(kot(data)) % _tables.size();

	//...

	}

这样就可以使用类模板定义的对象通过重载括号获取你想要的值,如果插入类型是键值对,那么就获得键值对中的键;如果不是键值对,括号返回的也是数据本身;这样不同的数据都可以传给哈希函数🥳🥳


3. 迭代器


  哈希表的迭代器与链表的迭代器类似,都是使用指针来构造:

//迭代器
template<class T>
struct HashTableIterator {
	typedef HashNode<T> Node;
	typedef HashTableIterator<T> self;
	
	T& operator*()
	{
		return _node->_data;
	}

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

	bool operator!=(const self& t)
	{
		return _node != t._node;
	}

	bool operator==(const self& t)
	{
		return _node == t._node;
	}

	self& operator++()
	{
		//...
	}


	//构造
		HashTableIterator(Node* node)
			:_node(node)
		{}

		Node* _node;
	};

✨对于operator++

 因为哈希表中是使用哈希桶,当一个桶遍历完,就需要寻找下一个桶,所以我们得将哈希表传过去来寻找下一个桶:

self& operator++()
{
	if (_node->_next)
		_node = _node->_next;
	//如果当前桶走完了,找下一个桶
	//1.先找到当前位置
	KeyOfT kot;
	Hash hs;
	size_t hash = hs(kot(_node->_data)) % _pht->_tables.size();
	++hash;
	while (hash < _pht->_tables.size())
	{
		if (_pht->_tables[hash])
			break;
		hash++;
	}
	if (hash >= _pht->_tables.size())
		_node = nullptr;
	else
		_node = _pht->_tables[hash];
		
	return *this;
}

可以看出上述代码使用了KeyOfT和Hash类分别获得传给哈希函数的值和哈希函数计算的哈希值,所以迭代器模板也要传入这两个参数:

template<class K, class T, class KeyOfT, class Hash>
truct HashTableIterator {
	typedef HashNode<T> Node;
	typedef HashTableIterator<K,T, KeyOfT, Hash> self;
	typedef HashTable<K, T, KeyOfT, Hash> HashTable;
	//...
	};

又因为获取哈希表需要传入四个参数,所以迭代器的参数也得包括这四个,所以除了KeyOfT和Hash还要传入K这个参数

然后在迭代器中除了需要定义一个指针外,还需要一个哈希表的指针以便将哈希表传过去:

template<class K, class T, class KeyOfT, class Hash>
truct HashTableIterator {
	typedef HashNode<T> Node;
	typedef HashTableIterator<K,T, KeyOfT, Hash> self;
	typedef HashTable<K, T, KeyOfT, Hash> HashTable;

//构造
	HashTableIterator(Node* node, HashTable* pht)
		:_node(node)
		,_pht(pht)
	{}

	Node* _node;
	HashTable* _pht;//传入哈希表指针
};
	

完整的普通迭代器类代码如下:

// 前置声明,因为迭代器类中要使用哈希表所以需要前置声明
template<class K, class T, class KeyOfT, class Hash>
class HashTable;

//迭代器
template<class K, class T, class KeyOfT, class Hash>
struct HashTableIterator {
	typedef HashNode<T> Node;
	typedef HashTableIterator<K,T, KeyOfT, Hash> self;
	typedef HashTable<K, T, KeyOfT, Hash> HashTable;
	T& operator*()
	{
		return _node->_data;
	}

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

	bool operator!=(const self& t)
	{
		return _node != t._node;
	}

	bool operator==(const self& t)
	{
		return _node == t._node;
	}

	self& operator++()
	{
		if (_node->_next)
			_node = _node->_next;
		//如果当前桶走完了,找下一个桶
		//1.先找到当前位置
		KeyOfT kot;
		Hash hs;
		size_t hash = hs(kot(_node->_data)) % _pht->_tables.size();
		++hash;
		while (hash < _pht->_tables.size())
		{
			if (_pht->_tables[hash])
				break;
			hash++;
		}
		if (hash >= _pht->_tables.size())
			_node = nullptr;
		else
			_node = _pht->_tables[hash];
			
		return *this;
	}


	//构造
		HashTableIterator(Node* node, HashTable* pht)
			:_node(node)
			,_pht(pht)
		{}

		Node* _node;
		HashTable* _pht;
	};
	

✨const迭代器


  相较于普通迭代器,const迭代器指向的内容不可以被修改,也就是说operator*operator->返回值不可以修改,所以只要在其返回值前加const修饰即可,为了与普通迭代器复用同一个迭代器类,我们需要在迭代器类的模板参数中多加两个:

// 前置声明
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 HashTableIterator {
	typedef HashNode<T> Node;
	typedef HashTableIterator<K,T,Ref, Ptr, KeyOfT, Hash> self;
	typedef const HashTable<K, T, KeyOfT, Hash> HashTable;//注意这里是const
	Ref operator*()
	{
		return _node->_data;
	}

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

	bool operator!=(const self& t)
	{
		return _node != t._node;
	}

	bool operator==(const self& t)
	{
		return _node == t._node;
	}

	self& operator++()
	{
		if (_node->_next)
			_node = _node->_next;
		//如果当前桶走完了,找下一个桶
		//1.先找到当前位置
		KeyOfT kot;
		Hash hs;
		size_t hash = hs(kot(_node->_data)) % _pht->_tables.size();
		++hash;
		while (hash < _pht->_tables.size())
		{
			if (_pht->_tables[hash])
				break;
			hash++;
		}
		if (hash >= _pht->_tables.size())
			_node = nullptr;
		else
			_node = _pht->_tables[hash];
			
		return *this;
	}


	//构造
		HashTableIterator(Node* node, HashTable* pht)
			:_node(node)
			,_pht(pht)
		{}

		Node* _node;
		HashTable* _pht;
	};

  这样在哈希表中如果是普通迭代器就传T,T&,T*这三个模板参数,如果是const迭代器就传T,const T&,const T*这三个模板参数,这样就很好限制了const迭代器修改指向的内容:

template<class K, class T, class KeyOfT, class Hash = HashFunc<K>>
class HashTable {
public:
	// 友元声明
	template<class K, class T, class Ref, class Ptr,class KeyOfT, class Hash>
	friend struct HashTableIterator;

	typedef HashNode<T> Node;

	typedef HashTableIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
	typedef HashTableIterator<K, T, const T&, const T*, KeyOfT, Hash> Const_Iterator;


	Iterator Begin()
	{
		if (_n == 0)
			return End();
		int i = 0;
		while (i < _tables.size())
		{
			if (_tables[i] != nullptr)
				break;
			i++;
		}
		return Iterator(_tables[i],this);
	}

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

	Const_Iterator Begin() const
	{
		if (_n == 0)
			return End();
		int i = 0;
		while (i < _tables.size())
		{
			if (_tables[i] != nullptr)
				break;
			i++;
		}
		return Const_Iterator(_tables[i], this);
	}

	Const_Iterator End() const
	{
		return Const_Iterator(nullptr, this);
	}
	//...
private:
	vector<Node*> _tables;
	size_t _n;//记录存储数据个数
};

因为在迭代器类中要使用哈希表中的数据,所以我们将迭代器设置为哈希表类的友元类

  有了迭代器之后,Find查找函数的返回值就可以使用迭代器了🥳🥳:

// 检测哈希表中是否存在值为key的节点,存在返回该节点的迭代器,否则返回End()
Iterator Find(const K& key)
{
	//先找到key对应的Hash值
	KeyOfT kot;
	Hash hs;
	size_t ht = hs(key) % _tables.size();
	Node* cur = _tables[ht];
	while (cur)
	{
		if (kot(cur->_data) == key)
			return Iterator(cur,this);
		cur = cur->_next;
	}
	return End();
}

这里同样要注意,查找函数需要通过特定的值获取哈希值,所以我们可以利用之前在插入函数中使用的类模板继续创建一个对象来获取哈希值。


4. unordered_map的[]访问


  在unordered_map的使用介绍中,我们知道可以用[]来访问修改键值对以及插入数据:

//迭代器构造
std::vector<pair<string, string>> v = { {"上", "up"}, { "下", "down"}, { "左", "left"}, { "右", "right"} };
std::unordered_map<string, string> m(v.begin(), v.end());

//[]访问
cout << m["上"] << endl;
cout << m["下"] << endl;
cout << m["左"] << endl;
cout << m["右"] << endl;

//[]修改
m["右"] += "tutu";

//[]插入
m["中"] = "center";

cout << endl;
cout << "修改插入后:" << endl;
std::unordered_map<string, string>::iterator it = m.begin();
while (it != m.end())
{
	cout << it->first << ":" << it->second << endl;

	++it;
}
cout << endl;

结果如下:


  unordered_map的[]能够插入数据是因为其复用了插入函数,如果[]里面引用的值不存在unordered_map中就会插入并返回键值对的值,存在就直接返回键值对的值,而插入函数中恰好会先寻找合适的插入位置,并返回bool值,所以我们只需对插入函数返回的值进行修改,这与之前学习过的map类似:
我们将插入函数的返回值设为pair类型,如果插入成功就返回新节点的迭代器和true;如果插入失败,那么map中肯定以及有相同的值,那么返回该位置的迭代器和false

这样在[]中就可以复用插入函数完成插入和修改操作了:

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

插入函数只需要修改返回值即可:

pair<Iterator,bool> Insert(const T& data)
{
	//...简写
	//1.插入成功
		return make_pair(Iterator(newnode,this),true);
	//2.插入失败
	return make_pair(it,false);//已有位置的迭代器
}


5. unordered_set和unordered_map封装


  我们对于unordered_setunordered_map封装需要各种新开一个头文件unordered_set.hunordered_map.h来进行,并且都需要包含Hash.h头文件,放在自己的命名空间内,避免与STL标准库中的map和set弄混。

✨修改后的哈希表

//哈希函数
template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

// 哈希表中支持字符串的操作
template<>
struct HashFunc<string>
{
	size_t operator()(const string& key)
	{
		size_t hash = 0;
		for (auto e : key)
		{
			hash *= 31;
			hash += e;
		}

		return hash;
	}
};

//哈希节点类
template<class T>
struct HashNode
{
	T _data;
	HashNode<T>* _next;

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

// 前置声明
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 HashTableIterator {
	typedef HashNode<T> Node;
	typedef HashTableIterator<K,T,Ref, Ptr, KeyOfT, Hash> self;
	typedef const HashTable<K, T, KeyOfT, Hash> HashTable;
	Ref operator*()
	{
		return _node->_data;
	}

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

	bool operator!=(const self& t)
	{
		return _node != t._node;
	}

	bool operator==(const self& t)
	{
		return _node == t._node;
	}

	self& operator++()
	{
		if (_node->_next)
			_node = _node->_next;
		//如果当前桶走完了,找下一个桶
		//1.先找到当前位置
		KeyOfT kot;
		Hash hs;
		size_t hash = hs(kot(_node->_data)) % _pht->_tables.size();
		++hash;
		while (hash < _pht->_tables.size())
		{
			if (_pht->_tables[hash])
				break;
			hash++;
		}
		if (hash >= _pht->_tables.size())
			_node = nullptr;
		else
			_node = _pht->_tables[hash];
			
		return *this;
	}


	//构造
		HashTableIterator(Node* node, HashTable* pht)
			:_node(node)
			,_pht(pht)
		{}

		Node* _node;
		HashTable* _pht;
	};
	

//哈希类
	template<class K, class T, class KeyOfT, class Hash = HashFunc<K>>
	class HashTable {
	public:
		// 友元声明
		template<class K, class T, class Ref, class Ptr,class KeyOfT, class Hash>
		friend struct HashTableIterator;

		typedef HashNode<T> Node;

		typedef HashTableIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
		typedef HashTableIterator<K, T, const T&, const T*, KeyOfT, Hash> Const_Iterator;


		Iterator Begin()
		{
			if (_n == 0)
				return End();
			int i = 0;
			while (i < _tables.size())
			{
				if (_tables[i] != nullptr)
					break;
				i++;
			}
			return Iterator(_tables[i],this);
		}

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

		Const_Iterator Begin() const
		{
			if (_n == 0)
				return End();
			int i = 0;
			while (i < _tables.size())
			{
				if (_tables[i] != nullptr)
					break;
				i++;
			}
			return Const_Iterator(_tables[i], this);
		}

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


		HashTable()
		{
			_tables.resize(10, nullptr);
		}

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

		pair<Iterator, bool> Insert(const T& data)
		{
			KeyOfT kot;
			//1.先找是否已经插入过相同的值
			Iterator it = Find(kot(data));
			if (it != End())
				return make_pair(it,false);
			Hash hs;
			//2.判断是否需要扩容
			//如果负载因子为1就扩容
			if (_n == _tables.size())
			{
				HashTable<K, T, KeyOfT> h;
				h._tables.resize(2 * _tables.size(), nullptr);
				//只需要将哈希桶插入即可
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						size_t hash = hs(kot(cur->_data)) % h._tables.size();
						Node* Next = cur->_next;
						cur->_next = h._tables[hash];
						h._tables[hash] = cur;
						cur = Next;
					}
					_tables[i] = nullptr;
				}

				_tables.swap(h._tables);
			}

			//3.通过Hash函数找到插入位置

			size_t addr = hs(kot(data)) % _tables.size();

			//4.头插到新表
			if (_tables[addr] == nullptr)//如果是空,_n就需要++
				_n++;
			Node* newnode = new Node(data);
			newnode->_next = _tables[addr];
			_tables[addr] = newnode;
			return make_pair(Iterator(newnode,this), true);
		}

		Iterator Find(const K& key)
		{
			//先找到key对应的Hash值
			Hash hs;
			KeyOfT kot;
			size_t ht = hs(key) % _tables.size();
			Node* cur = _tables[ht];
			while (cur)
			{
				if (kot(cur->_data) == key)
					return Iterator(cur,this);
				cur = cur->_next;
			}
			return End();
		}

		bool Erase(const K& key)
		{
			//1.先找到删除的位置
			Hash hs;
			KeyOfT kot;
			size_t ht = hs(key) % _tables.size();
			Node* cur = _tables[ht];
			Node* parent = nullptr;
			while (cur)
			{
				if (kot(cur->_data) == key)
					break;
				parent = cur;
				cur = cur->_next;
			}
			if (cur == nullptr)
				return false;

			//2.删除对应节点
			if (parent)
				parent->_next = cur->_next;
			else
				_tables[ht] = cur->_next;

			//修改_n
			if (_tables[ht] == nullptr)
				_n--;

			//3.释放原节点
			delete cur;
			return true;
		}

	private:
		vector<Node*> _tables;
		size_t _n;//记录存储数据个数
	};

✨unordered_map类

#include"Hash.h"
// unordered_map类
namespace tutu
{
template<class K, class V>
class unordered_map {
public:
	//定义一个类获取键值对里面的键
	struct MapKeyOfT {
		const K& operator()(const pair<K, V>& data)
		{
			return data.first;
		}
	};
	typedef typename HashTable<K, pair<K, V>, MapKeyOfT>::Iterator iterator;
	typedef typename HashTable<K, pair<K, V>, MapKeyOfT>:: 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();
	}
	
	pair<iterator, bool> insert(const pair<K, V>& kv)
	{
		return _ht.Insert(kv);
	}

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

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

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

private:
	HashTable<K, pair<K, V>, MapKeyOfT> _ht;//传给哈希表

};

//测试函数
	void test_map()
	{
		unordered_map<string, string> dict;
		dict.insert({ "sort", "排序" });
		dict.insert({ "left", "左边" });
		dict.insert({ "right", "右边" });

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

		unordered_map<string, string>::iterator it = dict.begin();
		while (it != dict.end())
		{

			cout << it->first << ":" << it->second << endl;
			++it;
		}
		cout << endl;
	}
}

结果如下:


✨unordered_set类

#include"Hash.h"
namespace tutu{
	template<class K>
	class unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename HashTable<K, const K, SetKeyOfT>::Iterator iterator;
		typedef typename HashTable<K, const K, SetKeyOfT>::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();
		}

		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:
		HashTable<K, const K, SetKeyOfT> _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;
	}


void test_set()
{
	unordered_set<int> s;
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14, 3,3,15 };
	for (auto e : a)
	{
		s.insert(e);
	}

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

	unordered_set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		//*it += 1;

		cout << *it << " ";
		++it;
	}
	cout << endl;

	Print(s);
}

}

结果如下:


6. 结语

  unordered_mapunordered_set的底层都是使用哈希表来实现的,然后在外面套了一层壳,为了能够更好的实现代码复用,我们对哈希表进行了很多修改还使用了仿函数,封装了普通迭代器和const迭代器等,最终成功对unordered_mapunordered_set实现了封装,它们和使用红黑树对map和set封装非常类似,大家可以一起参考学习。以上就是今天所有的内容啦~ 完结撒花 ~🥳🎉🎉

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

大耳朵土土垚

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

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

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

打赏作者

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

抵扣说明:

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

余额充值