当前位置: 首页 > news >正文

【 C++ 】开散列哈希桶的模拟实现

目录

1、框架

2、构建仿函数把数据类型转为整型并特化

3、哈希桶的插入

4、哈希桶的查找

5、哈希桶的删除

6、源码链接


1、框架

根据我们先前对开散列哈希桶的了解,得知其根本就是一个指针数组,数组里每一个位置都是一个链表指针,因此我们要单独封装一个链表结构的类,以此来告知我们哈希表类的每个位置为链表指针结构。

namespace Bucket
{
    //结点类
	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 K, class V, class HashFunc = DefaultHash<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	public:
		//相关功能的实现……
	private:
		//指针数组
		vector<Node*> _tables;
		size_t _n = 0;//记录有效数据的个数
	};
}

2、构建仿函数把数据类型转为整型并特化

此步操作的方法和闭散列哈希表所实现的步骤一致,目的都是为了能够让后续操作中传入的所有数据类型转换为整型数据,以此方便后续建立映射关系,直接看代码:

//利用仿函数将数据类型转换为整型
template<class K>
struct DefaultHash
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
//模板的特化
template<>
struct DefaultHash<string>
{
	size_t operator()(const string& key)
	{
		//BKDR哈希算法
		size_t hash = 0;
		for (auto ch : key)
		{
			hash = hash * 131 + ch;//把所有字符的ascii码值累计加起来
		}
		return hash;
	}
};

3、哈希桶的插入

哈希桶的插入主要分为这几大步骤

  1. 去除冗余
  2. 扩容操作
  3. 头插操作

下面开始具体展开说明:

1、去除冗余:

  1. 复用Find查找函数,去帮助我们查找插入的值是否存在
  2. 若存在,直接返回false
  3. 不存在,再进行后续的插入操作

2、扩容操作:

针对哈希桶的扩容,我们有两种方法进行扩容,法一和哈希表扩容的方法一致

法一:

  1. 当负载因子==1时扩容
  2. 扩容后重新建立映射关系
  3. 创建一个新的哈希桶对象,扩容到newSize的大小
  4. 遍历旧表,把旧表每个存在的元素插入到新表,此步骤让新表自动完成映射关系,无序手动构建
  5. 利用swap函数把新表交换到旧表那,此时的旧表就是已经扩好容且建立号映射关系的哈希表

此扩容的方法会存在一个问题:释放旧表会出错!!!

  • 当我们把旧表的元素映射插入到新表的时候,最后都要释放旧表,按照先前哈希表的释放,我们无需做任何处理,但是现在定义的结构是vector,是自定义类型,会自动调用析构函数进行释放,vector存储的数据是Node*,Node*是内置类型,不会自动释放,最后会出现表我们释放了,但是链表结构的数据还没释放,因此,我们还需借助手写析构函数来帮助我们释放。
//析构函数
~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;//释放后置空
	}
}

此扩容的方法可以进行优化,见法二。

法二:

  • 这里我们没必要再创建新的节点,相反把扩容前的节点拿过来重新映射到新表中,大体逻辑和前面差不太多,只是这里不需要再创建新节点。直接利用原链表里的节点。

3、头插操作:

  1. 借助仿函数找到映射的位置(头插的位置)
  2. 进行头插的操作
  3. 更新有效数据个数

//插入
bool Insert(const pair<K, V>& kv)
{
	//1、去除冗余
	if (Find(kv.first))
	{
		return false;
	}
	//构建仿函数,把数据类型转为整型,便于后续建立映射关系
	HashFunc hf;
	//2、负载因子 == 1就扩容
	if (_tables.size() == _n)
	{
		/*法一
		size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
		HashTable<K, V> newHT;//
		newHT._tables.resize(newSize, nullptr);
		//遍历旧表,把旧表的数据重新映射到新表
		for (size_t i = 0; i < _tables.size(); i++)
		{
			Node* cur = _tables[i];
			while (cur)//如果cur不为空,就插入
			{
				newHT.Insert(cur->_kv);
				cur = cur->_next;
			}
		}
		newHT._tables.swap(_tables);*/

		//法二:
		size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
		vector<Node*> newTable;
		newTable.resize(newSize, nullptr);
		for (size_t i = 0; i < _tables.size(); i++)//遍历旧表
		{
			Node* cur = _tables[i];
			while (cur)
			{
				Node* next = cur->_next;
				size_t hashi = hf(cur->_kv.first) % newSize;//确认映射到新表的位置
				//头插
				cur->_next = newTable[hashi];
				newTable[hashi] = cur;
				cur = next;
			}
			_tables[i] = nullptr;
		}
		newTable.swap(_tables);
	}
	//3、头插
	//找到对应的映射位置
	size_t hashi = hf(kv.first);
	hashi %= _tables.size();
	//头插到对应的桶即可
	Node* newnode = new Node(kv);
	newnode->_next = _tables[hashi];
	_tables[hashi] = newnode;
	++_n;
	return true;
}

4、哈希桶的查找

遵循下列规则:

  1. 先去判断表的大小是否为0,为0直接返回nullptr
  2. 利用仿函数去找到映射的位置
  3. 在此位置往后的一串链表中进行遍历查找,找到了,返回节点指针
  4. 遍历结束,说明没找到,返回nullptr
