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

news2024/11/24 18:42:46
🔥 个人主页:大耳朵土土垚
🔥 所属专栏:C++从入门至进阶

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

文章目录

  • 前言
  • 1. unordered_set和unordered_map介绍
    • ✨unordered_map介绍
    • ✨unordered_set介绍
  • 2. 修改哈希表
  • 3. 迭代器
    • ✨const迭代器
  • 4. unordered_map的[]访问
  • 5. unordered_set和unordered_map封装
    • ✨修改后的哈希表
    • ✨unordered_map类
    • ✨unordered_set类
  • 6. 结语

前言

  前面我们学习过红黑树实现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封装非常类似,大家可以一起参考学习。以上就是今天所有的内容啦~ 完结撒花 ~🥳🎉🎉

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2045878.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CSS继承、盒子模型、float浮动、定位、diaplay

一、CSS继承 1.文字相关的样式会被子元素继承。 2.布局样式相关的不会被子元素继承。&#xff08;用inherit可以强行继承&#xff09; 实现效果&#xff1a; 二、盒子模型 每个标签都有一个盒子模型&#xff0c;有内容区、内边距、边框、外边距。 从内到外&#xff1a;cont…

基于 Android studio 实现停车场管理系统--原创

目录 一、项目演示 二、开发环境 三、项目页面 四、项目详情 五、项目完整源码 一、项目演示 二、开发环境 三、项目详情 1.启动页 这段代码是一个简单的Android应用程序启动活动&#xff08;Activity&#xff09;&#xff0c;具体功能如下&#xff1a; 1. **延迟进入登…

计算机网络三级笔记--原创 风远 恒风远博

典型设备中间设备数据单元网络协议物理层中继器、集线器中继器、集线器数据位(bit) binary digit二进 制数据的缩写HUB使用了光纤、 同轴电缆、双绞 线.数据链路层网卡、网桥、交换机网桥、交换机数据帧(Frame)STP、ARQ、 SW、CSMA/CD、 PPP(点对点)、 HDLC、ATM网络层路由器、…

SQL注入(cookie、base64、dnslog外带、搜索型注入)

目录 COOKIE注入 BASE64注入 DNSLOG注入—注入判断 什么是泛解析&#xff1f; UNC路径 网上邻居 LOAD_FILE函数 搜索型注入—注入判断 本文所使用的sql注入靶场为sqli-labs-master&#xff0c;靶场资源文件已上传&#xff0c;如有需要请前往主页或以下链接下载 信安必备…

【漫谈C语言和嵌入式002】嵌入式中的大小端

在计算机科学中&#xff0c;"端序"&#xff08;Endianness&#xff09;是指多字节数据类型&#xff08;如整数或浮点数&#xff09;在内存中的存储方式。主要分为两种&#xff1a;大端模式&#xff08;Big-Endian&#xff09;和小端模式&#xff08;Little-Endian&am…

星戈瑞FITC-DXMS荧光素标记地塞米松不同方向的应用

FITC-DXMS&#xff0c;全称异硫氰基荧光素-地塞米松&#xff0c;是一种创新的科研试剂。他是由FITC-NH2的&#xff08;-NH2&#xff09;氨基与地塞米松的-OH&#xff08;羟基&#xff09;结合。它结合了地塞米松的特性和荧光素的高灵敏度标记技术&#xff0c;为医药研究、生物医…

栈与括号匹配——20、636、591、32(简中难难)

20. 有效的括号&#xff08;简单&#xff09; 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭…

springboot的学习(二):常用配置

简介 springboot的各种常用的配置。 springboot 项目是要打成jar包放到服务器上运行的。 打包 idea上使用maven打包的时候&#xff0c;会执行自动测试&#xff0c;可能会对数据库中的数据有影响&#xff0c;先点跳过测试&#xff0c;在点package。 运行 Windows上运行的…

新闻资讯小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;新闻类别管理&#xff0c;新闻信息管理&#xff0c;用户管理&#xff0c;管理员管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;新闻信息&#xff0c;我的 开发系统&a…

