【C++ —— 哈希】学习笔记 | 模拟实现封装unordered_map和unordered_set

news2024/12/26 0:56:10

文章目录

  • 前言
  • 一、unordered系列关联式容器
        • 1.1 unordered_map
        • 1.2 unordered_set
  • 二、底层结构
        • 2.1哈希概念(哈希是一种算法思想)
        • 2.2哈希冲突
        • 2.3 解决哈希冲突方法:
          • 1.直接定址法(值和位置关系是唯一关系,每个人都有唯一位置,值很分散,直接定址法会导致空间开很大,资源的浪费)
          • 2.闭散列
          • 2.1 开放地址法/线性探测
          • 3.开散列
            • 1. 哈希桶/拉链法
          • 2.字符串映射问题
  • 三、unordered_map和unordered_set封装哈希表模拟实现
      • 1.UnOrdered_map.h
      • 2.UnOrdered_set.h
      • 3.HashTable.h


前言

本文参考文档:https://legacy.cplusplus.com/reference/unordered_map/


一、unordered系列关联式容器

1.1 unordered_map

1.unordered_map接口说明

函数声明功能介绍
unordered_map构造不同格式的unordered_map对象

在这里插入图片描述
2. unordered_map的容量

函数声明功能介绍
bool empty() const检测unordered_map是否为空
size_t size() const获取unordered_map的有效元素个数

3.unordered_map的迭代器(无序set和map的迭代器都是单向迭代器)

函数功能函数介绍
begin ()返回unordered_map中第一个元素的迭代器
end()返回unordered_map中最后一个元素后一个位置的迭代器
cbegin()返回unordered_map中第一个元素的const迭代器
cend()返回unordered_map中最后一个元素后一个位置的const迭代器

4.unordered_map的元素访问

函数功能函数介绍
operator[]返回key值对应的val值(没有默认值)

注意:该函数中实际调用哈希桶的插入操作,用参数key与V()构造一个默认值往底层哈希桶
中插入,如果key不在哈希桶中,插入成功,返回V(),插入失败,说明key已经在哈希桶中,
将key对应的value返回。

5.unordered_map查询

函数功能函数介绍
find()查询key值是否存在,存在返回key值对应的迭代器的位置,返回key在哈希桶中的位置
count返回哈希桶中关键码为key的键值对的个数

注意:unordered_map中key是不能重复的,因此count函数的返回值最大为1

在这里插入图片描述
6. unordered_map的修改操作

函数功能函数介绍
insert向容器中插入键值对
erase删除容器中的键值对
void clear()清空容器中有效元素个数
void swap()交换两个容器中的元素

7.unordered_map的桶操作

函数功能函数介绍
size_t bucket_count()const返回哈希桶中桶的总个数
size_t bucket_size(size_t n)const返回n号桶中有效元素的总个数
size_t bucket(const K& key)返回元素key所在的桶号
1.2 unordered_set

unordered_set接口根map差不多一样,不过set只存key值且不能修改
这里就不过多赘述了,详情可自行观看文档
set文档资料

二、底层结构

2.1哈希概念(哈希是一种算法思想)

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即
O( l o g 2 N log_2 N log2N)
,搜索的效率取决于搜索过程中元素的比较次数。

理想的搜索方法:可以 不经过任何比较,一次直接从表中得到要搜索的元素。
如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立
一一映射的关系,那么在查找时通过该函数可以很快找到该元素

哈希/散列:映射 一个值和另一个值建立关系。
哈希表/散列表: 映射 关键字和存储位置建立一个关系。

2.2哈希冲突

不同关键字通过相同哈希哈数计算出相同的哈希地址该种现象称为哈希冲突
或哈希碰撞。

把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。

2.3 解决哈希冲突方法:
1.直接定址法(值和位置关系是唯一关系,每个人都有唯一位置,值很分散,直接定址法会导致空间开很大,资源的浪费)

在这里插入图片描述

2.闭散列
2.1 开放地址法/线性探测

