unordered_map和unordered_set

news2024/11/27 3:50:15

目录

一、unordered_map

1.1、unordered_map的特点

1.2、unordered_map和map的区别

二、unordered_set

2.1、unordered_set的特点

2.2、unordered_set和set的区别

三、哈系桶的改造

3.1 结构设置

3.2 构造函数和析构函数

3.3 数据插入

3.4 数据查找

3.5 数据删除

3.6 迭代器实现

3.7、友元声明

​编辑

四、unordered_map封装

五、unordered_set封装

六、哈希表


一、unordered_map

unordered_map其实就是与map相对应的一个容器。学过map就知道,map的底层是一个红黑树,通过中序遍历的方式可以以有序的方式遍历整棵树。而unordered_map,正如它的名字一样,它的数据存储其实是无序的,这也和它底层所使用的哈希结构有关。而在其他功能上,unordered_map和map基本上就是一致的。

1.1、unordered_map的特点

(1)unordered_map是用于存储<key, value>键值对的关联式容器,它允许通过key快速的索引到对应的value。

(2)在内部,unorder_map没有对<key, value>按照任何特定的顺序排序,为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的<key, value>键值对放在相同的桶中。

(3)unordered_map容器的搜索效率比map快,但它在遍历元素自己的范围迭代方面效率就比较低。

(4)它的迭代器只能向前迭代,不支持反向迭代。

1.2、unordered_map和map的区别

从大结构上看,unordered_map和map的模板其实没有太大差距。学习了map和set我们就应该知道,map是通过T来告诉红黑树要构造的树的存储数据类型的,unordered_map也是一样的,但是它的参数中多了Hash和Pred两个参数,这两个参数都传了仿函数,主要和哈希结构有关。这里先不过多讲解

二、unordered_set

unordered_set其实也是一样的,从功能上来看和set并没有什么区别,只是由于地层数据结构的不同,导致unordered_set的数据是无序的,但是查找效率非常高。

2.1、unordered_set的特点

  1. 无序性:unordered_set中的元素没有特定的顺序,不会根据插入的顺序或者元素的值进行排序。

  2. 唯一性:unordered_set中的元素是唯一的,不允许重复的元素。

  3. 快速查找:unordered_set使用哈希表实现,可以在平均常数时间内进行查找操作,即使在大型数据集上也能保持高效。

  4. 插入和删除效率高:unordered_set的插入和删除操作的平均时间复杂度为常数时间,即O(1)。

  5. 高效的空间利用:unordered_set使用哈希表来存储元素,不会浪费额外的空间。

  6. 不支持修改操作:由于unordered_set中的元素是唯一的,不支持直接修改元素的值,需要先删除旧值,再插入新值。

  7. 迭代器失效:在进行插入和删除

2.2、unordered_set和set的区别

很明显,unordered_set相较于set,多了Hash和Pred两个参数。这两个参数都是传了仿函数,和unordered_map与map之间的关系都是一样的,这里先不过多讲解。

三、哈系桶的改造

3.1 结构设置

首先,这里实现的哈希桶需要能够同时支持unordered_map和unordered_set的调用。因此,在传入的数据中就需要有一个T,来标识传入的数据类型。同时,还需要有Hash函数和KeyOfT来分别对传入的数据转换为整形和获取传入数据的key值,主要是提供给使用了KV模型的数据。

我们还要知道,哈希桶其实是保存在一个顺序表中的,每个下标对应的位置上都是桶的头节点,每个桶中的数据以单链表的方式链接起来。因此,我们就需要一个vector来存储结构体指针,这个结构体中包含了当前节点存储的数据和下一个节点的位置。当然,还有有一个_n来记录顺序表中数据的个数。

namespace BucketHash//哈希桶
{
	//哈希桶内的每个节点/
	template<class T>
	struct HashNode
	{
		T _data;//存储数据
		HashNode<T>* _next;//指向下一个节点
 
		HashNode(const T& data)
			: _data(data)
			, _next(nullptr)
		{}
	};
 
    template<class K, class T, class Hash, class KeyOfT>
	class HashBucket
	{
		template<class K, class T, class Hash, class KeyOfT>
		friend struct HashIterator;//友元声明,让迭代器可以访问哈希桶的私有成员
 
