哈希表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.xdnf.cn/news/145942.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

毅速课堂:3D打印随形水路设计应注意什么?

随形水路是一种基于3D打印技术的新型模具冷却水路&#xff0c;能有效提高冷却效率、缩短冷却周期、提升产品良率、提高生产效率、 与传统的水路设计相比&#xff0c;随形水路更加贴合模具型腔表面&#xff0c;能够更加均匀地分配冷却水&#xff0c;使模具各部分的冷却效果得到有…

buuctf-[WUSTCTF2020]CV Maker

打开环境 随便登录注册一下 进入到了profile.php 其他没有什么页面&#xff0c;只能更换头像上传文件&#xff0c;所以猜测是文件上传漏洞 上传一句话木马看看 <?php eval($_POST[a]);?>回显 搜索一下 添加文件头GIF89a。上传php文件 查看页面源代码&#xff0c;看…

[红明谷CTF 2021]write_shell %09绕过过滤空格 ``执行

目录 1.正常短标签 2.短标签配合内联执行 看看代码 <?php error_reporting(0); highlight_file(__FILE__); function check($input){if(preg_match("/| |_|php|;|~|\\^|\\|eval|{|}/i",$input)){ 过滤了 木马类型的东西// if(preg_match("/| |_||php/&quo…

Springboot中使用拦截器、过滤器、监听器

一、Servlet、Filter&#xff08;过滤器&#xff09;、 Listener&#xff08;监听器&#xff09;、Interceptor&#xff08;拦截器&#xff09; Javaweb三大组件&#xff1a;servlet、Filter&#xff08;过滤器&#xff09;、 Listener&#xff08;监听器&#xff09; Spring…

【力扣周赛】第 364 场周赛⭐(前后缀分解+单调栈DFS技巧)

文章目录 竞赛链接Q1&#xff1a;2864. 最大二进制奇数&#xff08;贪心&#xff09;写法1——手动模拟&#xff08;代码长&#xff0c;运行快&#xff09;写法2——API&#xff08;代码短&#xff0c;运行慢&#xff09; Q2&#xff1a;2865. 美丽塔 I竞赛时代码——枚举山顶 …

WPF 实现点击按钮跳转页面功能

方法1. 配置环境 首先添加prism依赖项&#xff0c;配置好所有文件。需要配置的有两个文件&#xff1a;App.xaml.cs和App.xaml App.xaml.cs using System.Data; using System.Linq; using System.Threading.Tasks; using System.Windows;namespace PrismDemo {/// <summa…

正点原子嵌入式linux驱动开发——STM32MP1启动详解

STM32单片机是直接将程序下载到内部 Flash中&#xff0c;上电以后直接运行内部 Flash中的程序。 STM32MP157内部没有供用户使用的 Flash&#xff0c;系统都是存放在外部 Flash里面的&#xff0c;比如 EMMC、NAND等&#xff0c;因此 STM32MP157上电以后需要从外部 Flash加载程序…

Linux高性能服务器编程 学习笔记 第九章 IO复用

IO复用使程序能同时监听多个文件描述符&#xff0c;这可以提高程序的性能&#xff0c;通常网络程序在以下情况需要使用IO复用&#xff1a; 1.客户端进程需要同时处理多个socket。 2.客户端进程需要同时处理用户输入和网络连接。 3.TCP服务器要同时处理监听socket和连接socket…

配置OSPF路由

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

【通意千问】大模型GitHub开源工程学习笔记(2)--使用Qwen进行推理的示例代码解析,及transformers的库使用

使用Transformers来使用模型 如希望使用Qwen-chat进行推理,所需要写的只是如下所示的数行代码。请确保你使用的是最新代码,并指定正确的模型名称和路径,如Qwen/Qwen-7B-Chat和Qwen/Qwen-14B-Chat 这里给出了一段代码 from transformers import AutoModelForCausalLM, Aut…

机器学习笔记 - 基于强化学习的贪吃蛇玩游戏

一、关于深度强化学习 如果不了解深度强化学习的一般流程的可以考虑看一下下面的链接。因为这里的示例因为在PyTorch 之上实现深度强化学习算法。 机器学习笔记 - Deep Q-Learning算法概览深度Q学习是一种强化学习算法,它使用深度神经网络来逼近Q函数,用于确定在给定状态下采…

ROS2 中的轻量级、自动化、受控回放

一、说明 这篇文章描述了一种在 ROS2 中实现受控重播器的轻量级方法。用以测试中将现象重新播放一遍&#xff0c;以实现调参或故障定位的目的。所有源代码都可以在这里找到。该帖子也可在此处获得。 二、问题&#xff1a;不同步重播 任何曾经认真开发过 ROS2 的人都会知道这个问…

springboot和vue:八、vue快速入门

vue快速入门 新建一个html文件 导入 vue.js 的 script 脚本文件 <script src"https://unpkg.com/vuenext"></script>在页面中声明一个将要被 vue 所控制的 DOM 区域&#xff0c;既MVVM中的View <div id"app">{{ message }} </div…

uboot启动流程涉及reset汇编函数

一. uboot启动流程中函数 之前了解了uboot链接脚本文件 u-boot.lds。 从 u-boot.lds 中我们已经知道了入口点是 arch/arm/lib/vectors.S 文件中的 _start。 本文了解 一下&#xff0c;uboot启动过程中涉及的 reset 函数。本文继上一篇文章学习&#xff0c;地址如下&#xff…

统计模型----决策树

决策树 &#xff08;1&#xff09;决策树是一种基本分类与回归方法。它的关键在于如何构建这样一棵树。决策树的建立过程中&#xff0c;使用基尼系数来评估节点的纯度和划分的效果。基尼系数是用来度量一个数据集的不确定性的指标&#xff0c;其数值越小表示数据集的纯度越高。…

揭秘:机构招生电子传单制作的五个黄金法则

机构招生微传单制作一直都是让很多人在意的事情。一款好的微传单不仅可以吸引更多的学生&#xff0c;还可以省去很多招生工作的时间和精力。但是&#xff0c;很多人却不知道如何制作一款精美的微传单。下面就让我们来学习一下如何制作一款机构招生的微传单吧。 首先&#xff0c…

Egg 封装接口返回信息

中间件封装 代码 const msgArr {"200":成功,"401":token失效 } module.exports (option, app) > {return async function(ctx, next) {try{//成功是返回的信息ctx.emit(code,data,msg)>{console.log(1111,code,data,msg)ctx.body {code,data:dat…

springboot 简单配置mongodb多数据源

准备工作&#xff1a; 本地mongodb一个创建两个数据库 student 和 student-two 所需jar包&#xff1a; # springboot基于的版本 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId>&l…

C++之std::atomic解决多线程7个问题(二百四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

竞赛选题 多目标跟踪算法 实时检测 - opencv 深度学习 机器视觉

文章目录 0 前言2 先上成果3 多目标跟踪的两种方法3.1 方法13.2 方法2 4 Tracking By Detecting的跟踪过程4.1 存在的问题4.2 基于轨迹预测的跟踪方式 5 训练代码6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习多目标跟踪 …