哈希——开散列

2023-05-16

哈希——开散列

  • 开散列概念
  • 开散列的简单实现
    • HashFunc
    • 开散列的构成
    • 插入
      • 去重
      • 扩容
      • 插入
    • 测试

开散列概念

上一篇博客中介绍了解决哈希冲突的一种方法:闭散列。但是闭散列中不管是线性探测还是二次探测,解决哈希冲突问题都不够好,一旦数据冲突过多还是会造成堆积问题。那么有什么更好一点的方法么?于是又出现了开散列这种新的方法。开散列又叫拉链法或者哈希桶。他先将数据按照哈希函数计算得到哈希地址,如果出现相同的哈希地址的数据,那么就用一个单链表把数据连起来,各链表的头节点存储在哈希表中。这样就避免了闭散列中一旦出现哈希冲突就去侵占其他位置从而造成堆积的情况。

开散列的简单实现

HashFunc

由于类似于字符串等这些不能直接使用除留余数法进行哈希函数计算哈希地址的情况,这里和闭散列里面情况类似,需要设计一些哈希方法来进行转换,从而能够进行这里的取模运算。这里还是以字符串为例,使用模板的特化来实现。

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


	template<>
	struct Hash<string>
	{
		size_t operator()(const string& s)
		{
			size_t value = 0;
			for (auto ch : s)
			{
				value *= 31;
				value += ch;
			}
			return value;
		}
	};

开散列的构成

开散列的结构是由链表构成的,因此需要先定义一个节点的数据结构,哈希表仍然是使用vector数组,但是其中存放的元素是各个链表。

	template<class K, class V>
	struct HashNode
	{
		K _data;
		HashNode<K, V>* _next;
	};


	template<class K,class V, class HashFunc = Hash<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;
	private:
		vector<Node*> _tables;//数组中存放的是节点的指针
		size_t _n;
	};

插入

插入和闭散列需要注意的方法类似,插入可以分为以下几步:判断是否重复(可以使用Find函数),判断负载因子是否为1从而考虑是否需要扩容,找到哈希地址头插插入入新的节点。
下面来分别介绍这些步骤:

去重

去重这里是通过使用Find函数来判断是否能够找到该元素,Find函数实现如下:这里是通过先找到哈希地址所在的位置,然后遍历该地址上面的链表,从来进行对比判断。

		Node* Find(const K& key)
		{
			if (_tables.size() == 0)
			{
				return nullptr;
			}
			HashFunc hf;
			size_t index = hf(key) % _tables.size();
			Node* cur = _tables[index];
			while (cur)
			{
				if (cur->_data.first == key)
				{
					return cur;
				}
				else
				{
					cur = cur->_next;
				}
			}
			return nullptr;
		}

扩容

这里的扩容是先创建一个新的vector数组,给他设置为新的大小,然后通过遍历原哈希表,将哈希表每个地址的元素,以及每个链表上面所挂的数据,一一重新映射到新的vector数组中,最后进行交换即可

			if (_tables.size() == _n)//载荷因子为1的时候需要进行扩容
			{
				size_t newCapacity = _tables.size() == 0 ? 10 : _tables.size() * 2;
				vector<Node*> newTables;
				newTables.resize(newCapacity);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						size_t index = hf(cur->_data.first) % newTables.size();
						Node* next = cur->_next;
						//头插
						cur->_next = newTables[index];
						newTables[index] = cur;
						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newTables);
			}

插入

最后插入的过程比较简单,直接通过哈希函数计算出该数据的哈希地址,然后通过头插的方式,将其插入链表即可。代码实现如下:

			size_t index = hf(kv.first) % _tables.size();
			Node* newnode = new Node(kv);
			newnode->_next = _tables[index];
			_tables[index] = newnode;
			_n++;

