前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >怒肝 JavaScript 数据结构 — 散列表篇(三)

怒肝 JavaScript 数据结构 — 散列表篇(三)

作者头像
杨成功
发布于 2022-09-22 06:18:26
发布于 2022-09-22 06:18:26
55500
代码可运行
举报
文章被收录于专栏:前端砍柴人前端砍柴人
运行总次数:0
代码可运行

大家好,我是杨成功。

前两篇我们分别介绍了什么是散列表,如何动手实现一个散列表,并且用“分离链接法”解决了散列表中散列值冲突的问题。这一篇我们介绍另一个方案:线性探查法

如果你还不清楚散列表,请先阅读前两篇:

线性探查法比分离链接法更优雅一些,也不会额外占用内存。

线性探查法

在计算机世界中,某个值的放缩或叠加被称为线性。顾名思义,线性探查法是指当散列值重复的时候,试着将散列值叠加,直到其变成唯一的值。

比如你得到一个 hash 值,你想以这个值为 key 向散列表中添加新元素。如果这个 key 在散列表中已存在,那么你可以尝试 hash + 1;如果依然存在,继续尝试 hash + 2,直到这个值变成唯一的 key 再进行添加。

如下图,索引值(key)与散列值(hash)的关系如下:

理论就是这样,具体到实现方式,有两种:

  • 软删除
  • 移动元素

软删除并不是真的删除,只是将 key 对应的 value 标记为已删除,这样的好处是重要数据被保存了下来,为后期使用或恢复提供了可能。坏处么也显而易见,散列表中会堆积越来越多的 key 值,数量庞大时查询效率就会变低。

移动元素的方法会直接删除某个键值对,但是这样会造成 hash 值断开,产生空位置。所以在删除的时候要做特殊处理,将符合条件的键值对填充到这个空位置。

我们这里只介绍第二种 移动元素 方案的实现代码。

首先是创建基本的类结构,继承 HashMap 类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class HashTableLinearProbing extends HashMap {
  constructor() {
    super()
    this.table = {}
  }
}

依然是重写三个方法。

put 方法

put 方法用来添加元素,重写如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
put(key, value) {
  if(key && value) {
    let pos = this.hashCode(key);
    while(this.table[pos]) {
      pos++;
    }
    this.table[pos] = new ValuePair(key, value);
    return true;
  }
  return false;
}

这个代码里检测 hash 值是否已经是对象的 key 值,并将其作为循环条件。如果 key 已存在则自增一,直到 hash 值变成对象唯一的 key,我们再创建键值对。

这样一来,我们相当于“跳过”了已存在的 key,添加元素时就避免了覆盖已有的值。

get 方法

上一个方法插入了元素,那么接下来用 get 方法获取它吧。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
get(key) {
  let pos = this.hashCode(key);
  if(this.table[pos]) {
    if(this.table[pos].key === key) {
      return this.table[pos].value
    }
    let index = pos + 1;
    while(this.table[index] && this.table[index].key !== key) {
      index++;
    }
    if(this.table[index] && this.table[index].key === key) {
      return this.table[index].value
    }
  }
  return undefined;
}

这个方法中,首先获取 key 的 hash 值,然后检测对象中是否存在这个属性,不存在直接返回 undefined。

如果存在的话,就会匹配到一个键值对,此时还要分两种情况

如果键值对的 key 和参数 key 的值一样,那就说明找准了,直接返回键值对的 value 即可。

如果不一样,那就说明参数 key 对应的这条数据在创建时遇到了 hash 重复的情况,将 hash 进行了自增后才创建的数据,所以我们匹配到的数据不准确。

那怎么办呢?自然也是将解析到的 hash 自增,逐渐向后查找数据,直到找到两个 key 相匹配的那个键值对,这就是我们要找的数据。

注意:在 hash 递增时,必须确保每次的新索引在散列表中都有匹配的数据,否则会终止循环,直接返回 undefined

remove 方法