当前位置被占用,在开放空间里按某种规则,找一个没有被占用的位置存储

  1. 线性探测 hashi + i(i >= 0)
  2. 二次探测 hashi + i^2(i >= 0)
    在这里插入图片描述
    多定义一个负载因子,存储有效关键字个数
    当有效关键字个数超过表大小的6~8成就再次扩容,用此种方法可以有效的减少哈希冲突的次数。
    以空间换效率。

    注意:
    负载因子太多:会影响效率。
    负载因子太少:会浪费空间。
    代码实现:
	enum Stutas

	{
		//删除状态的意义:
		//1.再插入,这个位置可以覆盖
		//2.防止后面冲突的值,出现找不到的情况,遇到删除状态还要继续向后寻找
		DELETE,
		EMPTY,
		EXIST

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


	//开放地址法
	//线性查找,插入
	template<class K>
	struct Hashfunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};
	//struct HashfuncString
	//{
	//	//BKDR算法
	//	size_t operator()(const string& key)
	//	{
	//		size_t hash = 0;
	//		//把他们的ascll码值加起来
	//		for (auto e : key)
	//		{
	//			hash += e;
	//		}
	//		return hash;
	//	}
	//};
	template<>
	struct Hashfunc<string>
	{

		size_t operator()(const string& key)
		{
			size_t hash = 0;
			//把他们的ascll码值加起来
			for (auto e : key)
			{
				hash += e;
			}
			return hash;
		}
	};

	template<class K,class V, class Hash = Hashfunc<K>>
	class HashTable
	{
	public:
		HashTable()
		{
			_tables.resize(10);//resize也直接会开初始化size的大小。reserve之会开capecity的大小

		}
		//提供find解决insert能够插入相同key的问题
		
		HashData<K, V>* Find(const K& key)
		{
			Hash ht;
			size_t hashi = ht(key) % _tables.size();
			while (_tables[hashi]._s != EMPTY)
			{
				if (_tables[hashi]._s == EXIST && _tables[hashi]._kv.first == key)
				{
					//找到return找到位置的地址
					//没找到return空
					return &_tables[hashi];
				}
				hashi++;
				//hashi超出table的size大小就给他%回来
				hashi = hashi % _tables.size();
			}

			return nullptr;
		}

		bool Insert(const pair<K, V>& kv)
		{
			Hash ht;
			if (Find(kv.first))
			{
				return false;
			}
			if ((double)_n / _tables.size() >= 0.7)
			{
				//不能直接扩容size的二倍,还要转移数据,重新映射关系(扩容之后值和空间的位置关系已经乱了)
				//释放旧空间
				size_t newsize = _tables.size() * 2;
				HashTable<K,V,Hash> newHT;
				newHT._tables.resize(newsize);
				//遍历旧表,插入新表
				for ( size_t i= 0; i < _tables.size(); i++)
				{
					if (_tables[i]._s == EXIST)
					{
						newHT.Insert(_tables[i]._kv);
					}
				}
				_tables.swap(newHT._tables);

			}
			//size_t hashi = kv.first % _tables.size();//key不一定是整形,如果是string呢?用仿函数把kv.first的值转化整形值
			//先用字符串映射一个整形
			size_t hashi = ht(kv.first) % _tables.size();

			while (_tables[hashi]._s == EXIST)
			{
				hashi++;
				//hashi超出table的size大小就给他%回来
				hashi = hashi % _tables.size();
			}
			_tables[hashi]._kv = kv;
			_tables[hashi]._s = EXIST;
			++_n;

		}
		bool Erase(const K& key)
		{
			HashData<K,V>* ret =  Find(key);
			//find找到会返回找到位置的地址
			if (ret)
			{
				ret->_s = DELETE;
				--_n;
				return true;
			}
			return false;
		}
		void Print()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				if (_tables[i]._s == EXIST)
				{
					//cout << i << "->";
					cout <<"["<<i<<"]->"<< _tables[i]._kv.first << ":"<< _tables[i]._kv.second << endl;
					/*printf("[%d]->%d\n", i, _tables[i]._kv.first);
					printf("")*/
				}
				else if (_tables[i]._s == EMPTY)
				{
					printf("[%d]->\n", i);
				}
				else 
				{
					printf("[%d]->DELETE\n", i);
				}

			}
			cout << endl;
		}
	private:
		vector<HashData<K,V>> _tables;
		size_t _n = 0;//存储的关键字个数
	};
	

