【C++ —— 哈希】哈希原理 | 哈希表的模拟实现 | 模拟实现封装unordered_map和unordered_set


前言

本文参考文档: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 << 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值