哈希表hash_table

一个人为什么要努力? 我见过最好的答案就是:因为我喜欢的东西都很贵,我想去的地方都很远,我爱的人超完美。

文章目录

  • 哈希表的引出
    • unordered系列的关联式容器
  • 底层结构
    • 哈希的概念
  • 开放寻址法
  • 拉链法(哈希桶)
    • 拉链法的结构
    • 什么是拉链法
  • 总结

哈希表的引出

unordered系列的关联式容器

在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 l o g 2 N log_2 N log2N,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同,本文中只对unordered_map和unordered_set进行介绍,unordered_multimap和unordered_multiset可查看文档介绍。

底层结构

unordered系列的查询效率高是因为底层运用了哈希结构

哈希的概念

顺序表以及平衡树中元素的关键码和数据的大小没有直接对应的关系,因此我们在顺序表和平衡树中需要对关键码或者是数据进行逐一比对,在平衡树中我们使用关键码的大小关系从而减少比对次数,而顺序表我们只能逐一对数据进行比对才可以确定我们想要的元素,我们发现查找中我们中间会因为查找大量的无关元素而浪费时间,平衡树和顺序表的区别就在于,顺序表是逐个查找而平衡树则是通过不断的判断查找的方向从而减少查询的次数。所以查找的效率主要在于能不能减少无谓的查找。
理想情况下理想的情况下的查找是不需要经过任何比对,直接通过可以直接找到数据元素的,但是这种方式是理想的我们无法做到只能尽可能的接近理想状态,那么这里就引出了我们的哈希表。
哈希表就是通过键值对和数据的映射关系从而可以在接近O(1)的时间内找到对应的数据。
那么哈希表的插入等情况是怎么进行的呢?其实就是通过特定的函数来算出该数据的关键码从而在关键码中插入。
列如我们的函数设为关键码=数据%10然后我们要插入的数值为 1,2,13,16。那么我们该如何进行插入呢其实很简单,那就是用1%10,2%10,13%10,16%10,算出来他们的关键码(关键码其实就是可以理解为要插入的数据的下标值)然后通过关键码进行存储

搜索元素
对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功

对于上面的这个方法我们减少了关键码的比较因此搜索的速度非常的快,但是这里也会有问题那就是冲突,因为我们在插入的时候是可能会出现一对多的情况的,比如说上面的(%10)我们会知道20%10,30%10都是0这里就出现了冲突也就是说不同的关键字通过相同的函数进行计算可能会得到相同的关键码,那么这里处理冲突就分为两种了,拉链法(哈希痛)和开放寻址法。

开放寻址法

