哈希表——闭散列表

该哈希表实现是闭散列实现法。

闭散列表:

        闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。

        那如何寻找下一个空位置呢?

线性探测:

        线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

注意:除了线性探测,你还可以进行二次探测等,看个人实现策略。

如何插入
        通过哈希函数获取待插入元素在哈希表中的位置
        如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,
        使用线性探测找到下一个空位置,插入新元素

比如2.1中的场景,现在需要插入元素44,先通过哈希函数计算哈希地址,hashAddr为4,
因此44理论上应该插在该位置,但是该位置已经放了值为4的元素,即发生哈希冲突。

如何删除
        采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素。 

// 哈希表每个空间给个标记
// EMPTY此位置空, EXIST此位置已经有元素, DELETE元素已经删除
enum State{EMPTY, EXIST, DELETE};

线性探测实现插入:

		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
				return false;

			// 负载因子0.7就扩容
			if (_n*10 / _tables.size() == 7)
			{
				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);
			}

			Hash hf;
			// 线性探测
			size_t hashi = hf(kv.first) % _tables.size();
			while (_tables[hashi]._s == EXIST)
			{
				hashi++;

				hashi %= _tables.size();
			}

			_tables[hashi]._kv = kv;
			_tables[hashi]._s = EXIST;
			++_n;

			return true;
		}

什么是负载因子?

        负载因子是关键词key的存储个数与哈希表内存大小之比,一般取0.75左右,这样做是为了提高存储效率,(只有百分之75的内存可以用,剩余的百分之25,是不存储的)减少哈希冲突。

如何扩展内存?

        定义一个新的对象,开好想要的内存,将旧表的数据重新查到新的哈希表中,旧表的数据分布与新表的数据分布不一样,将旧表数据插入完之后,再将新表的哈希表数据与旧表的数据进行交换。

哈希表的查找:

		HashData<K, V>* Find(const K& key)
		{
			Hash hf;

			size_t hashi = hf(key) % _tables.size();
			while (_tables[hashi]._s != EMPTY)
			{
				if (_tables[hashi]._s == EXIST
					&& _tables[hashi]._kv.first == key)
				{
					return &_tables[hashi];
				}

				hashi++;
				hashi %= _tables.size();
			}

			return NULL;
		}

数据有三种状态:存在,删除,为空。

存在和删除的状态下如果没有找到要查找的数据就要向后继续查找,因为插入时进行的是线性插入,只有为空和删除的位置才进行插入,所以有可能想要的数据会出现在,删除状态的后面。

注意:如果是二次探测就进行二次查找

哈希表的删除:

		// 伪删除法
		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);
			if (ret)
			{
				ret->_s = DELETE;
				--_n;
				return true;
			}
			else
			{
				return false;
			}
		}

将要删除的数据状态进行标记就行了,如果标记为空,就会影响查找函数的进行,就可能会出现查找错误的情况。

完整代码及其测试代码:

#include<vector>


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)
	{
		// BKDR
		size_t hash = 0;
		for (auto e : key)
		{
			hash *= 31;
			hash += e;
		}

		//cout << key << ":" << hash << endl;
		return hash;
	}
};

namespace open_address
{
	enum Status
	{
		EMPTY,
		EXIST,
		DELETE
	};

	template<class K, class V>
	struct HashData
	{
		pair<K, V> _kv;
		Status _s;          //状态
	};

	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
	public:
		HashTable()
		{
			_tables.resize(10);
		}

		bool Insert(const pair<K, V>& kv)
		{
			if (Find(kv.first))
				return false;

			// 负载因子0.7就扩容
			if (_n*10 / _tables.size() == 7)
			{
				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);
			}

			Hash hf;
			// 线性探测
			size_t hashi = hf(kv.first) % _tables.size();
			while (_tables[hashi]._s == EXIST)
			{
				hashi++;

				hashi %= _tables.size();
			}

			_tables[hashi]._kv = kv;
			_tables[hashi]._s = EXIST;
			++_n;