3.开散列
1. 哈希桶/拉链法

哈希桶的平均时间复杂度为O(1)

思路概念:
我们把每个hashi映射的位置,替换成list,链接每次插入的冲突的新key

可以参考下图:如4位置都是冲突的关键字。
在这里插入图片描述

2.字符串映射问题

整形还是可以用除留余数法计算key映射的位置,但是例如string这种字符串呢
我们可以使用仿函数,实现类型的转换。
例如把字符串的每个字符的ascll码值加起来。
代码实现:

template<class K>
	struct Hashfunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};
	struct HashfuncString
	{
		size_t operator()(const string& key)
		{
			size_t hash = 0;
			//把他们的ascll码值加起来
			for (auto e : key)
			{
				hash += e;
			}
			return hash;
		}
	};

但是这种转化函数会有一些缺陷,例如abcacb这两个不同的字符串ascll码值加起来都是相同的,导致了映射位置冲突。
有没有什么办法能解决这类的问题呢?
有大佬专门研究出了很多种算法我们可以参考:
字符串哈希算法
例如:
在这里插入图片描述
这类算法会减少字符串转哈希算法的冲突。但是肯定不能完全避免。
我们也可以模仿大佬算法完善我们的代码:

struct HashfuncString
{
	size_t operator()(const string& key)
	{
		size_t hash = 0;
		//把他们的ascll码值加起来
		for (auto e : key)
		{
			hash *= 31;//在加ascll码值前*31/131
			hash += e;
		}
		return hash;
	}
};

这种方式也是类似库里面unordered_map的实现方式(要传一个仿函数)
在这里插入图片描述
但是库里面其实比我们实现的更好一些,
库里面不用传第三个模板参数直接取模,
库里面的unordered_map用string做key可以直接转化为整型。

std::unordered_map<string,string> dict;

我们可以用特化模板参数来模拟实现这总功能。

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;
		//把他们的ascll码值加起来
		for (auto e : key)
		{
			hash += e;
		}
		return hash;
	}
};

利用哈希桶实现哈希表代码:

template<class K,class V>
	struct HashNode
	{
		HashNode* _next;
		pair<K, V> _kv;

		HashNode(const pair<K, V>& kv)
			:_kv(kv)
			,_next(nullptr)
		{}
	};
	template<class K>
	struct Hashfunc
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};
	
	template<>
	struct Hashfunc<string>
	{
		//BKDR算法
		size_t operator()(const string& key)
		{
			size_t hash = 0;
			//把他们的ascll码值加起来
			for (auto e : key)
			{
				hash *= 31;//在加ascll码值之前*31/131...
				hash += e;
			}
			return hash;
		}
	};
	template<class K,class V ,class Hash = Hashfunc<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		HashTable()
		{
			_tables.resize(10);
		}
		~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;
			}
		}
		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
				return false;
			
			Hash ht;
			//扩容用开放地址法的扩容方法会产生大量的资源浪费
			//可以考虑直接把旧表的数据挪动下来
			if (_n == _tables.size())
			{
				vector<Node*> newtable;
				newtable.resize(_tables.size() * 2, nullptr);
				//遍历旧表
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						//挪动旧表的数据映射到新表
						Node* next = cur->_next;
						size_t hashi = ht(cur->_kv.first) % newtable.size();
						cur->_next = newtable[i];
						newtable[i] = cur;

						//利用cur把节点挪动到新表
						//cur利用next返回到旧表以此循环
						cur = next;

						
					}
					_tables[i] = nullptr;
					//旧表这个桶数据已经被挪动完,记得制空。
					//以防后面析构有问题
				}
				_tables.swap(newtable);

			}
			
			//先算出数据要存入哪个桶
			size_t hashi = ht(kv.first) % _tables.size();
			//头插
			Node* newnode = new Node(kv);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;
			return true;
		}
		Node* Find(const K& key)
		{
			Hash ht;
			size_t hashi = ht(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return NULL;
		}
		bool Erase(const K& key)
		{
			Hash ht;

			size_t hashi = ht(key) % _tables.size();
			Node* cur = _tables[hashi];
			Node* prev = nullptr;
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if (prev == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					return true;
				}
				prev = cur;
				cur = cur->_next;

			}
			return false;
		}
		void Some()
		{
			size_t bucketsize = 0;
			size_t maxbucketlen = 0;
			size_t sumbucketlen = 0;
			double averagebucketlen = 0;
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				if (cur == nullptr)
				{
					++bucketsize;
				}
				size_t bucketlen = 0;
				while (cur)
				{
					++bucketlen;
					cur = cur->_next;
				}
				sumbucketlen += bucketlen;
				if (bucketlen > maxbucketlen)
				{
					maxbucketlen = bucketlen;
				}
				

			}
			averagebucketlen = (double)sumbucketlen / (double)bucketsize;
			printf("bucketsize:%d\n", bucketsize);
			printf("maxbucketlen:%d\n", maxbucketlen);
			printf("averagebucketlen:%lf\n", averagebucketlen);
			
		}
		
	private:
		vector<Node*> _tables;
		size_t _n = 0;

	};

