散列是一种常用的数据存储技术,散列后的数据可以快速地插入或取用。散列使用的数据 结构叫做散列表。在散列表上插入、删除和取用数据都非常快。

下面的散列表是基于数组进行设计的,数组的长度是预先设定的,如有需要,可以随时增加。所有元素根据和该元素对应的键,保存在数组的特定位置。使用散列表存储数据时,通过一个散列函数将键映射为一个数字,这个数字的范围是0到散列表的长度。

散列函数会将每个键值映射为一个唯一的数组索引。然而,键的数量是无限的,数组的长度是有限的,一个更现实的目标是让散列 函数尽量将键均匀地映射到数组中。

即使使用一个高效的散列函数,仍然存在将两个键映射成同一个值的可能,这种现象称为碰撞(collision),当碰撞发生时,我们需要利用一定的方法去解决碰撞。

对数组大小常见的限制是:数组长度应该是一个质数。

HashTable类

使用 HashTable 类来表示散列表,该类包含计算散列值的方法、向散列中插入数据的方法、 从散列表中读取数据的方法、显示散列表中数据分布等方法。

function HashTable() {
  this.table = new Array(137);
  this.simpleHash = simpleHash;
  this.showDistro = showDistro;
  this.put = put;
  this.get = get;
  this.buildChains = buildChains;
}

散列函数

散列函数的选择依赖于键值的数据类型。如果键是整型,最简单的散列函数就是以数组的长度对键取余,这种散列方式称为除留余数法。

选择针对字符串类型的散列函数比较困难

简单的散列函数:字符串中每个字符的 ASCII 码值相加然后再除以数组长度,将得出的余数做为散列值。

function simpleHash(data) {
  var total = 0;
  for (var i = 0; i < data.length; ++i) {
    total += data.charCodeAt(i);
  }
  return total % this.table.length;
}

put() 和 showDistro(),一个用来将数据存入散列表, 一个用来显示散列表中的数据

function put(data) {
  var pos = this.simpleHash(data);
  this.table[pos] = data;
}
function showDistro() {
  var n = 0;
  for (var i = 0; i < this.table.length; ++i) {
    if (this.table[i] != undefined) {
      print(i + ": " + this.table[i]);
    }
  }
}

使用简答的散列函数 simpleHash() 时数据并不是均匀分布的,而是向数组的两端集中,并且数据很大概率将会产生碰撞而不会全部显示出来。

更好的散列函数:霍纳算法是一种比较好的散列函数算法,计算时仍然先计算字符串中各字符的 ASCII 码值,不过求和时每次要乘以一个质数。

为了避免碰撞,首先要确保散列表中用来存储数据的数组其大小是个质数。这一点和计算散列值时使用的取余运算有关。数组的长度应该在 100 以上,这是为了让数据在散列表中分布得更加均匀。

function betterHash(string, arr) {
  const H = 37; //质数
  var total = 0;
  for (var i = 0; i < string.length; ++i) {
    total += H * total + string.charCodeAt(i);
  }
  total = total % arr.length;
  return parseInt(total);
}

接受键和数据作为参数的put() 方法

function put(key, data) {
  var pos = this.betterHash(key); //使用更好的散列函数
  this.table[pos] = data;
}

get() 方法读取存储在散列表中的数据

function get(key) {
  return this.table[this.betterHash(key)];
}

碰撞处理

当散列函数对于不同的输入产生同样的散列值时,就产生了碰撞。下面是两种碰撞解决办法:开链法和线性探测法。

开链法:当碰撞发生时,仍然将键存储到通过散列算法产生的索引位置上,但实际上,每个数组元素又是一个新的数据结构,比如另一个数组,这样就能存储多个键了(即用二维数组实现)。

buildChains() 函数创建二维数组

function buildChains() {
  for (var i = 0; i < this.table.length; ++i) {
    this.table[i] = new Array();
  }
}

使用了开链法后,要重新定义 put() 和 get() 方法。

