哈希表实现-哈希桶法

哈希桶方法

由于直接定值法实现哈希表有着明显的弊端——如果多个节点的hash值相同会往后堆积,所以衍生出哈希桶方法

我们的哈希表设置成一个结点指针数组,每个哈希值对应的是一串链表,形状就像一个一个的桶我们就会把hash值相同的节点放到一起,不会出现往后堆积的问题而且还能一定程度解决频繁扩容的问题

节点定义

由于哈希桶是一个一个的链表,所以节点我们需要一个_next指针来形成链表

template<class Val_type>
struct HashNode
{
	HashNode<Val_type>* _next;
	Val_type _data;

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

成员/成员函数

 构造函数:这里默认先开十个节点的数组

析构函数:我们需要将每个桶中的每个节点都释放掉,所以需要一个一个的遍历,一个节点接着一个节点的释放就可以了(最后滞空) 

成员:需要一个vector<Node*> 用来控制整个哈希表用一个_n 来维护整个哈希表节点的个数(插入节点++ 删除节点--) 

template<class Key_type, class Val_type, class KeyOfT, class Hash>
class __Hashiterator
{
    typedef HashNode<Val_type> Node;
public:
	HashTable()
	{
		_table.resize(10,nullptr);//开十个空间
		_n = 0;
	}

	~HashTable()
	{
		for (size_t i = 0; i < _table.size(); i++)
		{
			Node* cur = _table[i];
			while (cur)
			{
				Node* next = cur->next;//标记下一个节点
				delete cur;//直接delete

				cur = next;
			}
			_table[i] = nullptr;
		}
	}
private:
	vector<Node*> _table;
	size_t _n;
};

插入

 首先看看能不能在表中找到该元素,如果表中有要插入的元素,不再进行插入!

Hash仿函数:用来计算哈希值 

KeyOfT仿函数:用来取出Val_type中的key值 

扩容:如果节点数与桶的数量相同则需要扩容

①开一个新表,容量扩成原来的二倍

②遍历旧表,一个一个的将节点插入新表中的对应桶中

③将新表与旧表交换(这样 一但出函数作用域,就表就会销毁,新表代替掉旧表) 

插入节点:这里直接头插就可以了(方便快捷,时间O(1))。 