三、unordered_map和unordered_set封装哈希表模拟实现

1.UnOrdered_map.h

#include"HashTable.h"

namespace goat
{
	template<class K,class V, class Hash = Hash_Bucket::Hashfunc<K>>
	class unordered_map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K,V>& kv)
			{
				return kv.first;
			}

		};
	public:
		typedef typename Hash_Bucket::HashTable<K, pair<const K, V>, MapKeyOfT , Hash>::iterator iterator;


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

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

	private:

		Hash_Bucket::HashTable<K, pair<const K,V> ,MapKeyOfT ,Hash> _mp;
	};
}

2.UnOrdered_set.h

#include"HashTable.h"

namespace goat
{
	template<class K ,class Hash = Hash_Bucket::Hashfunc<K>>
	class unordered_set
	{

		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}

		};
	public:
		typedef typename Hash_Bucket::HashTable<K, K, SetKeyOfT, Hash>::const_iterator iterator;
		typedef typename Hash_Bucket::HashTable<K, K, SetKeyOfT, Hash>::const_iterator const_iterator;

		/*iterator begin() 
		{
			return _st.begin();
		}
		iterator end() 
		{
			return _st.end();
		}*/

		const_iterator begin() const
		{
			return _st.begin();
		}
		const_iterator end() const
		{
			return _st.end();
		}

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

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


		//返回值pair要实现operator[]再去修改
		pair<iterator, bool> insert(const K& key)
		{
			//return _st.Insert(key);//返回普通迭代器,但是这里insert接受的是const迭代器
								   //正常解决方法1:支持const迭代器转化成普通迭代器,方法二:下一层Insert使用节点的指针返回(指针会产生隐式类型转换为迭代器
								   //但是这里unordered迭代器有三个值不能使用方法二
								   //iterator(cur , this ,hashi)
			//这里用另外一种简单的方法
			auto ret = _st.Insert(key);
			return pair<iterator, bool>(iterator(ret.first._node, ret.first._pht, ret.first._hashi), ret.second);


			
		}

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

3.HashTable.h

#include<iostream>
#include<vector>
#include<utility>
#include<list>
#include<set>

using namespace std;

namespace Hash_Bucket
{
	template<class T>
	struct HashNode
	{

		HashNode<T>* _next;
		T _data;

		//库里面的unordered是实现插入顺序遍历的
		//要单独维护一个链表
		//HashNode<T>* _linknext;
		//HashNode<T>* _linkprev;
		