		typedef HashNode<T> Node;
	public:
		typedef HashIterator<K, T, Hash, KeyOfT> iterator;
 
 
	private:
		vector<Node*> _bucket;//存储数据的指针数组
		size_t _n;
	};
}

在类的模板参数中,Hash为将数据转化为整形的仿函数KeyOfT是返回键值的仿函数。

注意,上面的代码中有一个迭代器的重命名和迭代器结构体的友元声明。重命名是为了方便后续的使用。友元声明则和迭代器的实现有关,这里先不过多讲解。

3.2 构造函数和析构函数

注意,这里没有实现拷贝构造。并不是不需要实现,实际上,虽然哈希桶内的成员是自定义类型的,会去调自己的拷贝构造,但是这里只会进行浅拷贝,不满足需要。如果有需要,可以自己实现拷贝构造,实现起来也很简单。

HashBucket()
	:_n(0)
{
	_bucket.resize(10);//构建时默认开10个空间
}
 
~HashBucket()//析构函数
{
	for (auto& cur : _bucket)
	{
		while (cur)
		{
			Node* prev = cur;
			cur = cur->_next;
 
			delete prev;
			prev = nullptr;
		}
	}
}

3.3 数据插入

insert()函数返回的是pair<iterator, bool>,这是为了后续实现 [ ] 重载做准备。

在插入时,首先要先查看哈希桶中是否存在相同键值,存在就直接返回当前位置。第二步就是要查看哈希桶中的元素个数与哈希桶的容量之间的负载因子,如果等于1,就需要进行扩容。第三步则是开始插入节点。先找到映射位置,然后新建一个节点连接到对应的数组下标的空间中即可

pair<iterator, bool> insert(const T& data)//插入数据
{
	KeyOfT kt;
	iterator it = find(kt(data));
	if (it != end())//不允许数据重复,找到相同的返回
		return make_pair(it, false);
 
	if (_bucket.size() == _n)//负载因子设置为1,超过就扩容
	{
		vector<Node*> newbucket;//这种方式就无需再开空间拷贝节点
		newbucket.resize(NextPrime(_bucket.size()));//开一个素数大小的空间
		for (size_t i = 0; i < _bucket.size(); ++i)
		{
			Node* cur = _bucket[i];
			while (cur)
			{
				Node* next = cur->_next;
 
				size_t hashi = Hash()(kt(cur->_data)) % newbucket.size();//找映射位置
				cur->_next = newbucket[hashi];//头插到新哈希表
				newbucket[hashi] = cur;
 
				cur = next;
			}
 
			_bucket[i] = nullptr;//将原哈希表中的指针置为空
		}
 
		_bucket.swap(newbucket);//将newbucket中的节点全部交换给_bucket
	}
 
	Hash knt;
	size_t hashi = knt(kt(data)) % _bucket.size();//找映射位置
 
	Node* newnode = new Node(data);
	newnode->_next = _bucket[hashi];//头插,让插入的节点的下一个指针指向头节点
	_bucket[hashi] = newnode;//让头节点指向插入的节点
	++_n;
 
	return make_pair(iterator(newnode, this), true);
}

3.4 数据查找

查找数据很简单。先通过hash函数计算出要查找的数据键值所对应的位置,如果对应的位置上存储的是空,说明不存在,直接返回。如果不为空,则比对键值,相同返回,不相同向下找直到为空。

iterator find(const K& key)//查找
{
	size_t pos = Hash()(key) % _bucket.size();//找映射位置	
 
	Node* cur = _bucket[pos];
	while (cur)
	{
		if (KeyOfT()(cur->_data) == key)
			return iterator(cur, this);
		else
			cur = cur->_next;
	}
 
	return end();
}

3.5 数据删除

哈希桶与哈希表不同,要删除数据时不能使用find()函数搜索。因为哈希桶中的数据是用单链表链接起来的,用find()就无法知道节点的父节点,进而无法链接链表。

因此,在删除时,首先要调用hash函数获取关键码,根据关键码蛆对应位置上找。如果该位置存的数据为空,则表示不存在,返回;如果不为空,就比较键值,相同为找到,删除,不同就继续向下找直到为空。