 ①:

 

bool insert(const Val_type& val)
{
	KeyOfT kot;
	if (Find(kot(val)))
		return false;

	Hash hs;

	if (_n == _table.size())//扩容
	{
		vector<Node*>newTable(_table.size() * 2, nullptr);
		for (size_t i = 0; i < _table.size(); i++)
		{
			Node* cur = _table[i];
			//转移hash桶
			while (cur)
			{
				Node* next = cur->_next;

				size_t hashi = hs(kot(cur->_data)) % _table.size();
				cur->_next = newTable[hashi];
				newTable[hashi] = cur;
				cur = next;
			}
			_table[i] = nullptr;
		}

		_table.swap(newTable);
	}

	size_t hashi = hs(kot(val)) % _table.size();
	Node* newnode = new Node(val);//开新节点
	//头插
	newnode->_next = _table[hashi];
	_table[hashi] = newnode;

	++_n;
	return true;
}

搜索

确定要找的节点的哈希值

到该桶里找到该节点

返回节点

Node* Find(const Val_type& val)
{
	KeyOfT kot;

	Hash hs;
	size_t hashi = hs(kot(val)) % _table.size();

	Node* cur = _table[hashi];
	while (cur)
	{
		if (kot(cur->_data) == val)
			return cur;

		cur = cur->_next;
	}

	return false;
}

删除

找到对应的桶

在桶中找到该节点

删除该节点 

bool erase(const Val_type& val)
{
	KeyOfT kot;

	Hash hs;
	size_t hashi = hs(kot(val)) % _table.size();

	Node* prev = nullptr;
	Node* cur = _table[hashi];
	while (cur)//找到该节点
	{
		if (kot(cur->_data) == val)//如果找到
		{
			if (prev)//桶中有多个节点
			{
				prev->_next = cur->_next;
			}
			else//要删除的节点是桶中的第一个节点
			{
				_table[hashi] = cur->_next;
			}

			delete cur;
			cur = nullptr;

			--_n;

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

	return false;
}

迭代器

 ①我们的迭代器需要知道是哪一个hash表中的迭代器,所以需要一个_ht 来记录指向我们迭代的哈希表

需要维护一个节点指针

template<class Key_type, class Val_type, class KeyOfT, class Hash>
class __Hashiterator
{
	typedef HashNode<Val_type> Node;
	typedef __Hashiterator<Key_type,Val_type,KeyOfT,Hash> self;

	Node* _node;
	HashTable<Key_type, Val_type, KeyOfT, Hash> _ht;

	__Hashiterator(Node* node, HashTable<Key_type, Val_type, KeyOfT, Hash>* ht)
		:_node(node)
		,_ht(ht)
	{}
};

重载: 这里主要分析一下operator++

分情况:如果下一个节点是空就需要去下一个右节点的桶中找节点

              如果下一个节点不是空,转移到下一个节点就可以了

Val_type& operator*()
{
	return _node->_data;
}

self& operator++()
{
	if (_node->next)//当前位置后边还有节点
	{
		return _node = _node->_next;
	}
	else//换桶
	{
		KeyOfT kot;
		Hash hs;
		size_t hashi = hs(kot(_node->_data)) % _ht->_table.size();//找下一个桶
		hashi++;
		while (hashi < _ht->_table.size())
		{
			if (_ht->_table[hashi])//当前桶里有节点,访问这个桶,没节点继续找到有节点的桶
			{
				_node = _ht->_table[hashi];
				break;
			}
		}
		if (hashi == _ht->_table.size())//后边没有桶了
		{
			_node = nullptr;
		}
	}

	return *this;
}

bool operator!=(const self& s)
{
	return _node != s._node;
}

bool operator== (const self& s)
{
	return _node == s._node;
}

 begin/end

如果容器没有begin函数和end函数,就不能支持C++11 中的范围for功能

begin只要找到第一个不为空的桶就可以了

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

	return end();//如果全部都是空桶
}

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

源码地址

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

宝塔怎么配置nginx

宝塔怎么配置nginx 1.找到nginx配置位置 2.修改nginx.conf文件 3.重启nginx

21岁的人生赚51W!拒绝捞男捞女,翻身也要爱惜身体!——早读(逆天打工人爬取热门微信文章解读)

身体是革命的本钱 引言Python 代码第一篇 卢克文工作室 捞女在今天的中国是怎样的存在第二篇 人民日报 来啦 新闻早班车要闻社会政策 结尾 我将我的健康视为生活的基石 不会为了短暂的成功而牺牲 我珍惜身体 知道健康是实现梦想的前提 引言 这里毕竟是一个程序员的代码学习平台…

基于SpringBoot实现各省距离Excel导出实战

目录 前言 一、列表及图表信息展示 1、数据过滤调整 2、信息列表及图表展示 3、Excel写入 二、界面可视化 1、Echarts图表和列表展示 2、城市详情和下载功能设计 三、成果展示 1、图表展示 2、部分城市数据分析 总结 前言 今天是五一黄金周假期第二天&#xff0c;不知…

Redis(Jedis和SpringBoot整合Redis)

文章目录 1.Jedis1.介绍2.环境配置1.创建maven项目2.pom.xml引入依赖3.新建一个包并创建一个文件 3.Jedis远程连接到Redis1.Redis放到服务器可以连接的前提条件2.为Redis设置密码1.编辑配置文件2.找到 requirepass3.设置密码为root4.重启Redis&#xff0c;在shutdown的时候报错…

R语言实战——中国职工平均工资的变化分析——相关与回归分析

链接: R语言学习—1—将数据框中某一列数据改成行名 R语言学习—2—安德鲁斯曲线分析时间序列数据 R语言学习—3—基本操作 R语言学习—4—数据矩阵及R表示 R语言的学习—5—多元数据直观表示 R语言学习—6—多元相关与回归分析 1、源数据 各行业平均工资变化 各地区平均工资…

常用算法介绍

1. 冒泡排序&#xff1a;冒泡排序是一种简单的排序算法&#xff0c;它的基本思想是比较相邻的两个元素&#xff0c;如果顺序错误就交换它们的位置&#xff0c;直到所有元素都按照升序排列。 2. 快速排序&#xff1a;快速排序是一种高效的排序算法&#xff0c;它的基本思想是选取…

内网端口转发与代理

思路&#xff1a;渗透的前提是双方能够建立通信。目前无法和win7建立通信&#xff0c;但是拿到了windows2003的权限&#xff0c;所以可以在Windows2003主机上面建立节点&#xff0c;作为跳板机去访问到内网。 目前状态&#xff1a;控制win2003&#xff08;IP&#xff1a;192.1…

基于JSP的人才公寓管理系统

目录 背景 技术简介 系统简介 界面预览 背景 随着互联网的广泛推广和应用&#xff0c;人才公寓管理系统在网络技术的推动下迅速进步。该系统的设计初衷是满足住户的实际需求&#xff0c;通过深入了解住户的期望&#xff0c;开发出高度定制化的人才公寓管理系统。利用互联网…

如何进行Go语言的性能测试和调优?

文章目录 开篇一、性能测试1. 使用标准库中的testing包2. 使用第三方工具 二、性能调优1. 优化算法和数据结构2. 减少不必要的内存分配和垃圾回收3. 并发和并行 结尾 开篇 Go语言以其出色的性能和简洁的语法受到了广大开发者的喜爱。然而&#xff0c;在实际开发中&#xff0c;…

39.乐理基础-拍号-认识音符

拍号是一个分数的形式&#xff0c;如下图篮色的圈圈里的东西&#xff0c;但它的实际意义和分数没什么关系&#xff0c;只是外观上是一个分数的形式 单独拿出拍号&#xff0c;如下图&#xff1a; 然后接下来只要搞懂什么是 Y分音符、一拍、小节就可以了。 音符&#xff1a; 控…

Java | Leetcode Java题解之第67题二进制求和

题目&#xff1a; 题解&#xff1a; class Solution {public String addBinary(String a, String b) {StringBuffer ans new StringBuffer();int n Math.max(a.length(), b.length()), carry 0;for (int i 0; i < n; i) {carry i < a.length() ? (a.charAt(a.leng…

特征提取(Feature Extraction)常见统计特征笔记(三)

统计特征是描述数据集中值的一组量&#xff0c;通常用于了解数据的分布、集中趋势和变异程度。常见的统计特征包括均值、中位数、众数、标准差、方差等。下面会详细解释每个统计特征&#xff0c;并给出相应的Python代码。 1、均值&#xff08;Mean&#xff09;&#xff1a;所有…

分布式存储 Ceph 的演进经验

从 2004 年到今天&#xff0c;Ceph 的存储后端一直都在演变&#xff0c;从最开始基于 B 树的 EBOFS 演变到今天的 BlueStore&#xff0c;存储后端已经变得非常成熟&#xff0c;新的存储系统不仅能够提供良好的性能&#xff0c;还有着优异的兼容性。我们在这篇文章中将要简单介绍…

华为eNSP小型园区网络配置(上)

→跟着大佬学习的b站直通车← 目标1&#xff1a;dhcp分配ip地址 目标2&#xff1a;内网用户访问www.yzy.com sw1 # vlan batch 10 # interface Ethernet0/0/1port link-type accessport default vlan 10 # interface Ethernet0/0/2port link-type trunkport trunk allow-pass…

【Linux】网络连接配置——nmcli工具配置连接增删改查实例

nmcli工具配置连接增删改查实例 &#xff08;一&#xff09;网络连接配置基本项目1.网络接口配置2.主机名配置3.DNS服务器配置 &#xff08;二&#xff09;网络连接配置文件&#xff08;三&#xff09;网络配置方法&#xff08;四&#xff09;nmcli工具配置连接管理1.增2.查3.改…

prometheus+grafana的安装与部署及优点

一、Prometheus 的优点 1、非常少的外部依赖&#xff0c;安装使用超简单&#xff1b; 2、已经有非常多的系统集成 例如&#xff1a;docker HAProxy Nginx JMX等等&#xff1b; 3、服务自动化发现&#xff1b; 4、直接集成到代码&#xff1b; 5、设计思想是按照分布式、微服…

GPT-3

论文&#xff1a;Language Models are Few-Shot Learners&#xff08;巨无霸OpenAI GPT3 2020&#xff09; 摘要 最近的工作表明&#xff0c;通过对大量文本进行预训练&#xff0c;然后对特定任务进行微调&#xff0c;在许多NLP任务和基准方面取得了实质性进展。虽然这种方法…

stm32单片机开发二、定时器-内部时钟中断和外部时钟中断、编码器

定时器本质就是一个计数器 案例&#xff1a;定时器定时中断 内部时钟中断 Timer_Init(); //定时中断初始化 /*** 函 数&#xff1a;定时中断初始化* 参 数&#xff1a;无* 返 回 值&#xff1a;无*/ void Timer_Init(void) {/*开启时钟*/RCC_APB1PeriphClockCmd(RCC…

【AI】指定python3.10安装Jupyter Lab

家里电脑 13900K, bash 不识别pythoncmd可以,但是cmd似乎默认是python2.7这个是webrtc构建需要的.python3 则可以识别到但是版本是python3.12*多个版本如何通过制定的python3.10 的pip来安装软件,例如Jupyter Lab安装3.10 C:\Users\zhangbin\AppData\Roaming\Microsoft\Windo…

网络安全之从原理看懂XSS

01、XSS的原理和分类 跨站脚本攻击XSS(Cross Site Scripting)&#xff0c;为了不和层叠样式表(Cascading Style Sheets&#xff0c;CSS)的缩写混淆 故将跨站脚本攻击缩写为XSS&#xff0c;恶意攻击者往Web页面里插入恶意Script代码&#xff0c;当用户浏览该页面时&#xff0c…