		HashNode(const T& data)
			:_data(data)
			,_next(nullptr)
		{}
	};
	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;
		}
	};
	//解决互相依赖方法一
	//前置声明(不用给缺省参数)
	//因为hashiterator和hashtable是互相依赖的关系

	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 __HashIterator<K, T,Ref,Ptr, KeyOfT, Hash> Self;

		Node* _node;
		const HashTable<K, T, KeyOfT, Hash>* _pht;
		//解决互相依赖方法二
		//实际上哈希迭代器需要的是:
		//vector<Node*>* _ptb;


		//1.记录当前桶的所在位置
		size_t _hashi;

		__HashIterator(Node* node,const HashTable<K, T, KeyOfT, Hash>* pht, size_t hashi)
			: _node(node)
			, _pht(pht)
			, _hashi(hashi)
		{}
		
		Self& operator++()
		{
			if (_node->_next)
			{
				//桶还有节点,就走下一个节点
				_node = _node->_next;
			}
			else
			{
				//当前桶已经走完了,需要下一个桶的开始
				//传个哈希表过来
				2.直接算出当前所在桶位置
				//KeyOfT kot;
				//Hash hf;
				//size_t hashi = hf(kot(_node->_data)) % _pht._tables.size();
				++_hashi;


				//_tables是私有成员变量
				//可以考虑友元
				while (_hashi < _pht->_tables.size())
				{
					if (_pht->_tables[_hashi])
					{
						_node = _pht->_tables[_hashi];
						break;
					}
					else
					{
						_hashi++;
					}
				}
				if (_hashi == _pht->_tables.size())
				{
					_node = nullptr;
				}
			}
			return *this;
		}
		Ref operator*()
		{
			return _node->_data;
		}
		Ptr operator->()
		{
			return &_node->_data;
		}
		bool operator!=(const Self& s)
		{
			return _node != s._node;

		}
	};
	//unordered_set -> HashTable<K,K>
	//unordered_map -> HashTable<K,pair<K,V>>
	template<class K,class T , class KeyOfT,class Hash = Hashfunc<K>>
	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()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				if (_tables[i])
				{
					return iterator(_tables[i] ,this , i);
				}
			}
			return end();
		}
		iterator end()
		{
			return iterator(nullptr, this, -1);
		}
		const_iterator begin() const //const修饰this-> HashTable<K, T, KeyOfT, Hash>* 
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				if (_tables[i])
				{
					return const_iterator(_tables[i], this, i);
				}
			}
			return end();
		}
		const_iterator end() const
		{
			return const_iterator(nullptr, this, -1);
		}



		HashTable()
		{
			_tables.resize(10);
		}
		~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;
			/*if (Find(kot(data)))
			{
				return false;
			}*/
			iterator it = Find(kot(data));
			if(it != end())
			{
				return make_pair(it, false);
			}
			
			Hash ht;
			
			//扩容用开放地址法的扩容方法会产生大量的资源浪费
			//可以考虑直接把旧表的数据挪动下来
			if (_n == _tables.size())
			{
				vector<Node*> newtable;
				newtable.resize(_tables.size() * 2, nullptr);
				//遍历旧表
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						//挪动旧表的数据映射到新表
						Node* next = cur->_next;
						size_t hashi = ht(kot(cur->_data)) % newtable.size();
						cur->_next = newtable[i];
						newtable[i] = cur;

						//利用cur把节点挪动到新表
						//cur利用next返回到旧表以此循环
						cur = next;

						
					}
					_tables[i] = nullptr;
					//旧表这个桶数据已经被挪动完,记得制空。
					//以防后面析构有问题
				}
				_tables.swap(newtable);

			}
			
			//先算出数据要存入哪个桶
			size_t hashi = ht(kot(data)) % _tables.size();
			//头插
			Node* newnode = new Node(data);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;
			return make_pair(iterator(newnode,this,hashi),true);
		}
		iterator Find(const K& key)
		{
			Hash ht;
			KeyOfT kot;

			size_t hashi = ht(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return iterator(cur , this ,hashi);
				}
				cur = cur->_next;
			}
			return end();
		}
		bool Erase(const K& key)
		{
			Hash ht;
			KeyOfT kot;

			size_t hashi = ht(key) % _tables.size();
			Node* cur = _tables[hashi];
			Node* prev = nullptr;
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					if (prev == nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					return true;
				}
				prev = cur;
				cur = cur->_next;

			}
			return false;
		}
		void Some()
		{
			size_t bucketsize = 0;
			size_t maxbucketlen = 0;
			size_t sumbucketlen = 0;
			double averagebucketlen = 0;
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				if (cur == nullptr)
				{
					++bucketsize;
				}
				size_t bucketlen = 0;
				while (cur)
				{
					++bucketlen;
					cur = cur->_next;
				}
				sumbucketlen += bucketlen;
				if (bucketlen > maxbucketlen)
				{
					maxbucketlen = bucketlen;
				}
				

			}
			averagebucketlen = (double)sumbucketlen / (double)bucketsize;
			printf("bucketsize:%d\n", bucketsize);
			printf("maxbucketlen:%d\n", maxbucketlen);
			printf("averagebucketlen:%lf\n", averagebucketlen);
			
		}
		
	private:
		vector<Node*> _tables;
		size_t _n = 0;

	};
	

	
}

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

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