remove 方法与 get 方法基本相同,核心都是找到某个元素。只不过 get 方法找到之后会返回,remove 方法则会删除。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
remove(key) {
  let pos = this.hashCode(key);
  if(this.table[pos]) {
    if(this.table[pos].key === key) {
      delete this.table[pos]
      this.verifyRemoveSideEffect(key, pos)
      return true
    }
    let index = pos + 1;
    while(this.table[index] && this.table[index].key !== key) {
      index++;
    }
    if(this.table[index] && this.table[index].key === key) {
      delete this.table[index]
      this.verifyRemoveSideEffect(key, index)
      return true
    }
  }
  return false;
}

上述代码中有一处需要特别说明,就是在删除完成之后,调用一个 verifyRemoveSideEffect 方法,这是为什么呢?

我们在上面写过一个注意事项,在索引递增时必须确保新索引在散列表中有对应的数据,否则影响 key 的查询。

这就要求在删除元素之后,如果在这个位置的后面有另一个元素 小于等于 被删元素的 hash 值,我们得把这个元素移动到被删除的位置,避免出现空位。

为什么?因为在被删位置之后,小于等于被删元素 hash 的其他元素在被检索时,会将 hash 值不断递增,因此必然会经过被删除的位置,此时该位置是一个空位,因为被删了嘛,所以检索会返回 undefined,事实上那个元素是存在的,只不过查询时被空位截断了。

所以我们要通过这个函数在必要时移动元素的位置:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
verifyRemoveSideEffect(key, pos) { 
  const hash = this.hashCode(key); 
  let index = pos + 1; // {2} 
  while (this.table[index] != null) { // {3} 
    const posHash = this.hashCode(this.table[index].key); // {4} 
    if (posHash <= hash || posHash <= pos) { // {5}
      this.table[pos] = this.table[index]; // {6} 
      delete this.table[index]; 
      pos = index; 
    } 
    index++; 
  }
}

verifyRemoveSideEffect 方法接收两个参数:被删除元素的 key 和被删除的位置 pos。

首先,因为 key 对应的位置已经被删除了,所以在我们在 {2} 处将 pos 加一,用于获取被删位置的下一个位置的索引。

接下来判断 index 处是否有元素。如果有,则获取这个元素的 hash 值 posHash,如果 posHash 小于等于被删元素的 hash,或者小于等于被删位置(递增后的 hash),则进行位置移动,即填充新位置,删除旧位置。

将这个过程循环,使被删元素之后满足条件的元素全部前移一位,就解决了空位的问题。

使用线性探查

上面重写了三个方法后,我们来使用这个 HashTableLinearProbing 类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var hashtable = new HashTableLinearProbing();
hashtable.put("name", "杨成功");
hashtable.put("mane", "成功杨");
console.log(hashtable.table);

浏览器控制台打印结果如下:

你看,namemane 这两个字符串的 hash 都是 21,冲突时后面的自然递增。

数据存储没有问题,我们再看数据获取结果如何:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
hashtable.get("name");
hashtable.get("mane");
hashtable.get("sex");

结果如下:

不错,也没问题。最后一步我们删除 name 这条数据,看对结构和查询有什么影响:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
hashtable.remove("name");
console.log(hashtable.table);
hashtable.get("mane");

结果如下:

看结果,删除 name 之后,mane 这条数据的 key 从 22 变成了 21,说明已经移动到了被删除的位置。最后一步查询也正常,说明删除逻辑没问题。

总结

本篇介绍了如何用 线性探查法 解决 hash 冲突的问题,并附上了实现代码。经过三篇的反复学习,相信你对散列表已经娴熟于心了。

下一篇,我们介绍一个运算基础 —— 递归。