bool erase(const K& key)//删除
{
	size_t hashi = Hash()(key) % _bucket.size();//找映射位置
	Node* cur = _bucket[hashi];
	Node* prev = nullptr;
 
	while (cur)
	{
		if (KeyOfT()(cur->_data) == key)
		{
			if (cur == _bucket[hashi])
				_bucket[hashi] = cur->_next;
			else
				prev->_next = cur->_next;
 
			delete cur;
			--_n;
			cur = nullptr;
 
			return true;
		}
		else
		{
			prev = cur;
			cur = cur->_next;
		}
	}
	return false;
}

3.6 迭代器实现

哈希桶中的迭代器实现方式比较复杂。要使用迭代器,首先就要有能够遍历哈希桶的手段。因此,为了便于遍历,迭代器的结构体中首先要有哈希桶的指针。当然,迭代器的结构体中还需要有一个数据的指针,用于初始化迭代器,获取对应位置上的内容。

要遍历哈希桶很简单,和find()的逻辑差不多。直接判断当前节点是否为空,不为空则返回;为空就说明当前下标对应的位置上已经没有数据了,就调用hash函数获取该节点的关键码。++关键码走向下一个下标,不为空则返回;为空则继续走,直到找到不为空的位置或结束。

如上图所示,一个哈希表,其中有四个哈希桶,迭代器是it。

++it操作:

  • 如果it不是某个桶的最后一个元素,则it指向下一个节点。
  • 如果it是桶的最后一个元素,则it指向下一个桶的头节点。
template<class K, class T, class Hash, class KeyOfT>
class HashBucket;//前置声明,因为迭代器中使用了HashBucket的模板,但是迭代器在它之前无法找到,所以要前置声明
 
template<class K, class T, class Hash, class KeyOfT>
struct HashIterator
{
	typedef HashNode<T> Node;
	typedef HashIterator<K, T, Hash, KeyOfT> Self;
	typedef HashBucket<K, T, Hash, KeyOfT> HB;//将哈希桶传进来
 
	HashIterator(Node* node, HB* hb)
		:_node(node)
		,_hb(hb)
	{}
 
	T& operator*()//解引用
	{
		return _node->_data;
	}
 
	T* operator->()//运算符->重载
	{
		return &_node->_data;
	}
 
	bool operator!=(const Self& sl) const//判断是否相等
	{
		return _node != sl._node;
	}
 
	Self operator++()//运算符++重载
	{
		if (_node->_next)//节点的下一个位置不为空
		{
			_node = _node->_next;
		}
		else//节点的下一个位置为空,说明该位置上已经没有值,找下一个不为空的桶节点
		{
			KeyOfT kt;
			size_t hashi = Hash()(kt(_node->_data)) % _hb->_bucket.size();
			++hashi;
			while (hashi < _hb->_bucket.size())//当前位置小于桶的数量
			{
				if (_hb->_bucket[hashi])//如果hashi位置上不为空,则修改_node的值
				{
					_node = _hb->_bucket[hashi];
					break;
				}
				else
					++hashi;
			}
 
			if (hashi == _hb->_bucket.size())//如果hashi的位置与桶的数量相当,说明没有找到
				_node = nullptr;
		}
		return *this;
	}
 
	Node* _node;//节点指针
	HB* _hb;//哈希桶的指针
};

注意,因为模板是向上寻找的,在迭代器的类模板中使用了哈希桶的类模板,所以如果迭代器类模板在哈希桶类模板之上,就需要进行类声明,让迭代器类模板能找到哈希桶类模板的位置。反之亦然。

注意,因为迭代器中传入了哈希桶,要对哈希桶进行遍历。但因为哈希桶中的成员变量都被设置为了私有,所以可以将迭代器声明为哈希桶的友元类,也可以单独提供一个获取哈希桶指针的函数。

有了迭代器的类模板后,哈希桶的迭代器实现起来就很轻松了。

iterator begin()
{
	for (size_t i = 0; i < _bucket.size(); ++i)
	{
		if (_bucket[i])
		{
			return iterator(_bucket[i], this);
		}
	}
 
	return iterator(nullptr, this);
}
 
iterator end()
{
	return iterator(nullptr, this);
}

注意,哈希桶的begin()要返回的是第一个不为空的桶,而不是第一个节点

3.7、友元声明

在++迭代器的时候,会使用到哈希表指针,哈希表指针又会使用到HashTable中的_tables。

  • HashTable中的_tables是私有成员,在类外是不能访问的。