相关文章

站在ESG“20+”新起点上,看中国ESG先锋探索力量

全链减碳、建设绿色工厂、打造零碳产品、守护生物多样性、向受灾群众捐助……不知你是否察觉&#xff0c;自“双碳”目标提出以来&#xff0c;一股“可持续发展热潮”正覆盖各行各业&#xff0c;并且渗透到我们衣食住行的方方面面。在资本市场&#xff0c;ESG投资热潮更是席卷全…

VMware虚拟机-设置系统网络IP、快照、克隆

1.设置网络IP 1.点击右上角开关按钮-》有线 已连接-》有线设置 2.手动修改ip 3.重启或者把开关重新关闭开启 2.快照设置 快照介绍&#xff1a; 通过快照可快速保存虚拟机当前的状态&#xff0c;后续可以使用虚拟机还原到某个快照的状态。 1.添加快照(需要先关闭虚拟机) 2.在…

数据结构(六)图

2024年5月26日一稿(王道P220) 6.1 图的基本概念 6.1.1 图的定义 6.2 图的存储及基本操作 6.2.1邻接矩阵法 6.2.2 邻接表

【开源】大学生竞赛管理系统 JAVA+Vue+SpringBoot+MySQL

目录 一、系统介绍 学生管理模块 教师管理模块 竞赛信息模块 竞赛报名模块 二、系统截图 三、核心代码 一、系统介绍 基于Vue.js和SpringBoot的大学生竞赛管理系统&#xff0c;分为管理后台和用户网页端&#xff0c;可以给管理员、学生和教师角色使用&#xff0c;包括学…

基于双差分值和RR间隔处理的心电信号R峰检测算法(MATLAB R2018A)

心电信号中的R峰是确定心率和节律、以及检测其它波形特征点&#xff08;图1A&#xff09;的基础。R峰的准确检测是心率变异性分析、心拍分割和心律失常识别重要的处理步骤。 现有的心电信号R峰检测方法主要为基于规则的决策法和基于深度学习的检测方法。基于规则的决策法通常对…

Liunx学习随笔

Linux学习随笔 一.前期准备1.安装Vmware Workstation软件2.下载linux镜像3.安装操作系统 夕阳无限好&#xff0c;只是近黄昏&#xff0c;时隔一年&#xff0c;重新提笔 没有比脚更远的路&#xff0c;没有比人更高的山 一.前期准备 1.安装Vmware Workstation软件 下载地址&am…

香橙派AIpro(OrangePi AIPro)开发板初测评

开发板简介 最近&#xff0c;我拿到手一款Orange Pi AI Pro 开发板&#xff0c;它是香橙派联合华为精心打造的高性能AI 开发板&#xff0c;最早发布于2023年12月&#xff0c;其搭载了昇腾AI 处理器&#xff0c;可提供8TOPS INT8 的计算能力&#xff0c;内存提供了8GB 和16GB两…

vue数据持久化仓库

本文章是一篇记录实用性vue数据持久化仓的使用&#xff01; 首先在src中创建store文件夹&#xff0c;并创建一个根据本页面相关的名称&#xff0c; 在终端导入&#xff1a;npm i pinia 和 npm i pinia-plugin-persistedstate 接下来引入代码&#xff1a; import { defineSt…

Tensorflow 2.0 安装过程

第一步&#xff1a;进入国内清华软件网站 anaconda | 镜像站使用帮助 | 清华大学开源软件镜像站 | Tsinghua Open Source Mirroranaconda 使用帮助 | 镜像站使用帮助 | 清华大学开源软件镜像站&#xff0c;致力于为国内和校内用户提供高质量的开源软件镜像、Linux 镜像源服务&…

【数据库基础-mysql详解之索引的魅力(N叉树)】

