JS数据结构 之 散列表

news/2024/7/11 0:55:44 标签: 数据结构, 算法, javascript, 散列表, es6

JS数据结构散列表

散列、散列函数、散列表

  • 散列 是一种常用的数据存储技术,散列后的数据可以快速地插入或取用。散列使用的数据结构叫做散列表哈希表-Hash Table)。

  • 散列表 ,是根据键(Key)直接访问在内存存储位置的数据结构。它通过计算一个关于键值的函数(散列函数),将所需查询的数据映射到表(散列表)中一个位置来访问记录。在散列表上插入、删除和取用数据都非常快。👇
    在这里插入图片描述

  • 散列表的作用

    • 用于快速查找
    • 防止重复
    • 用作缓存
  • 下面的散列表基于数组进行设计的,数组的长度是预先设定的,如有需要,可以随时增加(一旦填装因子超过0.7,就该调整散列表的长度)。所有元素根据和该元素对应的键,保存在数组的特定位置。使用散列表存储数据时,通过一个散列函数将键映射为一个数字,这个数字的范围是0到散列表的长度。

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

    填装因子:散列表包含的元素数 ➗ 位置总数

  • 碰撞(collision)

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

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

HashTable类

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

class HashTable{
    this.table = new Array([容量])
    this.simpleHash = simpleHash		//选择散列函数==>计算散列值的方法
    this.showDistor = showDistor		//显示数据分布
    this.put = put						//向散列表中插入数据
    this.get = get						//读取数据
    this.buildChains = buildChains		//冲突处理==collision
    this.values = []					//❗ 使用线性探测法时 需要创建一个新数组用来存放 data 对应 table 中的 key
}

散列函数

1处理整数:

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

  • 有一个集合U,里面分别是1000,10,152,9733,1555,997,1168
  • 右侧是一个10个插槽的列表(散列表),我们需要把集合U中的整数存放到这个列表中
  • 怎么存放,分别存在哪个槽里?这个问题就是需要通过一个散列函数来解决了。我的存放方式是取10的余数,我们对应这图来看
    • 1000%10=0,10%10=0 那么1000和10这两个整数就会被存储到编号为0的这个槽中
    • 152%10=2那么就存放到2的槽中
    • 9733%10=3 存放在编号为3的槽中

通过上面简单的例子,应该会对以下几点有大致的理解

  • 集合U,就是可能会出现在散列表中的键
  • 散列函数,就是你自己设计的一种如何将集合U中的键值通过某种计算存放到散列表中的方法,如例中的-取余数
  • 散列表中存放的是通过计算后得到的键

接下来如何取值呢?

比如我们存储一个key为1000,value为’张三’ ==> {key:1000,value:‘张三’}
从我们上述的解释,它是不是应该存放在1000%10的这个插槽里。
当我们通过key想要找到value张三,是不是到key%10这个插槽里找就可以了呢?到了这里你可以停下来思考一下。

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

2针对字符串类型的散列函数

①simpleHash:简单的散列函数

function simpleHash(data){
    var total = 0
    for(i; i<data.length; ++i){
        total += data.charCodeAt(i)
    }
    return total % this.table.length
}
//put() 和 showDistro(),一个用来将数据存入散列表, 一个用来显示散列表中的数据
function put(data){			//只接收数据值的put()方法
    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){
            document.write(i+':'+this.table[i])
        }
    }
}

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

②betterHash:更好的散列函数

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

1为了避免碰撞,首先要确保散列表中用来存储数据的数组 其大小是个质数。这一点和计算散列值时使用的取余运算有关。【❗】

this.table = new Array('这里应该是一个质数')

2数组的长度应该在 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()方法 和 get() 方法

function put(key, data){			//接收键和值作为参数
    var pos = this.betterHash(key)
    this.table[pos] = data
}

function get(key){					//获取储存在散列表中的数据
    return this.table[this.betterHash(key)]
}

散列的一些术语