			return true;
		}

		HashData<K, V>* Find(const K& key)
		{
			Hash hf;

			size_t hashi = hf(key) % _tables.size();
			while (_tables[hashi]._s != EMPTY)
			{
				if (_tables[hashi]._s == EXIST
					&& _tables[hashi]._kv.first == key)
				{
					return &_tables[hashi];
				}

				hashi++;
				hashi %= _tables.size();
			}

			return NULL;
		}

		// 伪删除法
		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);
			if (ret)
			{
				ret->_s = DELETE;
				--_n;
				return true;
			}
			else
			{
				return false;
			}
		}

		void Print()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				if (_tables[i]._s == EXIST)
				{
					//printf("[%d]->%d\n", i, _tables[i]._kv.first);
					cout << "[" << i << "]->" << _tables[i]._kv.first <<":" << _tables[i]._kv.second<< endl;
				}
				else if (_tables[i]._s == EMPTY)
				{
					printf("[%d]->\n", i);
				}
				else
				{
					printf("[%d]->D\n", i);
				}
			}

			cout << endl;
		}

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

	void TestHT1()
	{
		HashTable<int, int> ht;
		int a[] = { 4,14,24,34,5,7,1 };
		for (auto e : a)
		{
			ht.Insert(make_pair(e, e));
		}

		ht.Insert(make_pair(3, 3));
		ht.Insert(make_pair(3, 3));
		ht.Insert(make_pair(-3, -3));
		ht.Print();

		ht.Erase(3);
		ht.Print();

		if (ht.Find(3))
		{
			cout << "3存在" << endl;
		}
		else
		{
			cout << "3不存在" << endl;
		}

		ht.Insert(make_pair(3, 3));
		ht.Insert(make_pair(23, 3));
		ht.Print();
	}

	void TestHT2()
	{
		string arr[] = { "香蕉", "甜瓜","苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
		//HashTable<string, int, HashFuncString> ht;
		HashTable<string, int> ht;
		for (auto& e : arr)
		{
			//auto ret = ht.Find(e);
			HashData<string, int>* ret = ht.Find(e);
			if (ret)
			{
				ret->_kv.second++;
			}
			else
			{
				ht.Insert(make_pair(e, 1));
			}
		}

		ht.Print();

		ht.Insert(make_pair("apple", 1));
		ht.Insert(make_pair("sort", 1));

		ht.Insert(make_pair("abc", 1));
		ht.Insert(make_pair("acb", 1));
		ht.Insert(make_pair("aad", 1));

		ht.Print();
	}
}

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

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

相关文章

【傻瓜级JS-DLL-WINCC-PLC交互】3.JS-DLL进行交互

思路 JS-DLL-WINCC-PLC之间进行交互&#xff0c;思路&#xff0c;先用Visual Studio创建一个C#的DLL控件&#xff0c;然后这个控件里面嵌入浏览器组件&#xff0c;实现JS与DLL通信&#xff0c;然后DLL放入到WINCC里面的图形编辑器中&#xff0c;实现DLL与WINCC的通信。然后PLC与…

万宾科技可燃气体监测仪科技作用全览

燃气管网在运行过程中经常会遇到燃气管道泄漏的问题&#xff0c;燃气泄漏甚至会引起爆炸&#xff0c;从而威胁人民的生命和财产安全&#xff0c;因此对燃气管网进行定期巡检是十分必要的工作。但是传统的人工巡检已不能满足城市的需要&#xff0c;除了选择增加巡检人员之外&…

SELinux(一) 简介

首发公号&#xff1a;Rand_cs 前段时间的工作遇到了一些关于 SELinux 的问题&#xff0c;初次接触不熟悉此概念&#xff0c;导致当时配置策略时束手束脚&#xff0c;焦头烂额&#xff0c;为此去系统的学习了下 SELinux 的东西。聊 SELinux 之前&#xff0c;先来看看什么叫做访…

Mac电脑音乐标签管理 Yate 激活最新 for Mac

Yate是一款非常实用的音频编辑和标记软件&#xff0c;它提供了丰富的功能和工具来帮助用户编辑、整理和管理音频文件。无论是在音乐收藏管理、DJ和音乐制作方面&#xff0c;还是在其他需要处理大量音频文件的领域&#xff0c;Yate都是非常值得推荐的工具。 Yate for Mac功能特…

2023网络安全产业图谱

1. 前言 2023年7月10日&#xff0c;嘶吼安全产业研究院联合国家网络安全产业园区&#xff08;通州园&#xff09;正式发布《嘶吼2023网络安全产业图谱》。 嘶吼安全产业研究院根据当前网络安全发展规划与趋势发布《嘶吼2023网络安全产业图谱》调研&#xff0c;旨在进一步了解…

【力扣】189. 轮转数组

【力扣】189. 轮转数组 文章目录 【力扣】189. 轮转数组1. 题目介绍2. 解法2.1 方法一&#xff1a;不太正规&#xff0c;但是简单2.2 方法二&#xff1a;使用额外的数组2.3 方法三&#xff1a;环状替换2.4 方法四&#xff1a;数组翻转 3. Danger参考 1. 题目介绍 给定一个整数…

使用mock.js模拟数据

一、安装mock.js npm i mockjs 二、配置JSON文件 我们创建一个mock文件夹&#xff0c;用于存放mock相关的模拟数据和代码实现。 我们将数据全部放在xxx.json文件夹下&#xff0c;里面配置我们需要的JSON格式的数据。 注意&#xff1a;json文件中不要留有空格&#xff0c;否则…

八股文-如何理解Java中的多态

