C++:哈希表的模拟实现

文章目录

  • 哈希
    • 哈希冲突
    • 哈希函数
  • 解决哈希冲突
    • 闭散列:
    • 开散列

哈希

在顺序结构和平衡树中,元素的Key和存储位置之间没有必然的联系,在进行查找的时候,要不断的进行比较,时间复杂度是O(N)或O(logN)

而有没有这样一种方案,可以直接不经过比较,从表中得到所需要的元素呢?直接进行获取就可以,如果存在这样的结构,那么对它而言的查找效率是很高的

插入元素

根据上面的原理,在插入元素的时候,根据插入元素的Key,找到一个可以映射到一个表中的具体位置,并进行存放

搜索元素

在对元素的Key进行计算后,就可以直接找到它被映射到了表中的哪一个位置,从而可以直接找到它在表中的位置,如果找到了就返回true

上面的这个原理,就叫做哈希,也叫做散列,而在哈希中使用的这个转换函数就叫做哈希函数,也叫做散列函数,构造出来的结构就叫做哈希表,也叫做散列表

下面用一个例子来举例:

例如数据集合有{1, 7, 6, 4, 5, 9}

那么就可以把根据一个哈希转换函数:hash(key) = key % capacity,得到一个专属于它的下标,把这个值存到下标的位置:

在这里插入图片描述
通过这样的方法就可以对元素和下标建立一种关系,在寻找的时候可以直接寻找到,在进行数据的存储和查找的过程拥有相当高的效率

但依旧有问题,如果存储的元素正好已经被存储过了呢?

哈希冲突

所谓哈希冲突,简单来说就是不同的Key值经过计算,得到了一个相同的hash值,此时再向表中填写数据就会有问题,这个过程就叫哈希冲突,也叫做哈希碰撞,那为什么会引起哈希碰撞?如何解决?

哈希函数

通常来说,引起哈希碰撞的一个原因是哈希函数有问题

常见的哈希函数定义:

  1. 直接定址法:取Key值的某个线性函数作为散列地址,例如Hash(Key)= A*Key + B
  2. 除留余数法:设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址
  3. 平方取中法
  4. 折叠法
  5. 数学分析法

哈希函数设计的越好,出现哈希冲突的可能性就越低,但无法避免哈希冲突,也就是说,哈希冲突是一定会发生的

解决哈希冲突

解决的方法通常有两种,闭散列和开散列

闭散列:

当发生哈希冲突的时候,如果哈希表没有被装满,那么就说明哈希表中肯定还有空余位置,那么就放到冲突位置的下一个位置当中去

  1. 线性探测

从发生哈希冲突的位置开始,依次向后进行探测,直到探测到了一个空位置为止