据 例1:

  • 散列表中所有可能出现的键称作全集U
  • 用M表示槽的数量
  • 给定一个键,由散列函数计算它应该出现在哪个槽中,上面例子的散列函数h=k%M,散列函数h就是键k到槽的一个映射。
  • 1000和10都被存到了编号0的这个槽中,这种情况称之为碰撞(collision

看到这里不知道你是否大致理解了散列函数是什么。通过例子,再通过你的思考,你可以回头在读一遍文章头部关于散列表的定义。如果你能读懂了,那么我估计你应该是懂了。

碰撞处理(冲突处理(collision

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

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

如果数组的大小是待存储数据的两倍及两倍以上时,那么使用线性探测法

1开链法

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

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

img

使用了开链法后,要重新定义 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] 对应一个数组结构
        //该方法使用链中两个连续的单元格,第一个用来保存键值,第二个用来保存数据。
        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 pos = this.betterHash(key)
    var index = 0
    if(this.table[pos][index]==key){
        return this.this.table[pos][index+1]
    }else{
        while(this.table[pos][index]!=key){
            index += 2
        }
        return this.table[pos][index+1]
    }
    return undefined			//散列表里没有此项
}

2线性探测法

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

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

使用了线性探测法后,要重新定义 put() 和 get() 方法:

//重写put() get()方法
function put(key, data){
    var pos = this.betterHash(key)
    if(this.table[pos]==undefined){
        this.table[pos] = key
        this.value[pos] = data
    }else{
        while(this.table[pos]!=undefined){
            pos++
        }
        this.table[pos] = key
        this.value[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.value[hash]
        }
    }
}

结尾补充一个小知识点

v8引擎中的数组 arr = [1,2,3,4,5] 或 new Array(100) 我们都知道它是开辟了一块连续的空间去存储,而arr = [] , arr[100000] = 10 这样的操作它是使用的散列,因为这种操作如果连续开辟100万个空间去存储一个值,那么显然是在浪费空间。

来源 👉 JS中数据结构散列表

​ 👉 js数据结构-散列表(哈希表)

​ 👉《算法图解》[美] Aditya Bhargava


http://www.niftyadmin.cn/n/1664431.html

相关文章

Ubuntu 16.04出现Can't open /etc/rc.d/init.d/functions的问题解决

/etc/rc.d/init.d/functions是CentOS的位置&#xff0c;Ubuntu对应&#xff1a;/lib/lsb/init-functions 参考&#xff1a; https://unix.stackexchange.com/questions/9314/no-such-file-or-directory-etc-init-d-functions>如有问题&#xff0c;请联系我&#xff1a;eason…

MySQL 第一章 数据库概述

一. 数据库概述以及准备工作 1.1 MySQL的三层结构介绍 1.1.1 什么是数据库&#xff1f;什么是数据库管理系统&#xff1f;什么是SQL&#xff1f;关系&#xff1f; 数据库&#xff1a; 英文单词DataBase&#xff0c;简称DB。按照一定格式存储数据的一些文件组合。 顾名思义&…

详细的bootloader的移植(6)

详细的boorloader的移植六 {clkdiv clk_power->CLKDIVN;camdiv clk_power->CAMDIVN;/* work out clock scalings */switch (clkdiv & S3C2440_CLKDIVN_HDIVN_MASK) {case S3C2440_CLKDIVN_HDIVN_1:hdiv 1;break;case S3C2440_CLKDIVN_HDIVN_2:hdiv 2;break;case …

【C++】详谈malloc和new的区别

目录1.malloc函数2.free函数2.1 free是如何做到释放具体空间的大小的&#xff1f;2.2 不对malloc开辟的空间进行free会怎么样&#xff1f;3.new运算符3.1new申请的空间是在堆上嘛&#xff1f;3.2有了malloc为什么还需要new&#xff1f;4.delete运算符4.1operator new接口和oper…

Vue中计算属性与class,style绑定

var vmnew Vue({ el:#app, data:{ a:2, }, computed:{ //这里的b是计算属性&#xff1a;默认getter b:{ get:function(){ return this.a1 }, set:function(newValue){ this.anewValue-3 } } } }); console.log(vm.b);//3 vm.a3; console.log(vm…

xtrabackup简析

xtrabackup: xtrabackup是由percona公司开发的。对innoDB引擎支持非常好。在备份的时候不用锁表。如果是MYiSAM引擎的&#xff0c;则需要锁表。 xtrabackup 是复制 ib_logfile0(ib_logfile1)事务日志来实行复制的。此外&#xff0c;还提供了perpare 功能。在恢复的…

MySQL第二章 常用命令

二. MySQL常用命令 2.1 查看数据库 mysql> show databases; -------------------- | Database | -------------------- | information_schema | | bjpowernode | | ecshop | | mysql | | performance_schema | | sys …

JAVA-JSP内置对象之request范围

相关资料&#xff1a;《21天学通Java Web开发》 request范围1.在一次请求内有效。2.如果页面从一个页面跳转到另一个页面&#xff0c;那么属性就失效了。3.如果使用服务器端跳转<jsp:forward>&#xff0c;则属性仍然有效。4.通过使用request的setAttribute()方法来设置属…