解决这个问题可以在HashTable中写一个公有的访问函数,也可以采用友元,本喵这里就是使用的友元的方式。

  • 类模板的友元声明需要写模板参数,在类名前面加friend关键字,如上图绿色框中所示。

迭代器中的其他操作,如解引用,箭头,以及相等等运算符的重载本喵就不再详细介绍了,后面本喵会附源码,直接看代码即可。

四、unordered_map封装

#pragma once
#include "HashTable.h"
namespace lyl
{
    template<class K, class V, class Hash = HashFunc<K>>
	class unordered_map
	{
	public:
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::iterator iterator;

		typedef typename HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::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 = 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:
		HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
	};
  }
	
}

五、unordered_set封装

#pragma once

#include "HashTable.h"

namespace lyl
{
	template<class K, class Hash = HashFunc<K>>
	class unordered_set
	{
	public:
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename HashBucket::HashTable<K, K, SetKeyOfT, Hash>::const_iterator iterator;
		typedef typename HashBucket::HashTable<K, K, SetKeyOfT, Hash>::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:
		HashBucket::HashTable<K, K, SetKeyOfT, Hash> _ht;
	};

}

六、哈希表

#pragma once
#include <vector>
#include<iostream>


namespace OpenAddress
{

	enum State
	{
		EMPTY,
		EXIST,
		DELETE
	};

	template<class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		State _state = EMPTY;
	};

	template<class K, class V>
	class HashTable
	{
	public:
		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
				return false;

			// 负载因子超过0.7就扩容
			//if ((double)_n / (double)_tables.size() >= 0.7)
			if (_tables.size() == 0 || _n * 10 / _tables.size() >= 7)
			{
				//size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				//vector<HashData> newtables(newsize);
				 遍历旧表,重新映射到新表
				//for (auto& data : _tables)
				//{
				//	if (data._state == EXIST)
				//	{
				//		// 重新算在新表的位置
				//		size_t i = 1;
				//		size_t index = hashi;
				//		while (newtables[index]._state == EXIST)
				//		{
				//			index = hashi + i;
				//			index %= newtables.size();
				//			++i;
				//		}

				//		newtables[index]._kv = data._kv;
				//		newtables[index]._state = EXIST;
				//	}
				//}

				//_tables.swap(newtables);

				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				HashTable<K, V> newht;
				newht._tables.resize(newsize);

				// 遍历旧表,重新映射到新表
				for (auto& data : _tables)
				{
					if (data._state == EXIST)
					{
						newht.Insert(data._kv);
					}
				}

				_tables.swap(newht._tables);
			}

			size_t hashi = kv.first % _tables.size();

			// 线性探测
			size_t i = 1;
			size_t index = hashi;
			while (_tables[index]._state == EXIST)
			{
				index = hashi + i;
				index %= _tables.size();
				++i;
			}

			_tables[index]._kv = kv;
			_tables[index]._state = EXIST;
			_n++;

			return true;
		}

		HashData<K, V>* Find(const K& key)
		{
			if (_tables.size() == 0)
			{
				return 0;
			}

			size_t hashi = key % _tables.size();

			// 线性探测
			size_t i = 1;
			size_t index = hashi;
			while (_tables[index]._state != EMPTY)
			{
				if (_tables[index]._state == EXIST
					&& _tables[index]._kv.first == key)
				{
					return &_tables[index];
				}

				index = hashi + i;
				index %= _tables.size();
				++i;

				// 如果已经查找一圈,那么说明全是存在+删除
				if (index == hashi)
				{
					break;
				}
			}

			return nullptr;
		}

		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);
			if (ret)
			{
				ret->_state = DELETE;
				--_n;
				return true;
			}
			else
			{
				return false;
			}
		}

	private:
		vector<HashData<K, V>> _tables;
		size_t _n = 0; // 存储的数据个数

		//HashData* tables;
		//size_t _size;
		//size_t _capacity;
	};
}

template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return key;
	}
};