那么下面模拟实现一下线性探测的实现过程

	bool insert(const pair<K, V>& kv){// 考虑扩容问题if (_n * 10 / _t.size() == 7){size_t newsize = _t.size() * 2;vector<HashData<K, V>> newV;newV.resize(newsize);size_t _newn = 0;// 把原来的数据放到新表中 遍历一次旧表for (int i = 0; i < _t.size(); i++){// 如果旧表中这个位置的值存在 就准备放到新表中if (_t[i]._s == EXIST){size_t newhashi = _t[i]._kv.first % newsize;while (newV[newhashi]._s == EXIST){newhashi++;newhashi %= newsize;}newV[newhashi]._kv = _t[i]._kv;newV[newhashi]._s = EXIST;_newn++;}}_t.swap(newV);_n = newsize;}// 正常插入逻辑size_t hashi = kv.first % _t.size();while (_t[hashi]._s == EXIST){// 如果插入元素的位置有内容,就插入到下一个位置hashi++;hashi %= _t.size();}_t[hashi]._kv = kv;_t[hashi]._s = EXIST;_n++;return true;}

但是闭散列的缺陷是很明显的,比如当插入数据是12,22,32,42…这样的数据的时候,就会导致不停地触发哈希冲突,这样会产生堆积的效应,为了避免出现这样的问题,又提出了开散列的方案

开散列

开散列也叫做哈希桶,也叫做拉链法,原理就是把具有相同地址的Key值放到一起,每一个子集就叫做一个桶,每个桶的元素通过单链表来进行链接,每个链表的头结点在哈希表中

在这里插入图片描述
开散列中每个桶中放的都是哈希冲突的元素

namespace opened_hashing
{// 定义节点信息template<class K, class V>struct Node{Node(const pair<K, V>& kv):_next(nullptr), _kv(kv){}Node* _next;pair<K, V> _kv;};template<class K, class V>class HashTable{typedef Node<K, V> Node;public:// 构造函数HashTable():_n(0){_table.resize(10);}// 析构函数~HashTable(){//cout << endl << "*******************" << endl;//cout << "destructor" << endl;for (int i = 0; i < _table.size(); i++){//cout << "[" << i << "]->";Node* cur = _table[i];while (cur){Node* next = cur->_next;//cout << cur->_kv.first << " ";delete cur;cur = next;}//cout << endl;_table[i] = nullptr;}}// 插入元素bool insert(const pair<K, V>& kv){// 如果哈希表中有这个元素,就不插入了if (find(kv.first)){return false;}// 扩容问题if (_n == _table.size()){HashTable newtable;int newsize = _table.size() * 2;newtable._table.resize(newsize, nullptr);for (int i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;// 把哈希桶中的元素插入到新表中int newhashi = cur->_kv.first % newsize;// 头插cur->_next = newtable._table[newhashi];newtable._table[newhashi] = cur;cur = next;}_table[i] = nullptr;}_table.swap(newtable._table);}// 先找到在哈希表中的位置size_t hashi = kv.first % _table.size();// 把节点插进去Node* newnode = new Node(kv);newnode->_next = _table[hashi];_table[hashi] = newnode;_n++;return true;}Node* find(const K& Key){// 先找到它所在的桶int hashi = Key % _table.size();// 在它所在桶里面找数据Node* cur = _table[hashi];while (cur){if (cur->_kv.first == Key){return cur;}cur = cur->_next;}return nullptr;}void print(){for (int i = 0; i < _table.size(); i++){cout << i << "->";Node* cur = _table[i];while (cur){cout << cur->_kv.first << " ";cur = cur->_next;}cout << endl;}cout << endl;}private:vector<Node*> _table;size_t _n;};
}

上面的实现看似没有问题,实际上依旧有问题,如果要传入的数据是string类,那么在比较的过程中会出现错误,因此要写一个仿函数用以处理这些情况

在这里插入图片描述
在这里插入图片描述
这里利用版本模板中的特化进行处理即可,处理细节比较巧妙

	template<class T>struct _Convert{T& operator()(const T& key){return key;}};template<>struct _Convert<string>{size_t& operator()(const string& key){size_t sum = 0;for (auto e : key){sum += e * 31;}return sum;}};

那么下一步就要进行对于哈希表的封装了,详情见模拟实现篇章

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

审计dvwa高难度命令执行漏洞的代码,编写实例说明如下函数的用法

审计dvwa高难度命令执行漏洞的代码 &#xff0c;编写实例说明如下函数的用法 代码&#xff1a; <?phpif( isset( $_POST[ Submit ] ) ) {// Get input$target trim($_REQUEST[ ip ]);// Set blacklist$substitutions array(& > ,; > ,| > ,- > ,$ …

SSM框架(二):AOP和事物

文章目录 一、AOP的介绍1.1 基本概念1.2 AOP入门1.3 AOP工作流程1.4 切入点表达式1.5 AOP的通知类型1.6 ProceedingJoinPoint1.7 AOP通知获取参数数据 二、事物2.1 基本介绍2.2 事物角色2.3 事物属性2.4 事物的传播行为 一、AOP的介绍 1.1 基本概念 1.2 AOP入门 导入坐标 <…

linux之chmod命令

在linux系统中经常遇到需要对文件修改读写执行的权限&#xff0c;下面对chomod命令进行梳理总结。 1、文件权限 在linux系统中&#xff0c;每个文件都有归属的所有者和所有组&#xff0c;并且规定了文件的所有者、以及其他人对文件所拥有的可读&#xff08;r&#xff09;、可写…

gitlab 实战

一.安装依赖 yum install -y curl policycoreutils-python openssh-server perl 二.安装gitlab yum install gitlab-jh-16.0.3-jh.0.el7.x86_64.rpm 三.修改下面的 vim /etc/gitlab/gitlab.rbexternal_url http://192.168.249.156 四.初始化 gitlab-ctl reconfigure 五.查看状…

c语言-数据结构-链式二叉树

目录 1、二叉树的概念及结构 2、二叉树的遍历概念 2.1 二叉树的前序遍历 2.2 二叉树的中序遍历 2.3 二叉树的后序遍历 2.4 二叉树的层序遍历 3、创建一颗二叉树 4、递归方法实现二叉树前、中、后遍历 4.1 实现前序遍历 4.2 实现中序遍历 4.3 实现后序遍历 5、…

CV计算机视觉每日开源代码Paper with code速览-2023.11.16

点击CV计算机视觉&#xff0c;关注更多CV干货 论文已打包&#xff0c;点击进入—>下载界面 点击加入—>CV计算机视觉交流群 1.【基础网络架构】ConvNet vs Transformer, Supervised vs CLIP: Beyond ImageNet Accuracy 论文地址&#xff1a;https://arxiv.org//pdf/23…

深度模型压缩研究回顾

深度模型压缩研究回顾 作者&#xff1a;安静到无声 个人主页 目录 深度模型压缩研究回顾推荐专栏 在本节中&#xff0c;主要介绍了目前主流的深度神经网络压缩与加速方法&#xff0c;主要包括轻量化网络设计、参数量化、知识蒸馏、模型剪枝和硬件加速等&#xff0c;其中模型剪…

【算法】最小生成树——普利姆 (Prim) 算法

目录 1.概述2.代码实现2.1.邻接矩阵存储图2.2.邻接表存储图2.3.测试 3.应用 1.概述 &#xff08;1&#xff09;在一给定的无向图 G (V, E) 中&#xff0c;(u, v) 代表连接顶点 u 与顶点 v 的边&#xff0c;而 w(u, v) 代表此边的权重&#xff0c;若存在 T 为 E 的子集且为无循…

湖科大计网:应用层

一、应用层概述 交互&#xff0c;实现特定问题&#xff01; 二、客户与服务器模型 一、C/S 客户/服务器方式 服务与被服务的关系。 二、P2P方式 对等方式 P2P方式是对等的&#xff0c;没有固定的服务器。 三、DNS域名系统 DNS&#xff08;Domain Name System&#xff09; 一、域…

2018年计网408

第33题 下列 TCP/P应用层协议中, 可以使用传输层无连接服务的是()A. FTPB. DNSC. SMTPD. HTTP 本题考察TCP/IP体系结构中&#xff0c;应用层常用协议所使用的运输层服务。 如图所示。这是TCP/IP体系结构中常见应用层协议各自所使用的运输层端口,。在这些应用层协议中&#x…

Vue Router的使用

Vue.js是一个流行的JavaScript框架&#xff0c;用于开发单页面应用程序。Vue提供了一个强大的路由系统&#xff0c;可以帮助我们管理应用程序中的不同页面。在本文中&#xff0c;我们将详细讲解Vue路由的使用方法。 目录 1. 安装Vue Router2. 创建路由实例3. 配置路由4. 在模板…

关闭bitlocker加密

windows11的笔记本电脑买回来发现分驱都处于bitlocker状态&#xff0c;上网上搜索都是说进入控制面板的安全项进行关闭&#xff0c;包括去搜索栏搜索“管理 BitLocker”&#xff0c;对搜索出来的项打开&#xff0c;经过试验&#xff0c;它们进入的是同一个位置&#xff0c;只有…

【docker】虚拟化和docker容器概念

基础了解 IAAS&#xff1a; 基础设施服务&#xff0c;&#xff08;只提供基础设施&#xff0c;没有系统&#xff09; **SAAS&#xff1a; ** 软件即服务&#xff0c;&#xff08;提供基础设施和系统&#xff09; PAAS&#xff1a; 平台即服务&#xff0c;&#xff08;提供基…

SwiftUI 如何动态开始和停止播放永久重复(repeatForever)动画

0. 功能需求 在 SwiftUI 丰富多彩的动画世界中,我们有时希望可以随意开始和停止永久循环(repeatForever)的动画,不过这时往往会产生错误的动画“叠加”效果。 从上图可以看到:虽然我们希望密码输入框背景只在用户输入密码时才发生闪烁,但顶部的密码输入框随着不断输入其…

Linux02 VIM编辑器

Linux02 VIM编辑器 基本上 vi/vim 共分为三种模式&#xff0c; 分别是命令模式&#xff08;Command mode&#xff09;&#xff0c;输入模式&#xff08;Insert mode&#xff09;和底线命令模式&#xff08;Last line mode&#xff09;。 三种状态进行切换 插入模式&#xff1a…

Java入门篇 之 内部类

本篇碎碎念&#xff1a;本篇没有碎碎念&#xff0c;想分享一段话&#xff1a; 你不笨&#xff0c;你只是需要时间&#xff0c;耐心就是智慧&#xff0c;不见得快就好&#xff0c;如果方向都不对&#xff0c;如果心术不正&#xff0c;如果德不配位&#xff0c;快就是对自己天分的…

【数值计算方法】矩阵特征值与特征向量的计算(一):Jacobi 旋转法及其Python实现

文章目录 一、Jacobi 旋转法1. 基本思想2. 计算过程演示3. 注意事项 二、Python实现迭代过程&#xff08;调试&#xff09; 矩阵的特征值&#xff08;eigenvalue&#xff09;和特征向量&#xff08;eigenvector&#xff09;在很多应用中都具有重要的数学和物理意义。Jacobi 旋转…

六、流量监管、流量整形

流量监管、流量整形 流量监管、流量整形1.1.定义1.2.简单流分类应用场景举例1.3.简单流分类的配置1.4.复杂流分类1.5.复杂流分类在产品中的实现 ————————————————— 流量监管、流量整形 当报文的发送速率大于接收速率&#xff0c;或者下游设备的接口速率小于…

GitHub 2023报告-开源和AI的现状

GitHub 2023报告-开源和AI的现状 深入探讨人工智能如何与开源互动&#xff0c;以及未来几年可能出现的趋势。 背景介绍 2023年&#xff0c;开源已成为全球软件开发的标准。无论是大公司还是小团队&#xff0c;都广泛使用开源技术进行项目开发。此外&#xff0c;随着机器学习和…

【Java】java | CacheManager | redisCacheManager

一、说明 1、查询增加缓存&#xff0c;使用Cacheable注解 2、项目中已经用到了ehcache&#xff0c;现在需求是两个都用 二、备份配置 1、redisConfig增加代码 Bean("redisCacheManage")Primarypublic CacheManager redisCacheManager(RedisConnectionFactory fact…