最终一个完整的插入和查找代码就完成了,示例如下:

		Node* Find(const K& key)
		{
			if (_tables.size() == 0)
			{
				return nullptr;
			}
			HashFunc hf;
			size_t index = hf(key) % _tables.size();
			Node* cur = _tables[index];
			while (cur)
			{
				if (cur->_data.first == key)
				{
					return cur;
				}
				else
				{
					cur = cur->_next;
				}
			}
			return nullptr;
		}



		bool Insert(const pair<K, V>& kv)
		{
			Node* ret = Find(kv.first);//判断是否已经存在重复值
			if (ret)
			{
				return false;
			}
			HashFunc hf;
			if (_tables.size() == _n)//载荷因子为1的时候需要进行扩容
			{
				size_t newCapacity = _tables.size() == 0 ? 10 : _tables.size() * 2;
				vector<Node*> newTables;
				newTables.resize(newCapacity);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						size_t index = hf(cur->_data.first) % newTables.size();
						Node* next = cur->_next;
						//头插
						cur->_next = newTables[index];
						newTables[index] = cur;
						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newTables);
			}
			size_t index = hf(kv.first) % _tables.size();
			Node* newnode = new Node(kv);
			newnode->_next = _tables[index];
			_tables[index] = newnode;
			_n++;
			return true;
		}

测试

最后通过一段代码来测试一下这个开散列哈希表的数据存储情况:

	void TestHash()
	{
		HashTable<int, int> test;
		int arr[] = { 2,12,22,32,42,52,62,72 ,23,13,45,55 };
		for (auto e : arr)
		{
			test.Insert(make_pair(e, e));
		}

	}

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

哈希——开散列 的相关文章