新的put() 方法将键值散列,散列后的值对应数组中的一个位置,先尝试将数据放到该位置上的数组中的第一个单元格,如果该单元格里已经有数据了,put() 方法会搜索下一个位置,直到找到能放置数据的单元格,并把数据存储进去。

它既保存数据,也保存键值。该方法使用链中两个连续的单元格,第一个用来保存键值,第二个用来保存数据。

function put(key, data) {
var pos = this.betterHash(key);
var index = 0;
if (this.table[pos][index] == undefined) {
this.table[pos][index] = key;
this.table[pos][index + 1] = data;
} else {
while (this.table[pos][index] != undefined) {
++index;
}
this.table[pos][index] = key;
this.table[pos][index + 1] = data;
}
}

新的 get() 方法先对键值散列,根据散列后的值找到散列表中相应的位置,然后搜索该位置上的链,直到找到键值。如果找到,就将紧跟在键值后面的数据返回;如果没找到,就返回 undefined

function get(key) {
var index = 0;
var pos = this.betterHash(key);
if (this.table[pos][index] == key) {
return this.table[pos][index + 1];
} else {
while (this.table[pos][index] != key) {
index += 2;
}
return this.table[pos][index + 1];
}
return undefined;
}

线性探测法:线性探测法隶属于一种更一般化的散列技术:开放寻址散列。当发生碰撞时,线性探测法检查散列表中的下一个位置是否为空。如果为空, 就将数据存入该位置;如果不为空,则继续检查下一个位置,直到找到一个空的位置为止。

当存储数据使用的数组特别大时,选择线性探测法要比开链法好。如果数组的大小是待存储数据个数的 1.5 倍, 那就使用开链法;如果数组的大小是待存储数据的两倍及两倍以上时,那么使用线性探测法。

使用线性探测法需要为 HashTable 类增加一个新的数组,用来存储数据。数组 table 和 values 并行工作,当将一个键值保存到数组 table 中时,将数据存入数组 values 中相应的 位置上。即在 HashTable 的构造函数中加入下面一行代码: this.values = []

重写 put() 和 get() 方法。

function put(key, data) {
var pos = this.betterHash(key);
if (this.table[pos] == undefined) {
this.table[pos] = key;
this.values[pos] = data;
} else {
while (this.table[pos] != undefined) {
pos++;
}
this.table[pos] = key;
this.values[pos] = data;
}
} function get(key) {
var hash = this.betterHash(key);
for (var i = hash; this.table[hash] != undefined; i++) {
if (this.table[hash] == key) {
return this.values[hash];
}
}
return undefined;
}

