文章出处:极客时间《数据结构和算法之美》-作者:王争。该系列文章是本人的学习笔记。

1散列表的由来

从数组随机访问特性说起。
 数组的随机访问特性是:数组a,a[5]可以直接访问到数组的第6个元素。这就类似于在下标和数组对应的值之间建立了映射关系。
 散列表用的是数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的一种扩展,由数组演化而来。没有数组就没有散列表。
 应用在实践中。例如有100名学生,每个学生有一个学号,学号是从0到99,数组score存放每个学生的成绩,score[0]表示学号是0的学生的成绩,score[1]表示学号是1的学生的成绩… 学号->数组下标->学生成绩。f1(学号)=数组下标。
 现在更进一步,学号的规则是“年级+班级+数字”,例如“50137”表示5年级1班,37还是和上面的含义一样。那么我们就可以写函数把学号和数组下标映射起来,查找学生成绩。依然是:学号->数组下标->学生成绩。f2(学号)=数组下标。f1与f2不同。
 这里的学号就是key,学生成绩是value,f1、f2是散列函数。散列函数计算得到的值是哈希值。

2散列函数

散列函数一般表示为hash(key)
 散列函数的三点要求。
 1 散列值是非负的。
 2 如果key1=key2,那么hash(key1)=hash(key2)。
 3 如果key1̸=key2key1\not= key2key1̸​=key2,那么hash(key1)̸=hash(key2)hash(key1)\not=hash(key2)hash(key1)̸​=hash(key2)。

3散列冲突

定义
 在实际中因为数组存储空间的限制,要想做到key值不同的时候哈希值不同,几乎很难满足。这个时候就产生了散列冲突,也要哈希冲突。换句话说就是数组只有10个下标,学生有5个人,但学号是随机的,怎么映射,能够快速访问到学生成绩。

3.1开放寻址法

开放寻址法=可以改变哈希值的解决方法
 开放寻址法的核心思想是:如果发生了散列冲突,就重新找一个空闲位置插入数据。怎么找呢?三种方法:线性探测、平方探测、再哈希。
 

线性探测

如果hash(key)=7,且数组score[7]已经被占用,那就探测8的位置是不是被占用,9的位置是不是被占用,0的位置是不是被占用…一直找到空闲位置。探测顺序是(hash(key)+0)%size,(hash(key)+1)%size,(hash(key)+2)%size,(hash(key)+3)%size…

平方探测

如果hash(key)=7,且数组score[7]已经被占用,那就探测8的位置是不是被占用,1的位置是不是被占用,8的位置是不是被占用…一直找到空闲位置。探测顺序是(hash(key)+0)%size,(hash(key)+1)%size,(hash(key)+4)%size,(hash(key)+9)%size…
 数组大小是有限的,再探测的时候一定要对数组大小size取余。

再哈希

再哈希是指当发生冲突的时候,再找一个散列函数计算,探测空间是不是被占用,如果被占用,继续再找一个散列函数计算。
 线性探测和平方探测其实是再哈希的特殊形式。再哈希的函数f(x)=x,或者 f(x)=x2f(x)=x^2f(x)=x2

查找


 查找过程和插入过程类似。我们通过散列函数求出要查找元素key值的哈希值,然后比较数组中下标为散列值的key和要查找的元素key值。如果相等则查找到,否则继续探测查找,直到数组中出现空闲位置。
 我的思考:散列表中存储的是value值。例如最上面的例子,学号就是数组下标的时候,散列表就是数组,存储的是学生成绩。当学号变成随机的时候,散列表中存储的是学生实体。包含学号和学生成绩。查找的时候 比对的是key值是否相同。学号->哈希值->学生成绩。

删除操作

当需要删除数据的时候,需要注意不能直接将数组的值置为空。因为在查找过程中出现空闲位置就停止不找了。这样查找就不准确了。可以将该位置放置删除标记。

3.2 链表法

所有哈希值相同的元素放在同一个槽(slot)或者链表内,形成一个链表。
 当插入的时候只需要计算插入元素key值的哈希值,找到对应的slot,添加到链表中即可。时间复杂度O(1)。
 当查找或者删除的时候同样计算哈希值,添加或者删除链表中的元素。时间复杂度与链表的长度k成正比,所以是O(k)。所以更希望哈希值的分布式均匀的。

4散列函数与内存

4.1 散列函数的设计要求

散列函数需要满足:
1 设计简单高效,计算时间短。
2 生成的值要随机且均匀。