// 特化
template<>
struct HashFunc<string>
{
	// BKDR
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto ch : s)
		{
			hash += ch;
			hash *= 31;
		}

		return hash;
	}
};


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

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

	// 前置声明
	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 __HashIterator
	{
		typedef HashNode<T> Node;
		typedef HashTable<K, T, KeyOfT, Hash> HT;
		typedef __HashIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;

		typedef __HashIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;

		Node* _node;
		const HT* _ht;

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

		__HashIterator(const Iterator& it)
			:_node(it._node)
			, _ht(it._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 != nullptr)
			{
				_node = _node->_next;
			}
			else
			{
				// 找下一个不为空的桶
				KeyOfT kot;
				Hash hash;
				// 算出我当前的桶位置
				size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size();
				++hashi;
				while (hashi < _ht->_tables.size())
				{
					if (_ht->_tables[hashi])
					{
						_node = _ht->_tables[hashi];
						break;
					}
					else
					{
						++hashi;
					}
				}

				// 没有找到不为空的桶
				if (hashi == _ht->_tables.size())
				{
					_node = nullptr;
				}
			}

			return *this;
		}
	};

	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 __HashIterator;

		typedef HashNode<T> Node;
	public:
		typedef __HashIterator<K, T, T&, T*, KeyOfT, Hash> iterator;
		typedef __HashIterator<K, T, const T&, const T*, KeyOfT, Hash> const_iterator;

		iterator begin()
		{
			Node* cur = nullptr;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				cur = _tables[i];
				if (cur)
				{
					break;
				}
			}

			return iterator(cur, this);
		}

		iterator end()
		{
			return iterator(nullptr, this);
		}

		const_iterator begin() const
		{
			Node* cur = nullptr;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				cur = _tables[i];
				if (cur)
				{
					break;
				}
			}

			return const_iterator(cur, this);
		}

		const_iterator end() const
		{
			return const_iterator(nullptr, this);
		}

		~HashTable()
		{
			for (auto& cur : _tables)
			{
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}

				cur = nullptr;
			}
		}

		iterator Find(const K& key)
		{
			if (_tables.size() == 0)
				return end();

			KeyOfT kot;
			Hash hash;
			size_t hashi = hash(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return iterator(cur, this);
				}

				cur = cur->_next;
			}

			return end();
		}

		bool Erase(const K& key)
		{
			Hash hash;
			KeyOfT kot;
			size_t hashi = hash(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					if (prev == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;

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

			return false;
		}

		// 休息一下:15:55继续
		// size_t newsize = GetNextPrime(_tables.size());
		size_t GetNextPrime(size_t prime)
		{
			// SGI
			static const int __stl_num_primes = 28;
			static const unsigned long __stl_prime_list[__stl_num_primes] =
			{
				53, 97, 193, 389, 769,
				1543, 3079, 6151, 12289, 24593,
				49157, 98317, 196613, 393241, 786433,
				1572869, 3145739, 6291469, 12582917, 25165843,
				50331653, 100663319, 201326611, 402653189, 805306457,
				1610612741, 3221225473, 4294967291
			};

			size_t i = 0;
			for (; i < __stl_num_primes; ++i)
			{
				if (__stl_prime_list[i] > prime)
					return __stl_prime_list[i];
			}

			return __stl_prime_list[i];
		}

		pair<iterator, bool> Insert(const T& data)
		{
			KeyOfT kot;
			iterator it = Find(kot(data));
			if (it != end())
			{
				return make_pair(it, false);
			}

			Hash hash;

			// 负载因因子==1时扩容
			if (_n == _tables.size())
			{
				/*size_t newsize = _tables.size() == 0 ? 10 : _tables.size()*2;
				HashTable<K, V> newht;
				newht.resize(newsize);
				for (auto cur : _tables)
				{
					while (cur)
					{
						newht.Insert(cur->_kv);
						cur = cur->_next;
					}
				}

				_tables.swap(newht._tables);*/

				//size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				size_t newsize = GetNextPrime(_tables.size());
				vector<Node*> newtables(newsize, nullptr);
				//for (Node*& cur : _tables)
				for (auto& cur : _tables)
				{
					while (cur)
					{
						Node* next = cur->_next;

						size_t hashi = hash(kot(cur->_data)) % newtables.size();

						// 头插到新表
						cur->_next = newtables[hashi];
						newtables[hashi] = cur;

						cur = next;
					}
				}

				_tables.swap(newtables);
			}

			size_t hashi = hash(kot(data)) % _tables.size();
			// 头插
			Node* newnode = new Node(data);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;

			++_n;
			return make_pair(iterator(newnode, this), false);;
		}

		size_t MaxBucketSize()
		{
			size_t max = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				auto cur = _tables[i];
				size_t size = 0;
				while (cur)
				{
					++size;
					cur = cur->_next;
				}

				//printf("[%d]->%d\n", i, size);
				if (size > max)
				{
					max = size;
				}
			}

			return max;
		}
	private:
		vector<Node*> _tables; // 指针数组
		size_t _n = 0; // 存储有效数据个数
	};
}

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

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