索引的魅力目录 &#x1f308;索引的概念&#x1f308;使用场景&#x1f308;索引的使用&#x1f31e;&#x1f31e;&#x1f31e;查看MySQL中的默认索引&#x1f31e;&#x1f31e;&#x1f31e;创建索引&#x1f31e;&#x1f31e;&#x1f31e;删除索引 站在索引背后的那个男…

基于CNN卷积神经网络的金融数据预测matlab仿真,对比BP,RBF,LSTM

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1 反向传播网络&#xff08;BP&#xff0c;多层感知器MLP&#xff09; 4.2 径向基函数网络&#xff08;RBF&#xff09; 4.3 卷积神经网络&#xff08;CNN&#xff09; 4.4 长短期记忆网…

FPGA的VGA显示

文章目录 一显示自定义字符1VGA介绍2准备字符数据3代码及实现的效果 二显示彩色条纹1实现代码及效果三显示彩色图片1准备图片数据2产生ROM IP核将生成的mif文件保存在ROM中3Pll ip核4更改分辨率5代码及实现效果 一显示自定义字符 1VGA介绍 本文基于DE2-115&#xff0c;如果有…

springboot基础篇(快速入门+要点总结)

目录 一、SpringBoot简介 二、创建SpringBoot&#xff08;通过Idea脚手架搭建项目&#xff09; 三、properties配置文件 properties 配置文件说明 ①. properties 基本语法 ②. 读取配置⽂件 ③. properties 缺点 2. yml 配置⽂件说明 ①. yml 基本语法 ②. yml 使用进…

python 面对对象 类 魔法方法

魔法方法 一、__init__ 构造函数&#xff0c;可以理解为初始化 触发条件&#xff1a;在实例化的时候就会触发 class People():def __init__(self, name):print(init被执行)self.name namedef eat(self):print(f{self.name}要吃饭)a People(张三) a.eat() # in…

数据结构(三)循环链表

文章目录 一、循环链表&#xff08;一&#xff09;概念&#xff08;二&#xff09;示意图&#xff08;三&#xff09;操作1. 创建循环链表&#xff08;1&#xff09;函数声明&#xff08;2&#xff09;注意点&#xff08;3&#xff09;代码实现 2. 插入&#xff08;头插&#x…

Redis数据类型(上篇)

前提&#xff1a;&#xff08;key代表键&#xff09; Redis常用的命令 命令作用keys *查看当前库所有的keyexists key判断某个key是否存在type key查看key是什么类型del key 删除指定的keyunlink key非阻塞删除&#xff0c;仅仅将keys从keyspace元数据中删除&#xff0c;真正的…

【机器学习】模型、算法与数据—机器学习三要素

探索机器学习三要素&#xff1a;模型、算法与数据的交融之旅 一、模型&#xff1a;构建机器学习的基石二、算法&#xff1a;驱动模型学习的引擎三、数据&#xff1a;驱动机器学习的动力源泉四、代码实例&#xff1a;展示三要素的交融与碰撞 在数字时代的浪潮中&#xff0c;机器…

C++模板——函数模板和类模板

目录 泛型编程 函数模板 函数模板概念 函数模板的定义和语法 函数模板的工作原理 函数模板的实例化 隐式实例化 显示实例化 函数模板的匹配原则 类模板 类模板的定义格式 类模板的实例化 泛型编程 什么是泛型编程&#xff1f; 泛型编程&#xff08;Generic Pr…

具身人工智能:人工智能机器人如何感知世界

什么是具身人工智能 虽然近年来机器人在智能城市、工厂和家庭中大量出现,但我们大部分时间都在与由传统手工算法控制的机器人互动。这些机器人的目标很狭隘,很少从周围环境中学习。相比之下,能够与物理环境互动并从中学习的人工智能 (AI) 代理(机器人、虚拟助手或其他智能系…

HCIP-Datacom-ARST自选题库_02_网络安全【道题】

一、单选题 1.关于网络安全性优化的内容&#xff0c;下列哪个选项是错误的? 管理安全 边界安全 访问控制 日志管理 2.如图所示&#xff0c;网络管理员为了抵御DHcP Server仿冒者攻击&#xff0c;在交换机上部署了DHcp snoping功能&#xff0c;那么以下哪一个接口应该被设…