随机推荐

  • Android.mk中LOCAL_MODULE_CLASS对LOCAL_MODULE_PATH 的影响

    LOCAL MODULE CLASS用于制定LOCAL MODULE PATH的路径所在 如果在Android mk没有直接明确LOCAL MODULE PATH 的话 xff0c 需要通过以下规则来自动生成base rules mk xf
  • PRODUCT_COPY_FILES的深入理解,为何不能在Android.mk使用

    PRODUCT COPY FILES本质是和定义产品的AndroidProducts mk xff08 get all product makefiles来获取系统中所有AndroidProducts mk 文件路径 xff09 联系在一起
  • linux驱动开发经验逐步积累2

    注 xff1a 笔记多少会有问题 xff0c 多多包涵 只是作为一个记录而已 1 cdev add的核心思想 cdev add允许添加一个字符设备到内核 xff0c 其核心是kobj map xff0c 也可以添加一个字符设备集合 xff0
  • 记录下在csdn那些年里所使用的博客座右铭

    xfeff xfeff 2016 xff0c 认认真真做事 xff0c 脚踏实地生活 路漫漫 xff0c 意不变 xff0c 求静 xff0c 求心 xff0c 求进 2017 xff0c 重新开始 xff0c 从心开始 xff0c 从家开
  • 多寄存器寻址指令ldmia/ldmib和ARM存储器访问指令——多寄存器存取

    多寄存器和堆栈寻址的用法 xff1a 多寄存器寻址 xff1a LDMIA xff0c LDMIB xff0c STMIA xff0c STMIB xff0c LDMDA xff0c LDMDB xff0c STMDA xff0c STMD
  • 使用CCS5.1导入的3.3工程编译错误lib/subdir_vars.mk:11: *** missing separator. Stop.

    D Program Files CCS5 1 ccsv5 utils bin gmake k all lib subdir vars mk 11 missing separator Stop TI方面说是CCS5 1的BUG xff0c 在
  • 写给我的2013

    前沿 xff1a 代码看的累了 xff0c 在新的一年终于可以找点时间来回忆我的2013 想着要写点什么 xff0c 可是又没有什么可以写 因为回忆无非就是夹杂着些许痛苦与欢乐 写给我的2013 家 生活 xff1a 2013年 xff0c
  • 写给我的2014——也写给我即将逝去的研究生生涯

    前言 xff1a 2014 1在写着代码的时写下了回忆 xff0c 2015 1在码着论文的时候开始写起消逝的2014 细细回忆 xff0c 真是又是那句老话 xff0c 时间过得真快 xff0c 1年过去了 xff1b 更快的是竟然都要毕
  • Oracle官网下载历史版本软件

    一 分享一个Oracle官网下载各种软件的网址 https edelivery oracle com osdc faces SoftwareDelivery 这个网址是Oracle官网专门下载软件的地址 xff0c 下载软件过程如下 xff
  • 技术盘点:消息中间件的过去、现在和未来

    作者介绍 xff1a 林清山 xff08 花名 xff1a 隆基 xff09 操作系统 数据库 中间件是基础软件的三驾马车 xff0c 而消息队列属于最经典的中间件之一 xff0c 已经有30多年的历史 其发展主要经历了以下几个阶段 xff
  • C语言小游戏——扫雷

    上次我们用C语言实现了一个三子棋的小游戏 xff0c 这次我们同样使用C语言来实现扫雷这个经典的小游戏 首先 xff0c 在开始编程之前还是先整理一下我们的编程思路 xff1a 一 菜单打印 xff1a 和上次实现三子棋的操作类型 xff0
  • 缺省参数讲解

    缺省参数 缺省参数定义缺省参数分类1 全缺省参数 xff1a 2 半缺省参数 xff1a 注意事项 缺省参数定义 缺省参数作为C 43 43 不同于C语言新增的一种语法功能 xff0c 他的作用是在声明或定义函数时为参数指定的一个默认值 x
  • Linux下的权限

    Linux下的权限 用户分类文件类型具体文件类型 基本权限root用户 xff1a 修改权限使用方法 xff1a 通过8进制数字更改权限 对于文件 xff0c 权限的意义读权限写权限运行权限 对于目录权限的意义 更改文件拥有者 所属组cho
  • 类和对象初识

    类和对象初识 类的由来类的定义类的特性封装访问限定符 类的定义方法声明和定义全部放在类体中声明放在 h文件中 xff0c 类的定义放在 cpp文件中类对象的大小 内存对齐规则 类的由来 在C语言中我们有自定义类型的struct xff0c
  • 类的默认成员函数——上

    类的默认成员函数 默认成员函数构造函数构造函数由来构造函数特征默认构造函数特征总结 xff1a 析构函数特征 拷贝构造默认拷贝构造总结 C 43 43 中如果一个类中什么成员都没有 xff0c 简称为为空类 空类中什么都没有吗 xff1f
  • 进程控制块

    进程控制模块 查看进程PCB内部构成标识符ppid 状态优先级查看优先级方式优先级确定原理调整优先级nice值范围 程序计数器内存指针上下文数据时间片上下文数据 I xff0f O状态信息记账信息 查看进程信息 进程 xff1a 加载到内存
  • 模拟实现stack/queue

    模拟实现stack queue stack大体框架接口函数实现 queue大体框架接口函数 stack 之前的博客中介绍了栈和队列的相关功能 xff0c 这里我们模拟实现一个栈和队列 大体框架 由于栈的特殊性 xff0c 栈不支持迭代器访问
  • 进程间通信——命名管道

    命名管道 命名管道定义命名管道创建命令行上创建程序内创建 命名管道间通信匿名管道和命名管道区别 命名管道定义 上一篇博客中介绍了匿名管道的用法以及他的特点 xff0c 但是它存在一定的限制 xff0c 例如他只能在两个具有公共祖先的进程间进
  • Altium Designer一些好用的系统设置

    AD软件系统设置 系统参数设置GeneralNavigationDesign InsightFile Types 原理图参数设置GeneralCross Overs位号自动增加设置原理图大小设置 Graphical Editing单一 39
  • 哈希——开散列

    哈希 开散列 开散列概念开散列的简单实现HashFunc开散列的构成插入去重扩容插入 测试 开散列概念 上一篇博客中介绍了解决哈希冲突的一种方法 xff1a 闭散列 但是闭散列中不管是线性探测还是二次探测 xff0c 解决哈希冲突问题都不够