什么是多态&#xff1f; 多态是面向对象编程的一个重要概念&#xff0c;它允许一个对象以不同的形式表现。也就是说&#xff0c;在父类中定义的属性和方法&#xff0c;在子类继承后&#xff0c;可以有不同的数据类型或表现出不同的行为。这可以使得同一个属性或方法&#xff0…

带残差连接的ResNet18

目录 1 模型构建 1.1 残差单元 1.2 残差网络的整体结构 2 没有残差连接的ResNet18 2.1 模型训练 2.2 模型评价 3 带残差连接的ResNet18 3.1 模型训练 3.2 模型评价 4 与高层API实现版本的对比实验 总结 残差网络&#xff08;Residual Network&#xff0c;ResNet&#xff09;…

【TinyALSA全解析(二)】wav和pcm音频文件格式详解

wav和pcm音频文件格式详解 一、本文的目的二、wav和pcm格式文件介绍三、pcm格式文件解析四、wav文件内容解析4.1 文件内容描述4.2 实战分析 五、如何在各种音频格式之间进行转换 /******************************************************************************************…

中英双语大模型ChatGLM论文阅读笔记

论文传送门&#xff1a; [1] GLM: General Language Model Pretraining with Autoregressive Blank Infilling [2] Glm-130b: An open bilingual pre-trained model Github链接&#xff1a; THUDM/ChatGLM-6B 目录 笔记AbstractIntroductionThe design choices of GLM-130B 框架…

Python Pyvis库:可视化复杂网络结构的利器

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;我是涛哥&#xff0c;今天为大家分享 Python Pyvis库&#xff1a;可视化复杂网络结构的利器&#xff0c;全文4000字&#xff0c;阅读大约12钟。 在数据科学和网络分析领域&#xff0c;理解和可视化复杂网络结构是…

华为设备使用python实现文件自动保存下载

实验目的&#xff1a; 公司有一台CE12800的设备&#xff0c;管理地址为172.16.1.2&#xff0c;现在需要编写自动化脚本&#xff0c;STELNET实现设备的自动保存配置文件&#xff0c;使用SFTP实现设备的文件下载。 实验拓扑&#xff1a; 实验步骤&#xff1a; 步骤1&#xff1…

深入Rust的模式匹配与枚举类型

今天&#xff0c;我们将深入探讨Rust语言中的两个强大特性&#xff1a;模式匹配&#xff08;Pattern Matching&#xff09;和枚举类型&#xff08;Enums&#xff09;。这两个特性是Rust提供的核心工具之一&#xff0c;它们在处理多种类型的数据和复杂的逻辑控制中发挥着关键作用…

手把手教你如何实现List——ArrayList

目录 前言&#xff1a; 线性表 顺序表 接口的实现 一. 打印顺序表 二.新增元素,默认在数组最后新增 三.在 pos 位置新增元素 四.判定是否包含某个元素 五. 查找某个元素对应的位置 六.获取 pos 位置的元素 七.给 pos 位置的元素设为 value 八.删除第一次出现的关键字k…

Python中如何用栈实现队列

目录 一、引言 二、使用两个栈实现队列 三、性能分析 四、应用场景 五、代码示例 六、优缺点总结 一、引言 队列&#xff08;Queue&#xff09;和栈&#xff08;Stack&#xff09;是计算机科学中常用的数据结构。队列是一种特殊的线性表&#xff0c;只允许在表的前端进行…

HTTPS的介绍以及工作过程

目录 一.HTTPS是什么&#xff1f; HTTPS的介绍 HTTPS产生的背景 二.https的安全机制 加密是什么 如何加密 客户端如何获取公钥 总结 &#x1f381;个人主页&#xff1a;tq02的博客_CSDN博客-C语言,Java,Java数据结构领域博主 &#x1f3a5; 本文由 tq02 原创&#xff0…

OkHttp的配置

一、拦截器 1.添加拦截器的作用&#xff1a; 每次在请求过程中就会回调一次intercept方法 2.拦截器的回调方法里我们可以做那些事情&#xff1a; 当前的请求还没有发给服务器&#xff0c;比如我们在与服务器通信的时候&#xff0c;一个应用中很多地方都会跟服务器发起通信。…

Linux端口流量统计

Ubuntu sudo apt-get install wiresharkCentOS sudo yum install wiresharkUDP端口统计 sudo tshark -i <interface> -f "udp port <port_number>" -a duration:60 -q -z conv,udp请将 替换为你的网络接口&#xff0c;<port_number> 替换为要监…

ASP.NET Core 使用 SignalR 实现实时通讯

&#x1f433;简介 SignalR是一个用于ASP.NET的库&#xff0c;它允许服务器代码向连接的客户端实时发送推送通知。它使用WebSockets作为底层传输机制&#xff0c;但如果浏览器不支持WebSockets&#xff0c;它会自动回退到其他兼容的技术&#xff0c;如服务器发送事件&#xff…