JavaScript Hashmap散列算法

news2025/4/16 9:48:38

文章目录

  • 前言
  • 一、什么是散列表
  • 二、为何使用散列算法
  • 三、实现散列算法
    • 1.字典结构
    • 2.散列函数
    • 3.put 设置/更新
    • 4. 获取值
  • 四、使用HashMap处理冲突
    • 1.分离链接
    • 2.线性探查
  • 总结


前言


一、什么是散列表

散列表是字典(Dictionary)的一种实现.
集合以[值, 值]形式存储, 字典则以[键, 值]对形式, 其中键名用于查询, 字典也称作映射, 符号表关联数组.

JavaScript中允许使用方括号获取对象的属性, 将属性名作为"位置"传入即可, 常见的用法:

const obj = {
  key: 'val';
}

console.log(obj[key]);

ECMAScript2015也就是ES6中实现了Map, 即此处字典结构.
虽然ES6Map可以使用String以外的类型作键(因为Map可以基于分别存储键和值的两个数组及其方法实现, 内部的结构是可预测的, 这也是它iterable的原因), 但是理想的字典结构应该使用String类型作键, 这会让查找变得更简单.

参考现实中的字典, 应当具备单词及其释义, 还有一套查找目录, 为了在字典中快速检索值, 将一个key(单词)作为索引, 为了保存信息依旧需要原始的key(单词), 因此他不能只是一个简单的对象的结构, 不能是这样:

{
  key1: 'val1',
  key2: 'val2',
  key3: 'val3',
  key4: 'val4'
}

应为:

{
  key1: { key1: 'val1' },
  key2: { key2: 'val2' },
  key3: { key3: 'val3' },
  key4: { key1: 'val4' }
}

table作为字典:

在这里插入图片描述

字典中解释单词的视觉体验基本是一个大写的单词后面跟着释义.
你可以把最外层的key看作用来给目录查找的索引, 找到之后, 这个对象的key作为单词大写, 后面值作为释义.


二、为何使用散列算法

要在数据结构中找到一个值, 采用迭代整个数组来应对, 时间复杂度为O(n), n为键值对的数量, 需要检查每一个键值对的匹配情况.
而如果使用散列函数就知道值的具体位置, 快速检索到该值. 散列函数的作用是给定一个键值, 然后返回该值在表中的位置, 参考上方典型的Dictionary数据结构.

在关系型数据库(比如MySQL)中创建一个新的表时, 也可以通过创建索引来更快的查询到记录的key.
JavaScript语言内部也使用散列表来表示每个对象, 此时对象上的方法和属性被存储为key对象类型, 每个key指向对应的对象成员
我大胆猜测是这样的:

{
  pro: { pro: 'val' },
  add: { add: f() },
  peek: { peek: f() }
}

参考第一章典型字典结构的举例.


三、实现散列算法

目标是建立一个人名与邮箱对应的散列表, 索引会是人名字母转ASCII之和, 但是为了保存数据, 内部的键仍需为人名, 会是这样的结构:

{
  '685': { 'Gandalf': 'gandalf@email.com' },
  '399': { 'John': 'johnsnow@email.com' },
  '645': { 'Tyrion': 'tyrion@email.com' },
}

这里直接采用理想化的模式, 键都用String, 那么先搞一个转String的方法:

function defaultToString(item) {
  if (item === null) {
    return 'NULL';
  } else if (item === undefined) {
    return 'UNDEFINED';
 } else if (typeof item === 'string' || item instanceof String) {
   return `${item}`;
 }
 return item.toString();
}

仅示例, 这并不是一个完美的方法, 用toString()处理复杂类型很糟糕.


1.字典结构

将会以table作为表体.

class HashMap {
  constructor (toStr = defaultToString) {
    this.toStr = toStr;
    this.table = {};
  }
}

2.散列函数

指明一个索引,然后返回值在表中的地址, 仅返回地址, 不是返回对应的值.
需要完成人名到ASCII的转换.