极市平台 | 如何通俗理解扩散模型?

本文来源公众号“极市平台”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;如何通俗理解扩散模型&#xff1f; 极市导读 还有谁没有看过diffusion的工作&#xff0c;席卷AI圈的diffusion到底是什么&#xff1f;本文作者用尽量通…

tcpdump快速入门及实践手册

tcpdump快速入门及实践手册 1. 快速入门 [1]. 基本用法 基本用法&#xff1a; tcpdump [选项 参数] [过滤器 参数] [rootkysrv1 pwe]# tcpdump -h tcpdump version 4.9.3 libpcap version 1.9.1 (with TPACKET_V3) OpenSSL 1.1.1f 31 Mar 2020 Usage: tcpdump [-aAbdDefhH…

Python爬虫使用实例

IDE&#xff1a;大部分是在PyCharm上面写的 解释器装的多 → 环境错乱 → error&#xff1a;没有配置&#xff0c;no model 爬虫可以做什么&#xff1f; 下载数据【文本/二进制数据&#xff08;视频、音频、图片&#xff09;】、自动化脚本【自动抢票、答题、采数据、评论、点…

3.2 实体-关系模型(ER模型)

欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;欢迎订阅相关专栏&#xff1a; 工&#x1f497;重&#x1f497;hao&#x1f497;&#xff1a;野老杂谈 ⭐️ 全网最全IT互联网公司面试宝典&#xff1a;收集整理全网各大IT互联网公司技术、项目、HR面试真题.…

Keycloak中授权的实现-转载

在Keycloak中实现授权&#xff0c;首先需要了解与授权相关的一些概念。授权&#xff0c;简单地说就是某个&#xff08;些&#xff09;用户或者某个&#xff08;些&#xff09;用户组&#xff08;Policy&#xff09;&#xff0c;是否具有对某个资源&#xff08;Resource&#xf…

基于SpringBoot的餐饮订单系统-计算机毕业设计源码39867

摘 要 随着现代生活节奏的加快和人们对便捷餐饮服务的需求不断增长&#xff0c;基于Spring Boot的餐饮订单系统的设计与实现成为当前研究的关键课题。本研究旨在开发一款包括首页、通知公告、餐饮资讯、餐饮菜单、商城管理等功能模块的系统&#xff0c;旨在提供便捷高效的餐饮订…

了解一下内测系统

内测系统是什么&#xff1f; 在软件或应用程序开发的过程中&#xff0c;供开发人员进行测试和调试的系统。 内测系统的作用是什么&#xff1f; 达到让用户使用游戏或者软件的时候体验感更好、减少风险、方便开发者更好的找到并解决自己软件中的问题。测试好后的app可以将自己的…

C ++ 也可以搭建Web?高性能的 C++ Web 开发框架 CPPCMS + MySQL 实现快速入门案例

什么是CPPCMS&#xff1f; CppCMS 是一个高性能的 C Web 开发框架&#xff0c;专为构建快速、动态的网页应用而设计&#xff0c;特别适合高并发和低延迟的场景。其设计理念类似于 Python 的 Django 或 Ruby on Rails&#xff0c;但针对 C 提供了更细粒度的控制和更高效的性能。…

Linux--传输层协议UDP

目录 传输层 再谈端口号 端口号范围划分 认识知名端口号(Well-Know Port Number) 两个问题 UDP 协议 UDP 协议端格式 UDP 的特点 面向数据报 UDP 的缓冲区 UDP 使用注意事项 基于 UDP 的应用层协议 进一步理解UDP协议 传输层 负责数据能够从发送端传输接收端. 再谈…

STM32F407ZET6使用LCD(9341)

1.原理图 屏幕是中景园2.8寸液晶屏&#xff0c;9341驱动不带触摸屏版本 2.STM32CUBEMX配置 3.编写驱动程序

【全国大学生电子设计竞赛】2021年K题

&#x1f970;&#x1f970;全国大学生电子设计大赛学习资料专栏已开启&#xff0c;限时免费&#xff0c;速速收藏~