散列函数的基本概念

散列函数

算法不能设计太过复杂

  • 太复杂的散列函数,势必会消耗很多计算时间

散列函数生成的值要尽可能随机并且均匀分布

  • 这样才能避免或者最小化散列冲突
  • 而且即便出现来冲突,散列到每个槽里的数据也会比较平均,不会出现某个槽内数据特别多的情况

散列冲突

开放寻址法

概述

如果出现了散列冲突,我们就重新探测一个空闲位置,将其插入

优点

  • 开放寻址法不像链表法,需要拉很多链表
  • 散列表中的数据都存储在数据中,可以有效利用CPU缓存加快查询速度而且这样实现的散列表,序列化起来比较简单,链表法包含指针,序列化起来就没那么容易

缺点

  • 删除数据的时候比较麻烦,需要特殊标记已经删除掉的数据
  • 所有数据都存储在一个数组中,比起链表法来说,冲突的代价更高
  • 所有,使用开放寻址解决冲突的散列表,装载因子的上限不能太大。这也导致这种方法比链表法更浪费内存空间

方法

线性探测(Linear Probing)

当往散列表中插入数据时,如果某个数据经过散列函数之后,存储位置已经被占用了,我们就从当前位置开始,依次往后查找,看是否有空闲位置,直到找到为止

二次探测(Quadratic probing)

线性探测每次探测的步长是1,那它探测的下标序列就是hash(key) + 0, hash(key) + 1, hash(key) + 2 …

而二次探测探测的步长就变成原来的“二次方”, 也就是说,它探测的下标序列就是 hash(key) + 0, hash(key) + (1 ^ 2), hash(key) + (2 ^ 2)

双重散列(Double hashing)

不仅要使用一个散列函数。我们使用一组散列函数 hash1(key), hash2(key), hash3(key)

我们先用第一个散列函数,如果计算得到的存储位置已经被占用,再用第二个散列,依次类推找到空闲的存储位置

装载因子(load factor)

不管采用那种方法,当散列表中空闲位置不多的时候,散列冲突的概率就会大大提高

为了尽可能保证散列表的操作效率,一般情况下,我们会尽快保证散列表中有一定比例的空闲槽位

状态因子概述及公式

散列表的装载因子 = 填入表中的元素个数 / 散列表的长度

装载因子来表示空位多少,状态因子越大,说明空闲位置越少,冲突越多,散列表的性能就会下降

如何解决装载因子过大的问题?

动态扩容

  • 重新申请一个更大的散列表,将数据搬移这个新的散列表中
  • 假设每次扩容我们都申请一个原来散列表大小两倍的空间。如果原来散列表的装载因子是0.8,那经过扩容之后,新散列表的装载因子因子就下降为原来的一半,变成了0.4

装载因子阀值的设置要权衡时间,空间复杂度

  • 如果内存空间不紧张,对执行效率要求很高,可以降低负载因子的阀值
  • 相反,如果内存空间紧张,对执行效率要求又不高,可以增加负载因子的值,甚至可以大于1

如何避免低效地扩容?

为了解决一次性扩容耗时过多的情况,我们可以将扩容操作穿插在插入操作的过程中,分批完成

当装载因子触达阀值之后,我们只申请新空间,但并不将老的数据搬移到新散列表中

当有新数据要插入时,我们将新数据插入新散列表中,并且从老的散列表中拿出一个数据放入新散列表中

每次插入一个数据到散列表,我们都重复上面的过程。经过多次插入操作之后,老的散列表中的数据就一点一点全部搬移到新散列表中

这时间的查询,为了兼容了新,老散列表中的数据,我们先从新散列表中查找,如果没有找到,再去老的散列表中查找

通过这样均摊的方法,将一次性扩容的代价,均摊到多次插入操作中,就避免一次性扩容耗时过多的情况。这种实现方式,任何情况下,插入一个数据的时间复杂度都是O(1)

链表法

概述

在散列表中,每个"桶(bucket)" 或者"槽(slot)" 会对应一条链条,所以散列值相同的元素我们都会放在相同槽位对应的链表中

当插入的时候,我们只需要通过散列函数计算出对应的散列槽位,将其插入到对应链表中即可,所以插入的时间复杂度O(1)

当查找删除一个元素时,我们同样通过散列函数计算出对应的槽,然后遍历链表查找或删除。那查找或删除操作的时间复杂度是多少呢?

  • 实际上,这两个操作的时间复杂度跟链表的长度k成正比,也就是O(k)
  • 对于散列比较均匀的散列函数来说,理论上讲, k = n / m,其中n表示散列中数据个数,m表示散列中“槽”的个数