getHashCode (key) {
  if (typeof key === 'unmber') return key;
  const tableKey = this.toStr(key);
  let hash = 0;
  for (let i = 0; i < tableKey.length; i++) {
    hash += tableKey.charCodeAt(i);
  }
  return hash % 37;
}

hashCode(key) {
  return getHashCode (key);
}

在此处它需要返回的地址即该人名的ASCII码之和.


3.put 设置/更新

大多数编程语言会将对HashMap进行set操作的函数命名为put, 此遵循相同的命名方式.

put(key, value) {
  if (key != null && value != null) {
    const position = this.hashCode(key);
    this.table[position] = new ValuePair(key, value);
    return true;
  }
  return false;
}
class ValuePair {
  constructor(key, value) {
     this.key = key;
     this.value = value;
  }
  toString() {
    return `[#${this.key}: ${this.value}]`;
  }
}

4. 获取值

用人名ASCII之和, 即hashCode提供的地址, 获取对应值.

get(key) {
  const valuePair = this.table[this.hashCode(key)]; // this.table['685'];
  return valuePair == null ? undefined : valuePair.value;
}

四、使用HashMap处理冲突

1.分离链接

这种方法为散列表的每一个位置创建一个链表, 并将索引匹配的元素存放在内部, 如果该索引对应的位置受到了多次添加(即会存在冲突, 值覆盖的情况), 那么将这些该被添加的值全部加入该位置的链表, 而非直接覆盖原值, 缺点是多出的大量链表会占用额外的存储空间, 结构大概如此:

{
  '685': [{ 'Gandalf': 'gandalf@email.com' }],
  '10': [{ 'Nathan': 'Nathan@email.com' }, { 'Sargeras': 'Sargeras@email.com' }],
  '7': [{ 'Jack': 'Jack@email.com' }, { 'Athelstan': 'tyrion@email.com' }]
}

放在本处, 有些人名不同但是ASCII之和相同, 会出现冲突.

分离链接法需要在原HashMap的操作方法上做出改动:
增加方法需要在该位置为null即首次添加时添加一个链表, 再把值放进去, 如果不是第一次添加那么直接加入到链表内.

put (key, value) {
  if (key !== null && value !== null) {
    const position = this.hashCode(key);
    if (this.table[position] !== null) {
      this.table[position] = new LinkedList(); // 可以看作一个带有自己操作方法的数组
    }
    this.table[position].push(new ValuePair(key, value));
    return true;
  }
  retrun false;
}

获取方法需要在该位置存在多个值时将多个值都返回:

get (key) {
  const position = this.hashCode(key);
  const linkedList = this.table[position];
  if (linkedList !== null && linkedList.isEmpty()) {
    let current = linkedList.getHead(); // 获取链表表头引用
    while (current !== null) { // 迭代链表
      if (current.element.key === key) { // 寻找匹配键(人名)
        return current.element.value;
      }
      current = current.next;
    }
  }
  return undefined;
}

总的来说通过链表避免值覆盖, 将冲突部分用不冲突的方式保留下来了.


2.线性探查

不借助链表, 在HashMap上做线性探查, 找到一个未被占用的位置将冲突值插入.
缺点也显而易见, 放在本例来说, get值的时候不能再直接使用索引的ASCII取值了, 因为值冲突时会找到合适的位置插进去, 而索引不能相同, 所以本该属于同一索引下的元素会被插入到其他位置.

put(key, value) {
  if (key != null && value != null) {
    const position = this.hashCode(key);
    if (this.table[position] == null) { // 首次添加, 直接加
      this.table[position] = new ValuePair(key, value);
    } else {
      let index = position + 1; // 看看下个位置能不能加
      while (this.table[index] != null) { // 不能加就再往下看
        index++;
      }
      this.table[index] = new ValuePair(key, value); // 有空位, 加
    }
    return true;
  }
  return false;
}

而这种不正确的位置占用发生的太靠前将会导致一连串的错误索引出现, 你需要去挨个校对元素的key以确保获取到正确的值.