//查找
Node* Find(const K& key)
{
	//防止后续除0错误
	if (_tables.size() == 0)
	{
		return nullptr;
	}
	//构建仿函数,把数据类型转为整型,便于后续建立映射关系
	HashFunc hf;
	//找到对应的映射下标位置
	size_t hashi = hf(key);
	//size_t hashi = HashFunc()(key);//匿名对象
	hashi %= _tables.size();
	Node* cur = _tables[hashi];
	//在此位置的链表中进行遍历查找
	while (cur)
	{
		if (cur->_kv.first == key)
		{
			//找到了
			return cur;
		}
		cur = cur->_next;
	}
	//遍历结束,没有找到,返回nullptr
	return nullptr;
}

5、哈希桶的删除

哈希桶的删除遵循这两大思路:

  1. 找到删除的值对应哈希桶的下标映射位置
  2. 按照单链表删除节点的方法对该值进行删除
//删除
bool Erase(const K& key)
{
	//防止后续除0错误
	if (_tables.size() == 0)
	{
		return false;
	}
	//构建仿函数,把数据类型转为整型,便于后续建立映射关系
	HashFunc hf;
	//找到key所对应的哈希桶的位置
	size_t hashi = hf(key);
	hashi %= _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;
}

法二:替换法删除

  • 上述的删除是实打实的删除,建立prev作为cur的前指针,以此利用prev和cur->next来建立关系从而删除cur节点,但是我们可以不用借助prev指针,就利用先前二叉搜索树的替换法删除的思想来解决。图示:

  1. 当我要删除88时,把节点88的下一个节点的值78替换掉88,随后删除节点78,再建立链表的关系即可。 
  2. 当删除的是尾节点98时,直接和头节点进行交换,删除头节点,并建立链表关系。

这里就不代码演示了,因为整体的成本看还是法一更方便理解些。


6、源码链接

开散列哈希桶的模拟实现

相关文章:

  • 阿里云数据库(RDS)查看空间使用情况
  • 【C++编程语言】之 文件操作
  • 人生模式 - 如何跟潜意识对话
  • ubuntu18.04安装redis
  • 02 LaTeX文字实战应用
  • Flash:Flash动画设计软件界面的简介、Flash AS 3.0代码编程入门教程之详细攻略
  • C语言进阶——自定义类型
  • 微信公众号网课查题系统
  • golang学习笔记系列之函数
  • VJ_Dressing_思维
  • 关于我的vsc不能远程debug这件事
  • [English]英语积累本
  • java-php-python-ssm爱馨敬老院网站计算机毕业设计
  • 9.24 Day59---网络相关知识
  • [leetcode top100] 0924 找到数组中消失的数,合并二叉树,比特位计数,汉明距离
  • [译]如何构建服务器端web组件,为何要构建?
  • 【391天】每日项目总结系列128(2018.03.03)
  • 【Leetcode】104. 二叉树的最大深度
  • 【每日笔记】【Go学习笔记】2019-01-10 codis proxy处理流程
  • 2018天猫双11|这就是阿里云!不止有新技术,更有温暖的社会力量
  • Docker下部署自己的LNMP工作环境
  • ES6 ...操作符
  • Flex布局到底解决了什么问题
  • java B2B2C 源码多租户电子商城系统-Kafka基本使用介绍
  • Java新版本的开发已正式进入轨道,版本号18.3
  • passportjs 源码分析
  • Quartz初级教程
  • React+TypeScript入门
  • Spring声明式事务管理之一:五大属性分析
  • TiDB 源码阅读系列文章(十)Chunk 和执行框架简介
  • webpack项目中使用grunt监听文件变动自动打包编译
  • 欢迎参加第二届中国游戏开发者大会
  • 强力优化Rancher k8s中国区的使用体验
  • 三栏布局总结
  • 王永庆:技术创新改变教育未来
  • - 语言经验 - 《c++的高性能内存管理库tcmalloc和jemalloc》
  • media数据库操作,可以进行增删改查,实现回收站,隐私照片功能 SharedPreferences存储地址:
  • 微龛半导体获数千万Pre-A轮融资,投资方为国中创投 ...
  • ​​​​​​​​​​​​​​汽车网络信息安全分析方法论
  • ​一些不规范的GTID使用场景
  • #{} 和 ${}区别
  • #我与Java虚拟机的故事#连载13:有这本书就够了
  • (arch)linux 转换文件编码格式
  • (C++)八皇后问题
  • (Matalb回归预测)PSO-BP粒子群算法优化BP神经网络的多维回归预测
  • (Oracle)SQL优化技巧(一):分页查询
  • (附源码)springboot社区居家养老互助服务管理平台 毕业设计 062027
  • (九十四)函数和二维数组
  • (免费分享)基于springboot,vue疗养中心管理系统
  • (转)一些感悟
  • (转载)Google Chrome调试JS
  • (自用)learnOpenGL学习总结-高级OpenGL-抗锯齿
  • .[hudsonL@cock.li].mkp勒索病毒数据怎么处理|数据解密恢复
  • .mat 文件的加载与创建 矩阵变图像? ∈ Matlab 使用笔记
  • .NET CORE Aws S3 使用