优点

  • 链表法对内存的利用率比开放寻址法要高
  • 因为链表结点可以在需要的时候再创建,并不需要像开放寻址法那样事先申请好
  • 链表法比起开放寻址法,对大装载因子的容忍度更高
  • 开放寻址法只能适用装载因子小于1的情况
  • 接近1时,就可能会又大量的散列冲突,导致大量的探测,再散列等,性能会下降很多
  • 但是对于链表法,只要散列函数的只随机均匀,即便装载因子变成10,也就是链表的长度变长了而已,虽然查找效率有所下降,但是比起顺序查找还是快很多

缺点

  • 链表因为要存储指针,所有对于比较小的对象的存储,是比较消耗内存,还有可能会让内存的消耗翻倍
  • 而且,因为链表中的结点是零散分布在内存中,不是连续,所有对于CPU缓存是不友好的,这方面对于执行效率也有一定的影响
  • 总结
  • 基于链表的散列冲突处理方法比较合适存储大对象,大数据量的散列表
  • 而且,比起开放寻址法,它更加灵活,支持更多优化策略,比如用红黑树代替链表

工业级的散列表应该具有哪些特征?

要求

支持快速的查询,插入,删除操作

内存占用合理,不能浪费过多的内存空间

性能稳定,极端情况下,散列表的性能也不会退化到无法接受的程度

设计

设计一个合适的散列函数

定义装载因子阀值,并且设计动态扩容策略

选择合适的散列冲突解决方法

散列表的缺点和改进

缺点

  • 散列表这种数据结构虽然支持非常高效的数据插入,删除,查找操作,但是散列表中的数据都是通过散列函数打乱之后无规律存储的。也就是,它无法支持按照某种顺序快速地遍历数据
  • 如果希望按照顺序遍历散列表中的数据,那我们需要将散列表中的数据拷贝到数组中,然后排序,再遍历

改进

  • 因为散列表是动态数据结构,不停有数据的插入,删除,所以每当我们希望按顺序遍历散列表中的数据的时候,都需要先排序,那效率势必会很低
  • 为了解决这个问题,我们将散列表和链表(或跳表)结合一起使用
资料参考
  • [Data Structure & Algorithm] Hash那点事儿

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

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

相关文章

AI芯片战场的迁徙:从训练到推理的深度剖析

在人工智能技术的飞速发展中,AI芯片作为底层硬件支撑,一直是技术创新的核心推手。近年来,一个显著的行业趋势是,AI芯片的主战场正悄然从模型训练向推理应用转移。这一转变背后,蕴含着技术发展、市场需求、以及经济效益…

使用Spyder进行Python编程和代码调试