get(key) {
  const position = this.hashCode(key);
  if (this.table[position] != null) { // 原始位置不为空, 先看看是不是要找的
    if (this.table[position].key === key) {
      return this.table[position].value; // 原始位置就是要找的, 那么直接返回
    }
    let index = position + 1; // 原始位置只能再往下找
    while (this.table[index] != null && this.table[index].key !== key) {
      index++;
    }
    if (this.table[index] != null && this.table[index].key === key) { // 找着了, key也是正确的, 返回
      return this.table[index].value;
    }
  }
  return undefined; // 找到头了没有, 返回undefined
}

找的话用数组Hashmap也可以, 但是这个例子的索引是数字String, 所以也可以index++这样子找.


总结

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

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

相关文章

不让袁树雄上春晚,导演于蕾是真英明

自从央视兔年春晚结束后&#xff0c;互联网上面就出现各种吐槽声音&#xff0c;尤其是关于热歌《早安隆回》的话题。说起歌曲《早安隆回》&#xff0c;这是2022年最火的歌曲之一&#xff0c;民间的呼声也一直很高&#xff0c;都希望能够登上央视春晚舞台。 不过随着央视春晚的结…

在甲骨文云容器实例(Container Instances)上部署Alist

在甲骨文云容器实例上部署Oracle Linux 8 Desktop加强版创建容器实例查看容器实例的公共 IP 地址查看密码使用Alist甲骨文云推出了容器实例&#xff0c;这是一项无服务器计算服务&#xff0c;可以即时运行容器&#xff0c;而无需管理任何服务器。今天我们尝试一下通过容器实例部…

数据结构与算法(一)(Python版)

python基础知识整理二 文章目录算法分析运行时间检测大O表示法“变位词”判断问题解法一: O(logn)解法二&#xff1a;暴力法 O(n!)解法三 O(n)Python数据类型的性能示例判断是否是素数求素数个数基本结构——线性结构栈抽象数据类型以及Python实现用Python实现抽象数据结构栈栈…

HQChart实战教程58-K线主图仿tradingview

HQChart实战教程58-K线主图仿tradingview 需求效果图实现思路步骤1. 写透明成交量柱子指标2. 调整成交量柱子和K线图显示区域HQChart插件源码地址完整的demo例子需求 主图K线图和成交量柱子图在一个同窗口显示,柱子图高度为主图窗口高度的1/4,并且成交量柱子图使用透明色 效…

【论文翻译】Deep High-Resolution Representation Learningfor Visual Recognition

目录 摘要 介绍 2.相关工作 3 HIGH-RESOLUTION NETWORKS 3.1 并行多分辨率卷积 3.2 重复的多分辨率融合融合模块 3.3 表示头 3.4实例化 3.5分析 4 人体姿态估计 5语义分割 8.总结 摘要 高分辨率表示对于位置敏感的视觉问题是必不可少的&#xff0c;例如人体姿势估计…

垃圾收集器必问系列—G1

本文已收录至Github&#xff0c;推荐阅读 &#x1f449; Java随想录 人生下来不是为了拖着锁链&#xff0c;而是为了展开双翼。——雨果 文章目录基于Region的堆内存布局可预测的停顿时间模型跨Region引用对象对象引用关系改变运作过程CMS VS G1相关参数Garbage First&#xff…

重要的数据表

重要的数据表目录概述需求&#xff1a;设计思路实现思路分析1.-- 组织结构数据库.参考资料和推荐阅读Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,wait for change,challeng…

51单片机学习笔记-6串口通信

6 串口通信 [toc] 注&#xff1a;笔记主要参考B站江科大自化协教学视频“51单片机入门教程-2020版 程序全程纯手打 从零开始入门”。 6.1 串口通信原理 串口是一种应用十分广泛的通讯接口&#xff0c;串口成本低、容易使用、通信线路简单&#xff0c;可实现两个设备的互相通…

层级选择器