相关文章

MQ面试题整理(持续更新)

1. MQ的优缺点 优点&#xff1a;解耦&#xff0c;异步&#xff0c;削峰 缺点&#xff1a; 系统可用性降低 系统引入的外部依赖越多&#xff0c;越容易挂掉。万一 MQ 挂了&#xff0c;MQ 一挂&#xff0c;整套系统崩 溃&#xff0c;你不就完了&#xff1f;系统复杂度提高 硬生…

2.4作业

编写程序实现二叉树的创建&#xff0c;三种遍历自己销毁 #include <myhead.h> //树结点 typedef struct Node {char data;struct Node* lchild;struct Node* rchild; }*Btree; //结点创建 Btree create_node() {Btree t(Btree)malloc(sizeof(struct Node));if(NULL t)re…

ARM PAC指针认证的侧信道攻击——PACMAN安全漏洞

目录 Q1. PACMAN论文的内容是什么&#xff1f; Q2. Arm处理器是否存在漏洞&#xff1f; Q3. 受Arm合作伙伴架构许可设计的处理器实现是否受到影响&#xff1f; Q4. Cortex-M85受到影响吗&#xff1f; Q5. Cortex-R82受到影响吗&#xff1f; Q6. 指针认证如何保护软件&…

Spark Shuffle Service简介与测试

一 Dynamic Resource Allocation(动态资源分配) 了解Shuffle Service之前&#xff0c;我们需要先了解和Shuffle Service有关的另一个特性&#xff1a;动态资源分配。 Spark管理资源有两种方式&#xff1a;静态资源分配和动态资源分配。 静态资源分配&#xff1a;spark提交任…

深度学习在智能交互中的应用:人与机器的和谐共生

深度学习与人类的智能交互是当前人工智能领域研究的热点之一。深度学习作为机器学习的一个重要分支&#xff0c;具有强大的特征学习和模式识别能力&#xff0c;可以模拟人脑的神经网络进行数据分析和预测。而人类的智能交互则是指人类与机器之间的信息交流和操作互动&#xff0…

1.理解AOP,使用AOP

目录 1AOP基础 1.1 AOP概述 1.2AOP快速使用 2.3 AOP核心概念 1AOP基础 首先介绍一下什么是AOP&#xff0c;再通过一个快速入门程序&#xff0c;让大家快速体验AOP程序的开发。最后再介绍AOP当中所涉及到的一些核心的概念。 1.1 AOP概述 什么是AOP&#xff1f; 说白了&am…

【C/C++ 12】C++98特性

目录 一、命名空间 二、缺省参数 三、函数重载 四、引用 五、内联函数 六、异常处理 一、命名空间 在C/C项目中&#xff0c;存在着大量的变量、函数和类&#xff0c;这些变量、函数和类都存在于全局作用域中&#xff0c;可能会导致命名冲突。 使用命名空间的目的就是对…

YOLOv5改进 | 损失函数篇 | 更加聚焦的边界框损失Focaler-IoU | 二次创新Inner-FocalerIoU

一、本文介绍 本文给大家带来的改进机制是更加聚焦的边界框损失Focaler-IoU以及我二次创新的InnerFocalerIoU同时本文的内容支持现阶段的百分之九十以上的IoU,比如Focaler-IoU、Focaler-MpdIoU、Innner-Focaler-MpdIoU、Inner-FocalerIoU包含非常全的损失函数,边界框的损失函…

sqlserver alwayson部署文档手册

1、ALWAYSON概述 详细介绍参照官网详细文档,我就不在这里赘述了&#xff1a; https://learn.microsoft.com/zh-cn/sql/database-engine/availability-groups/windows/overview-of-always-on-availability-groups-sql-server?viewsql-server-ver16 下图显示的是一个包含一个…