JS中数据结构之散列表的更多相关文章

  1. Python与数据结构[4] -> 散列表[0] -> 散列表与散列函数的 Python 实现

    散列表 / Hash Table 散列表与散列函数 散列表是一种将关键字映射到特定数组位置的一种数据结构,而将关键字映射到0至TableSize-1过程的函数,即为散列函数. Hash Table: ...

  2. Nginx数据结构之散列表

    1. 散列表(即哈希表概念) 散列表是根据元素的关键码值而直接进行访问的数据结构.也就是说,它通过把关键码值映射到表中一个位置来访问记录, 以加快查找速度.这个映射函数 f 叫做散列方法,存放记录的数 ...

  3. 【PHP数据结构】散列表查找

    上篇文章的查找是不是有意犹未尽的感觉呢?因为我们是真真正正地接触到了时间复杂度的优化.从线性查找的 O(n) 直接优化到了折半查找的 O(logN) ,绝对是一个质的飞跃.但是,我们的折半查找最核心的 ...

  4. Python与数据结构[4] -> 散列表[2] -> 开放定址法与再散列的 Python 实现

     开放定址散列法和再散列 目录 开放定址法 再散列 代码实现 1 开放定址散列法 前面利用分离链接法解决了散列表插入冲突的问题,而除了分离链接法外,还可以使用开放定址法来解决散列表的冲突问题. 开放定 ...

  5. Python与数据结构[4] -> 散列表[1] -> 分离链接法的 Python 实现

    分离链接法 / Separate Chain Hashing 前面完成了一个基本散列表的实现,但是还存在一个问题,当散列表插入元素冲突时,散列表将返回异常,这一问题的解决方式之一为使用链表进行元素的存 ...

  6. jdk1.8HashMap底层数据结构:散列表+链表+红黑树,jdk1.8HashMap数据结构图解+源码说明

    一.前言 本文由jdk1.8源码整理而得,附自制jdk1.8底层数据结构图,并截取部分源码加以说明结构关系. 二.jdk1.8 HashMap底层数据结构图 三.源码 1.散列表(Hash table ...

  7. JS中数据结构之列表

    列表是一组有序的数据.每个列表中的数据项称为元素.在 JavaScript 中,列表中的元素可以是任意数据类型.列表中可以保存多少元素并没有事先限定并可以不断壮大,实际使用时元素的数量受到程序内存的限 ...

  8. JS中数据结构之链表

    1.链表的基本介绍 数组不总是组织数据的最佳数据结构,在很多编程语言中,数组的长度是固定的,所以当数组已被数据填满时,再要加入新的元素就会非常困难.在数组中,添加和删除元素也很麻烦,因为需要将数组中的 ...

  9. JS中数据结构之栈

    1.栈的基本介绍 栈是一种高效的数据结构,因为数据只能在栈顶添加或删除,所以这样的操作很快,而且容易实现. 栈是一种特殊的列表,栈内的元素只能通过列表的一端访问,这一端称为栈顶.栈被称为一种后入先出( ...

随机推荐

  1. UML 类图快速入门

    UML 图形 官方定义 UML 类图(Class Diagram) UML 时序图(Sequence Diagram) 领域 UML 类图和实现 UML 类图 领域 UML 类图 实现 UML 类图 ...

  2. Grafana+Prometheus系统监控之Redis

    REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统. Redis是一个开源的使用ANSI C语言编写.遵守B ...

  3. 2019/10/13 TZOJ

    水题虽不好,但是很爽 渴望未来某天能把剩下的题补了,先做个记录. Hard Disk Drive http://acm.hdu.edu.cn/showproblem.php?pid=4788 单位转化 ...

  4. [功能集锦] 002 - mysql查询数据库字典+导出+样式一键整合至excel

    写在前面: 因为工作时候经常遇到半路接手项目的情况,由于年代久远,数据库字典这块经常缺失.故写此篇,以便复用,也希望对大家有点帮助. 随笔内容不高级,如有不妥,不吝指正. 20190730-加了一些简 ...

  5. JAVA线程初体验

    线程的创建 线程的启动和停止 /** * 演员类 继承Thread类 * @author Administrator * */ public class Actor extends Thread { ...

  6. spring-第四篇之让bean获取所在的spring容器

    1.如上一篇文章所述,有时候bean想发布一些容器事件,就需要先获取spring容器,然后将Event交由spring容器将事件发布出去. 为了让bean获取它所在的spring容器,可以让该bean ...

  7. 批量调整word 图片大小

    打开文档后,按Alt+F11,在左边Porject下找到ThisDocument,右键插入模块,贴上下面的 Sub Macro()For Each iShape In ActiveDocument.I ...

  8. 水题(三角形与扇形面积计算sin()应用)

    J - Sincerely Gym - 101350J Physics cat likes to draw shapes and figure out their area. He starts by ...

  9. 【转载】sizeof()、strlen()、length()、size()详解和区别

    c/c++中获取字符串长度.有以下函数:size().sizeof() .strlen().str.length();一.数组或字符串的长度:sizeof().strlen()1.sizeof():返 ...

  10. 针对三星Exynos CPU Root漏洞

    因为系统为了保护这些符号地址泄露,而用的一种保护手段,从而使除root用户外的普通用户不能直接查看符号地址: 原因在于内核文件kallsyms.c中的显示符号地址命令中做了如下限制: seq_prin ...