<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>层级选择器</title> <style type"text/css"> /*需求&#xff1a;需要选中前三个段落标签*/ /*下面两个选择器之间加…

JavaScript 发布订阅者模式和观察者模式及区别

一、发布订阅模式 发布订阅模式其实是一种对象间一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都将得到状态改变的通知。 多方订阅&#xff0c;一方发布&#xff0c;订阅放会收到通知 举例&#xff1a;教学楼中每个教室都有一个广…

Iterator_fail-fast和Iterator_fail-safe~

初识fail-fast&#xff1a; fail-fast 机制是java集合(Collection)中的一种错误机制&#xff0c;当多个线程对同一个集合的内容进行操作时&#xff0c;就可能会产生fail-fast事件&#xff0c;它只是一种错误检测机制&#xff0c;只能被用来检测错误&#xff0c;因为JDK并不保证…

蓝桥杯2022Python组

蓝桥杯2022Python组 1.排列字母 用一个sorted就好了&#xff0c;没啥好说的 s WHERETHEREISAWILLTHEREISAWAY s sorted(s) # 变成列表形式了 print(.join(s))2.寻找整数 这题我刚开始以为答案只能是11和17的倍数&#xff0c;就在他们的倍数里面找&#xff0c;发现不对&…

STL——STL简介、STL六大组件

一、STL是什么 STL(standard template library)&#xff1a; C标准模板库&#xff0c;是C标准库的重要组成部分&#xff0c;不仅是一个可复用的组件库&#xff0c;还是一个包罗数据结构与算法的软件框架。 通俗来讲&#xff1a; STL就是将常见的数据结构&#xff08;顺序表、…

Superset权限管理

文章目录1.Superset角色1&#xff09;Admin2&#xff09;Alpha3&#xff09;Gamma4&#xff09;Sql_lab5&#xff09;Public2.实操自定义授权1&#xff09;权限集2&#xff09;实操1.Superset角色 Superset的默认角色有&#xff1a;Admin、Alpha、Gamma、sql_lab、Public 1&a…

【信息资源建设】

考试分为单选、判断改错、名词解释、简答、综合论述 1.1 如果按价值观念划分&#xff0c;则可将信息分为有用信息和无用信息 信息的特性&#xff1a;普遍性、客观性、时效性、传递性、共享性、变换性、转化性、可伪性。 1.1.2 &#xff08;必考&#xff09;OECD的知识分类…

【docker概念和实践 4】(4) 本地镜像提交到本地仓库

一、说明 registry是一个镜像&#xff0c;该镜像专门生成镜像仓库的容器&#xff0c;registry是基于http协议&#xff0c;那就是说&#xff0c;在单机、局域网、或者互联网上都可以建立registry数据仓库&#xff0c;存放自己构建的镜像。本篇专门介绍如何在本地单机上建立容器仓…

Python中的集合(set and frozenset)语法汇总

集合的基本语法知识目前有两种集合类型&#xff1a;set和frozenset。可变集合&#xff1a;set()set类型是可变的&#xff0c; 其内容可以使用 add() 和 remove() 这样的方法来改变&#xff0c;因为是可变的&#xff0c;所以没有哈希值&#xff0c;且不能被用作字典的键或其它集…

java线上项目排查,Arthas简单上手

Arthas 是Alibaba开源的Java诊断工具。参考&#xff1a;Arthas 用户文档 — Arthas 3.5.4 文档 当你遇到以下类似问题而束手无策时&#xff0c;Arthas可以帮助你解决&#xff1a; 这个类从哪个 jar 包加载的&#xff1f;为什么会报各种类相关的 Exception&#xff1f;我改的代…

一起Talk Android吧(第四百七十九回:集合类视图动画)

文章目录使用方法属性介绍示例代码各位看官们大家好&#xff0c;上一回中咱们说的例子是"旋转类视图动画",这一回中咱们说的例子是"集合类视图动画"。闲话休提&#xff0c;言归正转&#xff0c;让我们一起Talk Android吧&#xff01; 使用方法 集合类动画…

web测试2:嵌入式移植boa

读此篇之前&#xff0c;先读前一篇 1.在源码编译的时候&#xff0c;指定交叉编译工具链 lkmaoubuntu:~$ arm-linux-gnueabi-gcc --version arm-linux-gnueabi-gcc (Ubuntu/Linaro 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609 Copyright (C) 2015 Free Software Foundation, Inc.…