本文来源公众号:程序员成功。这是学习 JavaScript 数据结构与算法的第 19 篇,本系列会连续更新一个月。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员成功 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
怒肝 JavaScript 数据结构 — 散列表篇(二)
上一篇我们介绍了什么是散列表,并且用通俗的语言解析了散列表的存储结构,最后动手实现了一个散列表,相信大家对散列表已经不陌生了。
杨成功
2022/09/22
5160
怒肝 JavaScript 数据结构 — 散列表篇(二)
【算法】272-每周一练 之 数据结构与算法(Dictionary 和 HashTable)
这些都是数据结构与算法,一部分方法是团队其他成员实现的,一部分我自己做的,有什么其他实现方法或错误,欢迎各位大佬指点,感谢。
pingan8787
2019/07/25
7230
【算法】272-每周一练 之 数据结构与算法(Dictionary 和 HashTable)
《学习JavaScript数据结构与算法》-- 5.字典和散列表(笔记)
在字典中,存储的是[键, 值]对,其中键名是用来查询特定元素的。字典和集合很相似,集合以[值, 值]的形式存储元素,字典则是以[键, 值]的形式来存储元素。字典也称作映射、符号表或关联数组。
爱学习的程序媛
2022/04/07
8000
《学习JavaScript数据结构与算法》-- 5.字典和散列表(笔记)
怒肝 JavaScript 数据结构 — 散列表篇(一)
上一篇我们一篇搞定了字典,这篇呢我们学习一个与字典非常相似的数据结构 —— 散列表。散列表与字典基本一致,区别是字典存储的 key 是字符串,而散列表是一个数值(哈希值)。
杨成功
2022/09/22
6080
怒肝 JavaScript 数据结构 — 散列表篇(一)
用js来实现那些数据结构12(散列表)
  上一篇写了如何实现简单的Map结构,因为东西太少了不让上首页。好吧。。。   这一篇文章说一下散列表hashMap的实现。那么为什么要使用hashMap?hashMap又有什么优势呢?hashMap是如何检索数据的?我们一点一点的来解答。   在我们学习一门编程语言的时候,最开始学习的部分就是循环遍历。那么为什么要遍历呢?因为我们需要拿到具体的值,数组中我们要遍历数组获取所有的元素才能定位到我们想要的元素。对象也是一样,我们同样要遍历所有的对象元素来获取我们想要的指定的元素。那么无论是array也好,o