数据分析法设计散列函数。例如学号复杂的例子,我们分析学号的特征设计散列函数。
 key为字符串类型的可以使用字符串进位相加的方法,然后再跟散列表大小取余。例如"nice"的哈希值为:

hash("nice")=(("n" - "a") * 26*26*26 + ("i" - "a")*26*26 + ("c" - "a")*26+ ("e"-"a")) / 78978

还有其他设计函数的方法。例如:直接寻址法、平方取中法、随机数法等。

4.2动态装载因子

散列表中元素个数m与散列表长度的比值就是装在因子:

装载因子=元素个数长度装载因子=\dfrac{元素个数}{长度}装载因子=长度元素个数​
 散列表中随着数据的插入和删除状态因子发生变化,成为动态装载因子。

4.3 扩容、缩容

当加载因子不断变大的时候,发生散列冲突的概率就会增加,操作就会变慢。这时候可以像动态数组一样做扩容。
 一般散列表扩容会在在当前长度的基础上再扩一倍。扩容之前装载因子是0.8,扩容之后就是0.4。散列表扩容与数组扩容不同的地方是扩容之后,因为散列表大小发生变化,散列值也可能发生变化。例如原来key=6的元素,哈希值是1,扩容后哈希值是10。
 支持动态扩容散列表的插入操作的平均时间复杂度,按照摊还分析法是O(1)。
 当散列表随着删除操作,装载因子会越来越小。如果对内存不敏感,浪费一些也可以,可以不采取操作。如果要求内存尽可能小,可以对散列表缩容。
 装载因子需要权衡时间和空间。操作时间优先,可以允许浪费一定的空间。

4.4避免低效扩容

低效扩容是因为一次扩容,重新计算哈希值,搬移数据导致的。如果原来的数据有1G大小,这一次搬移操作就很费劲。
 我们可以采集的策略是将原始n个数据的搬移工作分配到n次插入操作中。每次插入只将原来散列表中的一个值搬移到新散列表。这样在任何时候插入操作的时间复杂度都是O(1)。
 

5 如何选择冲突解决方法

开放寻址法
 优点:底层结构是数组,可以充分利用CPU缓存加快查询速度;利于序列化。
 缺点:删除数据需要标记,比较麻烦;所有数据在同一个数组,冲突的代价更大,所以会浪费更多的内存空间。
 当数据量比较小、装载因子比较小的时候,选择开放寻址法。例如Java的ThreadLocalMap。

链表法
 优点:内存利用率比开放寻址法高,因为节点可以在需要的时候才创建而不是提前创建。
 可以容忍装载因子大于1。当装载因子大于1,查找速度与每个槽对应的链表长度有关,但是比全链表查询效果要高。我们可以将链表改为跳表或者红黑树,这样即便出现散列冲突的极端情况,时间复杂度也是O(logn)。
 当数据对象比较大、数据量比较大的时候使用链表法。

6工业级散列表举例分析

6.1 Java的HashMap

1 初始大小16,可以指定。
2 装载因子0.75,当装载因子大于0.75的时候动态扩容。
3 冲突解决方法:链表法。当链表长度超过8,使用红黑树。
4 hash函数:

int hash(Object key) {int h = key.hashCode();return (h ^ (h >>> 16)) & (capitity -1); //capicity 表示散列表的大小
}

hashcode返回的是key的hash code。

6.2 工业级散列表应该具有的特征

1 支持快速查询、插入、删除;
2 内存占用合理;
3 性能稳定,在极端情况下也不会出现速度不可接受的情况。

6.3 工业级散列表设计思路

1 散列函数设计合适。
2 装载因子设置合理,不过多浪费空间,动态扩容策略合适。
2 散列冲突解决策略。

7散列表的应用

7.1 word中错误单词提示功能

把英文单词加载到内存中,用散列表存储。当用户输入一个词的时候,在散列表中查找是否存在,
常用因为单词20万,假设单词平均长度10个字母。一个单词占用10个字节的内存,所有单词加载大约是2M内存。放大10倍20M。内存可用。

7.2 假设有10条url访问记录,怎么按照访问次数给url排序

对url取哈希值,在散列表存储每个hash值的访问次数。最后再排序。