Docker部署Grafana+Promethus监控Mysql和服务器

一、Grafana部署所需资源 Grafana 需要最少的系统资源&#xff1a; 建议的最小内存&#xff1a;512 MB建议的最低 CPU&#xff1a;1 官方文档&#xff1a;https://grafana.com/docs/grafana/latest/getting-started/build-first-dashboard/ 可以看到&#xff0c;我的这台服务…

探索前端开发框架:React、Angular 和 Vue 的对决(三)

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

mysql:事务的特性ACID、并发事务(脏读、不可重复读、幻读、如何解决、隔离级别)、undo log和redo log的区别、相关面试题和答案

事务是一组操作的集合&#xff0c;它会把所有的操作作为一个整体一起向系统提交或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失败。 事务的特性&#xff08;ACID&#xff09; 原子性&#xff08;Atomicity&#xff09;&#xff1a;事务是不可分割的…

QT中,对于大小端UDP网络发送的demo,帧头帧尾

简单demo: 发送端&#xff1a; #include <QUdpSocket> #include <QtEndian>#pragma pack(1) struct Test {unsigned char t1:1;unsigned char t2:2;unsigned char t3:3;unsigned char t4:2;quint8 a 1;quint16 b 2;quint16 c 3;//double b …

Sqli靶场23-->30

不知不觉鸽了几天了&#xff0c;没办法去旅游摸鱼是这样的了&#xff0c;抓紧时间来小更一下 23.过滤注释符号 先手工注入一下&#xff0c;就能发现两个单引号不报错&#xff0c;但是一旦上到注释符号的话就会报错&#xff0c;可以猜测出是对注释符号进行了过滤&#xff0c;我…

华为配置OSPF与BFD联动示例

配置OSPF与BFD联动示例 组网图形 图1 配置OSPF与BFD联动组网图 OSPF与BFD联动简介配置注意事项组网需求配置思路操作步骤配置文件 OSPF与BFD联动简介 双向转发检测BFD&#xff08;Bidirectional Forwarding Detection&#xff09;是一种用于检测转发引擎之间通信故障的检测…

深度学习系列56:使用whisper进行语音转文字

1. openai-whisper 这应该是最快的使用方式了。安装pip install -U openai-whisper&#xff0c;接着安装ffmpeg&#xff0c;随后就可以使用了。模型清单如下&#xff1a; 第一种方式&#xff0c;使用命令行&#xff1a; whisper japanese.wav --language Japanese --model…

目标检测任务的调研与概述

目标检测任务的调研与概述 0 FQA1 目标检测任务基本知识&#xff1a;1.1 什么是目标检测&#xff1f;1.2 目标检测的损失函数都有那些&#xff1f;1.2.1 类别损失&#xff1a;1.2.2 位置损失&#xff1a; 1.3 目标检测的评价指标都有那些&#xff1f;1.4 目标检测有那些常见的数…

移动云ONAIR媒体云全解读!媒体内容数字化融合一站式解决方案

当下&#xff0c;传统媒体面临着诸多挑战&#xff0c;如何利用信息技术提升内容的质量、形式和分发效率&#xff0c;成为媒体行业的迫切需求。移动云作为数字中国建设的“主力军”&#xff0c; 立足于新兴媒体与云计算市场的变化与需求&#xff0c;推出了ONAIR 媒体云解决方案&…

Redis核心技术与实战【学习笔记】 - 20.Redis原子操作及并发访问

概述 使用 Redis 时&#xff0c;不可避免地会遇到并发访问的问题&#xff0c;比如说如果多个用户同时下单&#xff0c;就会对缓存在 Redis 中的商品库存并发更新。一旦有了并发写操作&#xff0c;数据就会被修改&#xff0c;如果我们没有对并发写请求做好控制&#xff0c;就可…

机器学习中的有监督学习和无监督学习

有监督学习 简单来说&#xff0c;就是人教会计算机学会做一件事。 给算法一个数据集&#xff0c;其中数据集中包含了正确答案&#xff0c;根据这个数据集&#xff0c;可以对额外的数据希望得到一个正确判断&#xff08;详见下面的例子&#xff09; 回归问题 例如现在有一个…