首先讲解一下开放寻址法开放寻址法其实就是当前的位置产生冲突的时候就去找下一个位置,就像我们去蹲坑这个坑位有人了我们就去下一个坑位一直到最后一个坑位都有人的话我们就去第一个坑位继续往后看,这里我们会发现开放寻址法的话必须保证这个厕所有坑位,那么其实我们哈希表的底层结构中是有办法保证,他肯定有空余位置的。
在这里插入图片描述
这里的线段编号代表的是查找空余坑位的次数。那么用代码的表示其实就是下面这样字

	bool Insert(const pair<K, V>& kv){// 扩容//if ((double)_n / (double)_table.size() >= 0.7)if (_n*10 / _table.size() >= 7){size_t newSize = _table.size() * 2;// 遍历旧表,重新映射到新表HashTable<K, V, HashFunc> newHT;newHT._table.resize(newSize);// 遍历旧表的数据插入到新表即可for (size_t i = 0; i < _table.size(); i++){if (_table[i]._state == EXIST){newHT.Insert(_table[i]._kv);}}_table.swap(newHT._table);}// 线性探测HashFunc hf;size_t hashi = hf(kv.first) % _table.size();while (_table[hashi]._state == EXIST){++hashi;hashi %= _table.size();}_table[hashi]._kv = kv;_table[hashi]._state = EXIST;++_n;return true;}

上面的插入代码用了模板泛型编程我给解读一下首先哈希表的插入我们首先就是要根据数据和函数从而计算出我们的关键码,另外就是我们在插入的时候为了避免表满了的情况我们设置的会有一个值也就是(当前插入节点)/(总长度)这样一个比值,当这个比值我们一般设为0.7当比值大于等于0.7的时候我们就会对原来的哈希表进行扩容。但是这里有个问题那就是我们在进行扩容的时候由于我们在计算关键码的时候做分母的值一般为目前容器的容量那么当我们扩容后这个容器的容量就会产生变化此时已经插入进入的值在扩容后的容器中位置是会发生改变的。那么有什么好的解决方法呢?

其实很简单我们只需要再开辟一个新容器然后把原来的容器中的值插入到新容器中再让新容器与就容器进行swap一下就可以了。(上面的代码中写的有)

拉链法(哈希桶)

拉链法的结构

上面我们讲了开放寻址法,开放寻址法有什么缺点呢?他的缺点就是说我们在寻找坑位的时候可能需要我们找到末尾再从头开始找就像我们去厕所的时候会发生可能你从这个位置一直找到最后一个坑位之后再回头才发现原来第一个坑位就是空余的。因此这时候就会导致我们查找的效率较慢,那么有什么办法呢?拉链法再处理的时候就比较不错。

什么是拉链法

如果我们把开放寻址法看成一个一维数组的话那么拉链法就是一个二维的数组,我觉得用二维数组也可以很好的讲述拉链法我给大家写一个很朴素的模仿拉链法的代码大家可以看一下

#include<iostream>
using namespace std;
int num[1010][1010];//假设num是我们要插入元素的容器这里呢我们"假设!!!!"当这个位置是0的时候就代表没有元素插入
int main()
{int N = 101;//假设我们的公式为(关键码)i=n(存储的数据)%N(101也是假设)int n;cin >> n;int i = n % N;//找到了要插入的位置是第i列for (int j = 0; j < 1010; j++)//从第i列的第一行往下找{if (num[j][i] == 0){num[j][i] = n;}}return 0;
}

正如上面的代码所示就是一个朴素的拉链法那么我们在实际中应该是什么的组合呢?相信大家不难知道我们实际上的组合应该是vector+list的组合代码如下

bool insert(const T& data){HashFunc func;if (Find(data.first)){return false;}if (_n == _table.size())//当原来的容器满了的时候{vector<Node*>newtable;//开辟一个新容器size_t newsize = _n * 2;//设置新容器的容量newtable.resize(newsize, nullptr);//开辟容器for (int i = 0; i < _table.size(); i++)//讲原来容器中的值插入到新容器中{Node* cur = _table[i];while (cur){Node* next = cur->_next;size_t hashi = func(cur->data) % newsize;cur->_next = newtable[hashi];newtable[hashi] = cur;cur = next;}_table[i] = nullptr;}_table.swap(newtable);//将新就容器进行swap}size_t hashi = func(data) % _table.size();Node* cur = new Node(data);cur->_next = _table[hashi];_table[hashi] = cur;++_n;return true;}

那么这里也有扩容,为什么这里也有扩容呢,是因为拉链法也有一个极端情况那就是很多数据甚至是全部数据在一条链上因此拉链法也有扩容而拉链法的扩容条件一般就是当插入元素个数与链表长度相同的时候。我们需要扩容。

总结

哈希表的优点
哈希最大的优点我相信就是哈希减少了比较的次数,从而使我们的查找效率都更加的快速。

哈希的缺点
哈希的缺点的我认为比较明显的一个就是当我们插入元素的时候可能会遇到扩容那么就会导致某一个元素插入的时候会比较慢但是总体而言利大于弊。

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

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

相关文章

睿趣科技:新手抖音开店卖什么产品好

抖音已经成为了一款年轻人热爱的社交媒体应用&#xff0c;同时也成为了一种全新的电商平台。对于新手来说&#xff0c;抖音开店卖什么产品是一个备受关注的问题。在这篇文章中&#xff0c;我们将探讨一些适合新手的产品选择&#xff0c;帮助他们在抖音上开店获得成功。 流行时尚…

面向对象特性分析大全集

面向对象特性分析 先进行专栏介绍 面向对象总析前提小知识分类浅析封装浅析继承浅析多态面向对象编程优点abc 核心思想实际应用总结 封装概念详解关键主要目的核心思想优点12 缺点12 Java代码实现封装特性 继承概念详解语法示例关键主要目的核心思想优点12 缺点12 Java代码实现…

【网络协议】TCP报文格式

1.源端口和目的端口 源端口字段占16比特&#xff0c;用来写入源端口号。源端口号用来标识发送该TCP报文段的应用进程。 目的端口字段占16比特&#xff0c;用来写入目的端口号。目的端口号用来标识接收该TCP报文段的应用进程。 2.序号 当序号增加到最后一个时&#xff0c;下…

MySQL 的 C 语言接口

1. mysql_init MYSQL *mysql_init(MYSQL *mysql); mysql_init函数的作用&#xff1a;创建一个 MYSQL 对象&#xff08;该对象用于连接数据库&#xff09;。 mysql_init函数的参数&#xff1a; ① mysql&#xff1a;MYSQL 结构体指针&#xff0c;一般设置为 NULL 。 mysql_init函…

PL/SQL+cpolar公网访问内网Oracle数据库

文章目录 前言1. 数据库搭建2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射 3. 公网远程访问4. 配置固定TCP端口地址4.1 保留一个固定的公网TCP端口地址4.2 配置固定公网TCP端口地址4.3 测试使用固定TCP端口地址远程Oracle 前言 Oracle&#xff0c;是甲骨文公司的一款关系…

JVM111

JVM1 字节码与多语言混合编程 字节码 我们平时说的java字节码&#xff0c; 指的是用java语言编译成的字节码。准确的说任何能在jvm平台上执行的字节码格式都是一样的。所以应该统称为:jvm字节码。不同的编译器&#xff0c;可以编译出相同的字节码文件&#xff0c;字节码文件…

十五、异常(4)

本章概要 Java 标志异常 特例&#xff1a;RuntimeException 使用 finally 进行清理 finally 用来做什么&#xff1f;在 return 中使用 finally缺憾&#xff1a;异常丢失 Java 标准异常 Throwable 这个 Java 类被用来表示任何可以作为异常被抛出的类。Throwable 对象可分为两…

配置OSPF路由

OSPF路由 1.OSPF路由 1.1 OSPF简介 OSPF(Open Shortest Path First&#xff0c;开放式最短路径优先&#xff09;路由协议是另一个比较常用的路由协议之一&#xff0c;它通过路由器之间通告网络接口的状态&#xff0c;使用最短路径算法建立路由表。在生成路由表时&#xff0c;…

LLM-TAP随笔——大语言模型基础【深度学习】【PyTorch】【LLM】

文章目录 2.大语言模型基础2.1、编码器和解码器架构2.2、注意力机制2.2.1、注意力机制&#xff08;Attention&#xff09;2.2.2、自注意力机制&#xff08;Self-attention&#xff09;2.2.3、多头自注意力&#xff08;Multi-headed Self-attention&#xff09; 2.3、transforme…

华为摄像头智能安防监控解决方案

云时代来袭&#xff0c;数字化正在从园区办公延伸到生产和运营的方方面面&#xff0c;智慧校园&#xff0c;柔性制造&#xff0c;掌上金融和电子政务等&#xff0c;面对各种各样的新兴业态的涌现&#xff0c;企业需要构建一张无所不联、随心体验、业务永续的全无线网络&#xf…

国内大语言模型的相对比较:ChatGLM2-6B、BAICHUAN2-7B、通义千问-6B、ChatGPT3.5

一、 前言 国产大模型有很多&#xff0c;比如文心一言、通义千问、星火、MOSS 和 ChatGLM 等等&#xff0c;但现在明确可以部署在本地并且开放 api 的只有 MOOS 和 ChatGLM。MOOS 由于需要的 GPU 显存过大&#xff08;不量化的情况下需要80GB&#xff0c;多轮对话还是会爆显存…

TSM动作识别模型【详解】

文章目录 本文使用的是somethingv2数据集&#xff0c;解压后是如下形式&#xff1b; 由于该压缩数据进行了分卷操作&#xff0c;需要合并后才能进行解压。首先我们将下面4个json文件剪贴到其他文件夹&#xff0c;只保留00-19的文件&#xff0c;然后在该文件夹下打开cmd&#xf…

【图像分割】图像检测(分割、特征提取)、各种特征(面积等)的测量和过滤(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Python 笔记06(Mysql数据库)

一 基础 1.1 安装 MySQL下载参考&#xff1a;MySQL8.0安装配置教程【超级详细图解】-CSDN博客 测试是否安装并正确配置环境变量&#xff1a; 1.2 查看服务器是否正常运行 1.3 显示数据库 show databases; 1.4 退出 exit 1.5 python 连接 1.6 查主机IP ipconfig

一篇文章教你自动化测试如何解析excel文件?

前言 自动化测试中我们存放数据无非是使用文件或者数据库&#xff0c;那么文件可以是csv&#xff0c;xlsx&#xff0c;xml&#xff0c;甚至是txt文件&#xff0c;通常excel文件往往是我们的首选&#xff0c;无论是编写测试用例还是存放测试数据&#xff0c;excel都是很方便的。…

SpringBoot使用Docker并上传至DockerHub

我的新书《Android App开发入门与实战》已于2020年8月由人民邮电出版社出版&#xff0c;欢迎购买。点击进入详情 文章目录 1.系列文章2.构建docker镜像的方式3.docker操作3.1 安装docker3.2 查看docker镜像3.3 本地运行docker3.4 修改tag3.5 推送docker镜像3.6 远端server拉取d…

Linux 集锦 之 最常用的几个命令

Linux最常用的几个命令 ​ Linux系统中的命令那是相当地丰富&#xff0c;不同的版本可能还有不同的命令&#xff0c;不过Linux核心自带的命令大概有几百个&#xff0c;这个不管是什么发行版一般都是共用的。 ​ 如果希望探索Linux的所有命令&#xff0c;可能不太实际&#xf…

树莓派基本配置(2)

安装motion $sudo apt-get update $sudo apt-get install motion配置motion sudo nano /etc/default/motionsudo nano /etc/motion/motion.conf主要改这些参数 //让Motion作为守护进程运行 daemon on ... //用这个端口号来读取数据 stream_port 8081 ... //网络上其它主机…

力扣刷题-哈希表-求两个数组的交集

349 求两个数组的交集 题意&#xff1a;给定两个数组&#xff0c;编写一个函数来计算它们的交集。注意&#xff1a;输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。 提示&#xff1a; 1 < nums1.length, nums2.length < 1000 0 < nums1[i], …

nodejs在pdf中绘制表格

需求 之前我已经了解过如何在pdf模板中填写字段了 nodejs根据pdf模板填入中文数据并生成新的pdf文件https://blog.csdn.net/ArmadaDK/article/details/132456324 但是当我具体使用的时候&#xff0c;我发现我的模板里面有表格&#xff0c;表格的长度是不固定的&#xff0c;所…