数据结构四——散列表(上)相关推荐

  1. 利用开放定址法实现散列表的创建、插入、删除、查找操作_快速入门数据结构:散列表(上)...

    散列表与散列算法 散列表的英文叫"Hash Table",我们平时也叫它"哈希表"或者"Hash 表",散列表用的是数组支持按照下标随机访问 ...

  2. 数据结构四——散列表(下)

    文章出处:极客时间<数据结构和算法之美>-作者:王争.该系列文章是本人的学习笔记. 7 散列表+链表的应用 很多情况下散列表会和链表一起使用.散列表可以通过key查找value.链表可以按 ...

  3. 第八章:数据结构四兄弟——列表(上),痴月熊学python

    痴月熊学Python 文章目录 痴月熊学Python 往期文章 前言 一.创建列表 二.读取列表 三.删除和修改元素 总结 系列文章 往期文章 第一章:Python-新人报道 第二章:小学生都会的数学 ...

  4. 数据结构之散列表(七)

    前言 一.什么是散列表 散列表是如何组织数据的呢? 散列表的基本概念 二.Hash算法的设计 什么是Hash算法 Hash算法的应用场景 三.散列表冲突的解决 1. 开放寻址法 2. 链表法 3. 开 ...

  5. 数据结构(55) 散列表(哈希表,hash table,hash map)

    目录 1.散列表的基本概念 2.散列函数的构造方法 3.常用的散列函数 3.1.直接定址法 3.2.除留余数法 3.3.数字分析法 3.4.平方取中法 3.5.乘法哈希法(The Multiplica ...

  6. 【数据结构】散列表知识点

    散列存储的特性 散列存储:散列表,采用的存储方式是散列存储.那么何为散列存储呢?散列存储是根据元素的关键字直接计算出该元素的存储地址,又称哈希(Hash)存储.采用散列存储的方式存储数据时,具备的优点 ...

  7. 第九章:数据结构四兄弟——列表(下),痴月熊学python

    痴月熊学Python 文章目录 痴月熊学Python 往期文章 前言 一.对象.方法() 二.列表方法 2.1.列表内置函数 2.2.列表追加元素 2.3.列表插入元素 2.4.列表删除元素 三.列表 ...

  8. c++数据结构:散列表(哈希)

    记录的存储位置与关键字之间存在对应关系,对应关系---hash函数 Loc(i)=H(keyi) 假设散列函数为H(key)=k 数据为:1 2 5 8 9 6 7  访问的话可以通过下标来访问数据. ...

  9. 查找、插入、删除都很快的数据结构(散列表vs红黑树vs跳表)

    散列表 散列表的插入.删除.查找操作的时间复杂度可以做到常量级的 O(1),非常高效. 平衡二叉查找树(红黑树) 二叉查找树在比较平衡的情况下(红黑树是一种平衡二叉树),插入.删除.查找操作时间复杂度 ...

最新文章

  1. access“idno”字段改为文本型_结构化文本计算示例(一)
  2. Bypass WAF Cookbook
  3. POJ 2455Secret Milking Machine(二分+网络流之最大流)
  4. stun server、turn server、coturn server安装与使用
  5. IntelliJ IDEA for Mac 直接将模块硬盘上的根目录删除会怎么样
  6. JavaMelody开源系统性能监控软件:
  7. 如何实现插入数据时自动更新另外一个表的内容
  8. squirrelmail+change_sqlpass 认证 问题
  9. 软件开发整理的一些工具
  10. 基于ASP.NET AJAX的WebPart开发与部署
  11. 外包以小时计算金额的费用_全了!各大税种的计算公式,建议收藏!
  12. WPS启动不再默认展示“稻壳”页面 - 去除稻壳的方法
  13. foxmail超大附件服务器文件怎么删,电脑中使用Foxmail发送超大附件的方法
  14. C# 创建桌面快捷方式
  15. VMWare16上安装CentOS 7镜像
  16. iis部署网站java_值得分享的IIS部署网站详细步骤
  17. 使用PowerCli来创建自定义ESXi ISO镜像
  18. zk-snark的算法详解
  19. word文档在线编辑推荐超级文档
  20. 塞班java手机qq浏览器下载_手机QQ浏览器 for Symbian S60v3

热门文章

  1. vscode 1.9.11 和pycharm 5.0.4 输入os.getcwd()后输出不相同
  2. 第二阶段冲刺站立会议09
  3. 双频无线网安装设置(5g ) for linux
  4. 思维探索者:从问题到答案的思维过程 像侦探一样思考
  5. a标签的href与onclick中使用js的区别
  6. 结对项目开发-电梯调度
  7. linux 下zip文件的压缩和解压
  8. Android 编码规范:(六)消除过期的对象引用
  9. C# MVC使用阿里云对象存储加快图片加载速度(一)
  10. linux centos/redhat mysql8.0安装(汇总贴)