zaking
2018/05/02
1.9K0
用js来实现那些数据结构12(散列表)
Java数据结构与算法解析(十二)——散列表
散列表就是一种以 键-值(key-indexed) 存储数据的结构,我们只要输入待查找的值即key,即可查找到其对应的值。
老马的编程之旅
2022/06/22
1.2K0
Java数据结构与算法解析(十二)——散列表
数据结构小记【Python/C++版】——散列表篇
散列表通常使用顺序表来存储集合元素,集合元素以一种很分散的分布方式存储在顺序表中。
Coder-ZZ
2023/02/23
6170
数据结构小记【Python/C++版】——散列表篇
查找-散列表(哈希表)详解篇
学编程的小程
2023/10/11
3820
怒肝 JavaScript 数据结构 — 字典篇
经过上一篇的学习,数据结构的集合部分已经完结了。那么下面我们又要认识一个新的数据结构,它的名字相信你绝不陌生,它就是字典。
杨成功
2022/09/22
5930
散列表
http://blog.csdn.net/yyxaf/article/details/7527878 搜索关键词:散列函数、散列表、哈希函数、哈希表、Hash函数、Hash表 散列方法不同于顺序查找、二分查找、二叉排序树及B-树上的查找。它不以关键字的比较为基本操作,采用直接寻址技术。在理想情况下,无须任何比较就可以找到待查关键字,查找的期望时间为O(1)。 散列表的概念 1、散列表 设所有可能出现的关键字集合记为U(简称全集)。实际发生(即实际存储)的关键字集合记为K(|K|比|U|小得多)。 散列方
用户1624346
2018/04/17
1K0
散列表结构 字典与集合
散列表(Hash Table)结构是字典(Dictionary)和集合(Set)的一种实现方式。散列算法的作用是尽可能快地在数据结构中找到一个值。在散列表上插入、删除和取用数据都非常快,但是对于查找操作来说却效率地下
py3study
2020/01/02
1K0
数据结构-散列表(上)
Word 这种文本编辑器你平时应该经常用吧,那你有没有留意过它的拼写检查功能呢?一旦我们在 Word 里输入一个错误的英文单词,它就会用标红的方式提示“拼写错误”。Word 的这个单词拼写检查功能,虽然很小但却非常实用。你有没有想过,这个功能是如何实现的呢?
acc8226
2022/05/17
8830
数据结构-散列表(上)
数据结构于JS也可以成为CP(七)散列
Hello小伙伴们大家好~~今天带来的是散列,这个其实是一个很重要然而很多人不是很理解的技术。散列是什么呢,是一种数据存储技术,能够达到经过散列后的数据可以快速地插入或取用,这种结构就是散列表。
萌兔IT
2019/07/26
5590
算法和数据结构: 十一 哈希表
在前面的系列文章中,依次介绍了基于无序列表的顺序查找,基于有序数组的二分查找,平衡查找树,以及红黑树,下图是他们在平均以及最差情况下的时间复杂度:
yaphetsfang
2020/07/30
9910
算法和数据结构: 十一 哈希表
HashMap?面试?我是谁?我在哪
现在是晚上11点了,学校屠猪馆的自习室因为太晚要关闭了,勤奋且疲惫的小鲁班也从屠猪馆出来了,正准备回宿舍洗洗睡,由于自习室位置比较偏僻所以是接收不到手机网络信号的,因此小鲁班从兜里掏出手机的时候,信息可真是炸了呀,小鲁班心想,微信群平时都没什么人聊天,今晚肯定是发生了什么大事,仔细一看,才发现原来是小鲁班的室友达摩(光头)拿到了阿里巴巴JAVA开发实习生的offer,此时小鲁班真替他室友感到高兴的同时,心里也难免会产生一丝丝的失落感,那是因为自己投了很多份简历,别说拿不拿得到offer,就连给面试邀的公司也都寥寥无几,小鲁班这会可真是受到了一万点真实暴击,不过小鲁班还是很乐观的,很快调整了心态,带上耳机,慢慢的走回了宿舍,正打算准备向他那神室友达摩取取经。
芋道源码
2019/09/17
5840
HashMap?面试?我是谁?我在哪
一篇文章带你了解Hashtable类
1.Hashtable类描述的是散列表,也称哈希表,它通过映射集合的方式,将一个元素通过其关键字与其存储位置相关联。散列表使用关键字查找元素,而不是使用线性搜索技术来查找元素,从而使查找性能大幅度提升。
Java进阶者
2022/01/18
2950
一篇文章带你了解Hashtable类
HashMap?面试?我是谁?我在哪?
来源:cnblogs.com/zhuoqingsen/p/HashMap.html
Java团长
2019/01/23
7730
HashMap?面试?我是谁?我在哪?
滚雪球学Java(65-1):Java语言中的Hashtable:从入门到精通
咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE相关知识点了,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~
bug菌
2024/08/02
920
滚雪球学Java(65-1):Java语言中的Hashtable:从入门到精通
HashMap 实现及原理
HashMap是一个散列桶(数组和链表),它存储的内容是键值对(key-value)映射 HashMap采用了数组和链表的数据结构,能在查询和修改方便继承了数组的线性查找和链表的寻址修改 HashMap是非synchronized,所以HashMap很快 HashMap可以接受null键和值,而Hashtable则不能(原因就是equlas()方法需要对象,因为HashMap是后出的API经过处理才可以) 2、HashMap的工作原理是什么?
爱撸猫的杰
2019/05/10
8830
HashMap 实现及原理
Python的字典与散列表
说明: 本文是上一篇《Python的可散列对象》的续篇,两者都是对《Python大学实用教程》和《跟老齐学Python:轻松入门》有关字典内容的进阶知识。
老齐
2021/03/11
4.7K0
相关推荐
怒肝 JavaScript 数据结构 — 散列表篇(二)
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文