Spyder的官方网站是获取其最新版本和下载安装包的最安全和直接的方式。您可以访问以下网址来下载Spyder: 官方网站下载页面: Home — Spyder IDE 在这个页面上,您会看到不同的下载选项,根据您的操作系统(Windows, macOS, Linux&…

使用 Oracle SQL Developer 导入数据

使用 Oracle SQL Developer 导入数据 1. 导入过程 1. 导入过程 选择要导入数据的表, 然后单击右键,选择"导入数据", 浏览本地文件,选择正确的工作表, 按默认, 按默认, 根据情况修改&…

2. 机器学习概述

机器学习是对能通过经验自动改进的计算机算法的研究。 ---汤姆. 米切尔 1997 通俗来讲,机器学习就是让计算机从数据中进行自动学习,得到某种知识(或规律)。在早期的工程领域,机器学习也经常被称为模式识别(…

React 使用 Zustand 详细教程

前言 Redux、MobX 和 Context API 等技术的存在,使得管理大型应用的状态变得更加可行。本教程要深入探讨的是 Zustand —— 一个极简且高效的状态管理库,详细介绍如何在 React 项目中使用 Zustand 来管理状态。 什么是 Zustand? Zustand 是…

现在的AI大模型,业已进入到泛滥成灾的发展阶段

我们都知道,现在的AI大模型,可以说,业已进入到泛滥成灾的发展阶段。 但凡是一个科技玩家,基本上都会推出自己的大模型。 从某种意义上来讲,AI大模型业已成为一个前瞻性的战略角色,蜕变成为了一种标配角色…

guli商城业务逻辑-基础篇笔记

这里写目录标题 0.1 viscode设置用户代码片段1.实现多级菜单接口1.1 对接前端菜单1.2 对接网关接口解决跨域问题,如果不解决跨域,浏览器还是访问不了api1.3 把商品服务添加网关1.4 修改前端显示分类菜单1.5 给菜单添加删除修改功能1.5.1 删除功能的后端业…

Oracle 入门--前提

目录 1.sqlplus 2.dual是什么? 3.SQL语句的种类 4.Oracle是如何工作的 5.Oracle查看配置文件 6.修改配置文件 7.常用的参数设置 1.sqlplus 管理数据库:启动,关闭,创建,删除对象......查看数据库的运行状态&…

【分布式计算】java消息队列机制

消息队列是一种在不同组件或应用之间进行数据传递的技术,通常用于处理异步通信。它允许消息的发送者(生产者)和接收者(消费者)之间进行解耦。 概念 消息队列是一种先进先出(FIFO)的数据结构&…

中介子方程二十

X$XFX$XEXyXαXiX$XαXiXrXkXtXyX$XpXVX$XdXuXWXπX$XWXyXWX$XπXWXuXdX$XVXpX$XyXtXkXrXiXαX$XiXαXyXEX$XFX$XEXyXαXiX$XαXiXrXkXtXyX$XpXVX$XdXuXWXπX$XWXyXWX$XπXWXuXdX$XVXpX$XyXtXkXrXiXαX$XiXαXyXEX$XαXηXtXαX$XWXyX$XyXWX$XpXαXqXηX$XeXαXhX$XdX$XpX$XdX$…

Web前端开发12章:深入探索与实战解析

Web前端开发12章:深入探索与实战解析 在数字化浪潮的推动下,Web前端开发技术日新月异,成为了构建互联网应用的重要基石。本文将以12章的篇幅,从四个方面、五个方面、六个方面和七个方面,深入探索Web前端开发的精髓&am…

【INTEL(ALTERA)】Nios® II无法使用基于 Ubuntu 18.04.5 的 WSL 进行构建

现象 在使用 Ubuntu 18.04.5 构建 WSL 的Nios II处理器时,任何英特尔 Quartus Prime 软件版本都可能会看到此问题。 原因 这是因为在 Nios II Command Shell 中运行命令 “wslpath -u .”时返回值不同。 正常工作:命令返回”。故障:命令返回…

机器学习(V)--无监督学习(一)聚类

根据训练样本中是否包含标签信息,机器学习可以分为监督学习和无监督学习。聚类算法是典型的无监督学习,目的是想将那些相似的样本尽可能聚在一起,不相似的样本尽可能分开。 相似度或距离 聚类的核心概念是相似度(similarity)或距离(distance…

PyTorch 拼接与拆分-Tensor基本操作

拼接: cat, stack … 使用 cat 在指定维度 dim 上拼接: torch.cat(element_list, dim) >>> a torch.rand(2,3) >>> b torch.rand(1,3) >>> c torch.cat([a,b], dim0) >>> c.shape torch.Size([3, 3])使用 stack 在新增维…

嵌入式学习记录6.14(练习)

#include "mainwindow.h" #include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow) {ui->setupUi(this);this->resize(1028,783); //设置左侧背景QLabel *lab1new QLabel(this);lab1->…

uniapp使用vue3打包H5,android或ios加载白屏

前景介绍 按照uniapp官方文档介绍,根据步骤创建了使用Vue3的项目;执行命令npm run build:h5, 本地安装了http-server,打包之后的dist文件夹,执行http-server后,可以访问; 但是使用Android或者ios进行本地加…

【内存管理之堆内存】

1.栈上的基元 2.栈上的聚合对象 3.手动分配和释放 4.分配堆内存 5.数组内存分配和释放 6.数组内存分配 7.不要使用野指针 8.黑暗时代

STM32理论 —— μCOS-Ⅲ(2/2):时间管理、消息队列、信号量、任务内嵌信号量/队列、事件标志、软件定时器、内存管理

文章目录 9. 时间管理9.1 OSTimeDly()9.2 OSTimeDlyHMSM()9.3 OSTimeDlyResume()9.4 延时函数实验 10. 消息队列10.1 创建消息队列函数OSQCreate()10.2 发送消息到消息队列函数(写入队列)OSQPost()10.3 获取消息队列中的消息函数(读出队列)OSQPend()10.4 消息队列操作实验 11. …

12 款 Android 照片恢复应用程序列表

丢失难忘的照片总是令人痛苦的。如果软件崩溃或意外删除,Android 设备上的照片也可能会丢失。这时照片恢复应用程序就派上用场了。查看我们为 Android 收集的顶级照片恢复应用程序。 但是,您不会想为自己选择任何照片恢复应用程序。因此,我们…

解决小程序的异步请求问题

解决小程序的异步请求问题,可以从多个方面入手,以确保请求的顺畅执行和错误处理。以下是一些主要的解决方法和策略: 1. 确保网络连接正常 检查网络连接:首先,确保用户的设备已连接到互联网,并且网络连接稳…