用js来实现那些数据结构12(散列表)

  上一篇写了如何实现简单的Map结构,因为东西太少了不让上首页。好吧。。。

  这一篇文章说一下散列表hashMap的实现。那么为什么要使用hashMap?hashMap又有什么优势呢?hashMap是如何检索数据的?我们一点一点的来解答。

  在我们学习一门编程语言的时候,最开始学习的部分就是循环遍历。那么为什么要遍历呢?因为我们需要拿到具体的值,数组中我们要遍历数组获取所有的元素才能定位到我们想要的元素。对象也是一样,我们同样要遍历所有的对象元素来获取我们想要的指定的元素。那么无论是array也好,object也好,栈还是队列还是列表或者集合(我们前面学过的所有数据结构)都需要遍历。不然我们根本拿不到我们想要操作的具体的元素。但是这样就有一个问题,那就是效率。如果我们的数据有成百万上千万的数据。我们每一次循环遍历都会消耗大量的时间,用户体验可以说几乎没有。(当然,前端几乎不会遇到这种情况,因为大数据量的情况都通过分页来转化了)。

  那么,有没有一种快速有效的定位我们想要的元素的数据结构呢?答案就是hashMap。当然,应该也有其它更高效的数据处理方式,但是我暂时不知道啊。。。。

  那么hashMap是如何存取元素的呢?首先,hashMap在存储元素的时候,会通过lose lose散列函数来设置key,这样我们就无需遍历整个数据结构,就可以快速的定位到该元素的具体位置,从而获取到具体的值。

  什么是lose lose散列函数呢?其实lose lose散列函数就是简单的把每个key中的所有字母的ASCII码值相加,生成一个数字,作为散列表的key。当然,这种方法并不是很好,会生成很多相同的散列值。下面会具体的讲解如何解决,以及一种更好的散列函数djb2。

  那么我们开始实现我们的hashMap:

    // 这里我们没在重复的去写clear,size等其他的方法,因为跟前面实在是没啥区别。
function HashMap() {// 我们使用数组来存储元素var list = [];//转换散列值得loselose散列函数。var loseloseHashCode = function (key) {var hash = 0;// 遍历字符串key的长度,注意,字符串也是可以通过length来获取每一个字节的。for(var i = 0; i < key.length; i  ) {hash  = key.charCodeAt(i)}//对hash取余,这是为了得到一个比较小的hash值,//但是这里取余的对象又不能太大,要注意return hash % 37;}//通过loselose散列函数直接在计算出来的位置放入对应的值。this.put = function (key,value) {var position = loseloseHashCode(key);console.log(position   "-"   key);list[position] = value;}//同样的,我们想要得到一个值,只要通过散列函数计算出位置就可以直接拿到,无需循环this.get = function (key) {return list[loseloseHashCode(key)];}//这里要注意一下,我们的散列表是松散结构,也就是说散列表内的元素并不是每一个下标index都一定是有值,//比如我存储两个元素,一个计算出散列值是14,一个是20,那么其余的位置仍旧是存在的,我们不能删除它,因为一旦删除,我们存储元素的位置也会改变。//所以这里要移除一个元素,只要为其赋值为undefined就可以了。this.remove = function (key) {list[loseloseHashCode(key)] = undefined;}this.print = function () {for(var i = 0; i < list.length; i  ) {// 大家可以把这里的判断去掉,看看到底是不是松散的数组结构。if(list[i] !== undefined) {console.log(i   ":"   list[i]);}}}}
//那么我们来测试一下我们的hashMap
var hash = new HashMap();
hash.put("Gandalf",'www.gandalf.com');
hash.put("John",'www.john.com');
hash.put("Tyrion",'www.tyrion.com');
//因为我们在put代码中加了一个console以便我们更好的理解代码,我们看一下输出
// 19-Gandalf
// 29-John
// 16-Tyrion
console.log(hash.get('John'));//www.john.com
console.log(hash.get("Zaking"));//undefined//那么我们来移除一个元素John
hash.remove("John");
console.log(hash.get("John"));//undefined

  那么我们就实现并且简单测试了一下我们自定义的hashMap,发现还不错哦。但是元素太少,没有代表性。我们再多测试几个数据看看会如何?

var conflictHash = new HashMap();
conflictHash.put("Gandalf",'www.Gandalf.com');//19-Gandalf
conflictHash.put("John",'www.John.com');//29-John
conflictHash.put("Tyrion",'www.Tyrion.com');//16-Tyrion
conflictHash.put("Aaron",'www.Aaron.com');//16-Aaron
conflictHash.put("Donnie",'www.Donnie.com');//13-Donnie
conflictHash.put("Ana",'www.Ana.com');//13-Ana
conflictHash.put("Jonathan",'www.Jonathan.com');//5-Jonathan
conflictHash.put("Jamie",'www.Jamie.com');//5-Jamie
conflictHash.put("Sue",'www.Sue.com');//5-Sue
conflictHash.put("Mindy",'www.Mindy.com');//32-Mindy
conflictHash.put("Paul",'www.Paul.com');//32-Paul
conflictHash.put("Nathan",'www.Nathan.com');//10-Nathan

conflictHash.print();
/*
5:www.Sue.com
10:www.Nathan.com
13:www.Ana.com
16:www.Aaron.com
19:www.Gandalf.com
29:www.John.com
32:www.Paul.com
*/

  我们发现后来的把前面相同散列值得元素给替换了。那么之前的元素也就随之丢失了,这绝不是我们想要看到的样子。这才十几个元素就有这么多相同的,如果数据量极大那还了得。。。这啥用没有啊。。。所以,我们需要解决这样的问题,我们这里介绍两种解决这种冲突的方法。分离链接和线性探查。

  1、分离链接

    分离链接,其实核心就是为散列表的每一个位置创建一个链表,并将元素存储在里面。它可以说是解决冲突的最简单的方法,但是,它占用了额外的存储空间。之前的例子,如果用分离链接来解决冲突的话,那么看起来就是这个样子。

    

    那么我们就需要重写hashMap,我们来看看分离链接下的hashMap是如何实现的。由于我们要重写hashMap类中的方法,所以我们重新构建一个新的类:SeparateHashMap。

function LinkedList() {//...链表方法}// 创建分离链接法下的hashMap。
function SeparateHashMap () {var list = [];//loselose散列函数。var loseloseHashCode = function (key) {var hash = 0;for(var i = 0; i < key.length; i  ) {hash  = key.charCodeAt(i)}return hash % 37;}//这里为什么要创建一个新的用来存储键值对的构造函数?//首先我们要知道的一点是,在分离链接下,我们元素所存储的位置实际上是在链表里面。//而一旦在该散列位置下的链表中有多个值,我们仍旧需要通过key去找链表中所对应的元素。//换句话说,分离链接下的存储方式是,首先通过key来计算散列值,然后把对应的key和value也就是ValuePair存入linkedList。//这就是valuePair的作用了。var ValuePair = function (key,value) {this.key = key;this.value = value;this.toString = function () {return "["   this.key   "-"   this.value   "]";}}//同样的,我们通过loselose散列函数计算出对应key的散列值。this.put = function (key,value) {var position = loseloseHashCode(key);//这里如果该位置为undefined,说明这个位置没有链表,那么我们就新建一个链表。if(list[position] == undefined) {list[position] = new LinkedList();}//新建之后呢,我们就通过linkedList类的append方法把valuePair加入进去。//那么如果上面的判断是false,也就是有了链表,直接跳过上面的判断执行加入操作就好了。list[position].append(new ValuePair(key,value));}this.get = function (key) {var position = loseloseHashCode(key);//链表的操作前面相应的链表文章已经写的很清楚了。这里就尽量简单说清//如果这个位置不是undefined,那么说明存在链表if(list[position] !== undefined) {//我们要拿到current,也就是链表中的第一个元素进行链表中的遍历。var current = list[position].getHead();//如果current.next不为null说明还有下一个while(current.next) {//如果要查找的key是当前链表元素的key,就返回该链表节点的value。//这里要注意一下。current.element = ValuePair噢!if(current.element.key === key) {return current.element.value;}current = current.next;}//那么这里刚开始让我有些疑惑。为啥还要单独判断一下?//我们回头看一下,我们while循环的条件是current.next。没current什么事啊...对了。//所以,这里我们还要单独判断一下是不是current。//总结一下,这段get方法的代码运行方式是从第一个元素的下一个开始遍历,如果到最后还没找到,就看看是不是第一个,如果第一个也不是,那就返回undefined。没找到想要得到元素。if(current.element.key === key) {return current.element.value;}}return undefined;}//这个remove方法就不说了。跟get方法一模一样,get方法是在找到对应的值的时候返回该值的value,而remove方法是在找到该值的时候,重新赋值为undefined,从而移除它。this.remove = function (key) {var position = loseloseHashCode(key);if(list[position] !== undefined) {var current = list[position].getHead();while(current.next) {if(current.element.key === key) {list[position].remove(current.element);if(list[position].isEmpty()) {list[position] = undefined;}return true;}current = current.next;} if(current.element.key === key) {list[position].remove(current.element);if(list[position].isEmpty()) {list[position] = undefined;}return true;}}return false;};this.print = function () {for(var i = 0; i < list.length; i  ) {// 大家可以把这里的判断去掉,看看到底是不是松散的数组结构。if(list[i] !== undefined) {console.log(i   ":"   list[i]);}}}
}var separateHash = new SeparateHashMap();
separateHash.put("Gandalf",'www.Gandalf.com');//19-Gandalf
separateHash.put("John",'www.John.com');//29-John
separateHash.put("Tyrion",'www.Tyrion.com');//16-Tyrion
separateHash.put("Aaron",'www.Aaron.com');//16-Aaron
separateHash.put("Donnie",'www.Donnie.com');//13-Donnie
separateHash.put("Ana",'www.Ana.com');//13-Ana
separateHash.put("Jonathan",'www.Jonathan.com');//5-Jonathan
separateHash.put("Jamie",'www.Jamie.com');//5-Jamie
separateHash.put("Sue",'www.Sue.com');//5-Sue
separateHash.put("Mindy",'www.Mindy.com');//32-Mindy
separateHash.put("Paul",'www.Paul.com');//32-Paul
separateHash.put("Nathan",'www.Nathan.com');//10-Nathan

separateHash.print();
/*
5:[Jonathan-www.Jonathan.com]n[Jamie-www.Jamie.com]n[Sue-www.Sue.com]
10:[Nathan-www.Nathan.com]
13:[Donnie-www.Donnie.com]n[Ana-www.Ana.com]
16:[Tyrion-www.Tyrion.com]n[Aaron-www.Aaron.com]
19:[Gandalf-www.Gandalf.com]
29:[John-www.John.com]
32:[Mindy-www.Mindy.com]n[Paul-www.Paul.com]
*/
console.log(separateHash.get("Paul"));
/*
www.Paul.com
*/
console.log(separateHash.remove("Jonathan"));//true
separateHash.print();
/*
5:[Jamie-www.Jamie.com]n[Sue-www.Sue.com]
10:[Nathan-www.Nathan.com]
13:[Donnie-www.Donnie.com]n[Ana-www.Ana.com]
16:[Tyrion-www.Tyrion.com]n[Aaron-www.Aaron.com]
19:[Gandalf-www.Gandalf.com]
29:[John-www.John.com]
32:[Mindy-www.Mindy.com]n[Paul-www.Paul.com]
*/

 

    其实,分离链接法,是在每一个散列值对应的位置上新建了一个链表以供重复的值可以存储,我们需要通过key分别在hashMap和linkedList中查找值,而linkedList中的查找仍旧是遍历。如果数据量很大,其实仍旧会耗费一些时间。但是当然,肯定要比数组等这样需要遍历整个数据结构的方式要效率的多。

    下面我们来看看线性探查法。

   2、线性探查

    什么是线性探查呢?其实就是在hashMap中发生冲突的时候,将散列函数计算出的散列值 1,如果 1还是有冲突那么就 2。直到没有冲突为止。

    其实分离链接和线性探查两种方法,多少有点时间换空间的味道。

    我们还是来看代码。

function LinearHashMap () {var list = [];var loseloseHashCode = function (key) {var hash = 0;for(var i = 0; i < key.length; i  ) {hash  = key.charCodeAt(i)}return hash % 37;}var ValuePair = function (key,value) {this.key = key;this.value = value;this.toString = function () {return "["   this.key   "-"   this.value   "]";}}this.put = function (key,value) {var position = loseloseHashCode(key);//同样的,若是没有值。就把该值存入    if(list[position] == undefined) {list[position] = new ValuePair(key,value);} else {// 如果有值,那么久循环到没有值为止。var index =   position;while(list[index] != undefined) {index  }list[index] = new ValuePair(key,value);}}this.get = function (key) {var position = loseloseHashCode(key);if(list[position] !== undefined) {if(list[position].key === key) {return list[position].value;} else {var index =   position;while(list[index] === undefined || list[index].key !== key) {index   ;}if(list[index] .key === key) {return list[index].value}}}return undefined;}this.remove = function (key) {var position = loseloseHashCode(key);if(list[position] !== undefined) {if(list[position].key === key) {list[index] = undefined;} else {var index =   position;while(list[index] === undefined || list[index].key !== key) {index   ;}if(list[index] .key === key) {list[index] = undefined;}}}return undefined;};this.print = function () {for(var i = 0; i < list.length; i  ) {// 大家可以把这里的判断去掉,看看到底是不是松散的数组结构。if(list[i] !== undefined) {console.log(i   ":"   list[i]);}}}
}var linearHash = new LinearHashMap();
linearHash.put("Gandalf",'www.Gandalf.com');//19-Gandalf
linearHash.put("John",'www.John.com');//29-John
linearHash.put("Tyrion",'www.Tyrion.com');//16-Tyrion
linearHash.put("Aaron",'www.Aaron.com');//16-Aaron
linearHash.put("Donnie",'www.Donnie.com');//13-Donnie
linearHash.put("Ana",'www.Ana.com');//13-Ana
linearHash.put("Jonathan",'www.Jonathan.com');//5-Jonathan
linearHash.put("Jamie",'www.Jamie.com');//5-Jamie
linearHash.put("Sue",'www.Sue.com');//5-Sue
linearHash.put("Mindy",'www.Mindy.com');//32-Mindy
linearHash.put("Paul",'www.Paul.com');//32-Paul
linearHash.put("Nathan",'www.Nathan.com');//10-Nathan

linearHash.print();
console.log(linearHash.get("Paul"));
console.log(linearHash.remove("Mindy"));
linearHash.print();

 

  LinearHashMap与SeparateHashMap在方法上有着相似的实现。这里就不再浪费篇幅的去解释了,但是大家仍旧要注意其中的细节。比如说在位置的判断上的不同之处。

  那么HashMap对于冲突的解决方法这里就介绍这两种。其实还有很多方法可以解决冲突,但是我觉得最好的办法就是让冲突的可能性变小。当然,无论是使用什么方法,冲突都是有可能存在的。

  那么如何让冲突的可能性变小呢?很简单,就是让计算出的散列值尽可能的不重复。下面介绍一种比loselose散列函数更好一些的散列函数djb2。

var djb2HashCode = function(key) {var hash = 5831;for(var i = 0; i < key.length; i  ) {hash = hash * 33   key.charCodeAt(i);}return hash % 1013;
}

  大家可以把最开始实现的HashMap的loselose散列函数换成djb2。再去添加元素测试一下是否冲突的可能性变小了。

  djb2散列函数中,首先用一个hash变量存储一个质数(只能被1和自身整除的数)。将hash与33相乘并加上当前迭代道德ASCII码值相加。最后对1013取余。就得到了我们想要的散列值。

  到这里,hashMap就介绍完了。希望大家可以认真的去阅读查看。

 

  最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!


更多专业前端知识,请上 【猿2048】www.mk2048.com

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

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

相关文章

探索SwitchYard 2.0.0.Alpha2快速入门

在我的最后一篇文章中&#xff0c;我解释了如何在WildFly 8.1上使用SwitchYard。 同时&#xff0c;该项目很忙&#xff0c;并发布了另一个Alpha2。 这是一个很好的机会&#xff0c;在这里浏览快速入门并刷新您的记忆。 除了版本更改之外&#xff0c;您仍然可以使用较早的博客来…

走进webpack(1)--环境拆分及模块化

初级的文章和demo已经基本完成了&#xff0c;代码也已经上传到了我的github上&#xff0c;如果你对webpack的使用并不是十分了解&#xff0c;那么建议你回头看下走近系列&#xff0c;里面包括了当前项目中使用频繁的插件&#xff0c;loader的讲解。以及基本的webpack配置&#…

适用于微服务架构的Apache Camel

在知道微服务架构被称为之前&#xff0c;我一直在使用它们。 我曾经使用过由隔离模块组成的管道应用程序&#xff0c;这些模块通过队列相互交互。 从那时起&#xff0c;许多&#xff08;前&#xff09;ThoughtWorks专家讨论了微服务。 首先是 Fred George&#xff0c; 然后是 J…

QueryString加密

有些人不想由URL暴露一些訊息&#xff0c;除了可以使用URL Rewrite之外&#xff0c;其實簡便一點的方法還有使用編碼or加密來達到偽裝的目的。使用Base64的原因是因為他的編碼不會有難以接受的特殊字元(註1)&#xff0c;你也可以用其他的編碼or加密算法替代(註2)。其實這邊已經…

即时大数据流处理=即时风暴

在Ubuntu背后的公司Canonical&#xff0c;每6个月进行一次技术工作&#xff0c;以第一手测试我们的工具并向其他人展示新想法。 这次&#xff0c;我创建了一个即时大数据解决方案&#xff0c;更具体地讲是“即时风暴”。 Storm现在是Apache基金会的一部分&#xff0c;但以前St…

webstorm中vue项目--运行配制

## npm搭建的项目&#xff0c;需要运行npm run dev来启动 webstorm作为一款优秀的编辑器&#xff0c;通过配置运行设置&#xff0c;达到一键运行 1.添加node.js配置 2.configuration->node interpreter : 路径/node.exe 3.configuration->working directory&#xff1a; …

VS2010 自动化整理代码(1)--- VS正则表达替换 PK Vim

自从开始在VS2010的IDE中开始用正则表达式修改 最近为了给Fortran找个好一点的编辑器&#xff0c;又开始使用Vim了。Vim是久负盛名的编辑器之神&#xff0c;可我们习惯了Visual Studio的智能提示等方便的操作&#xff0c;就总在琢磨要是VS 1. VS正则表达替换 PK Vim 这是善用…

2019.7.16考试总结

对于这个狗屎成绩我不想说什么&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c;前两次考炸也就算了&#xff0c;主要因为不会&#xff0c;这次考成这狗屎&#xff0c;是因为手残眼瘸大脑间歇性抽搐 T1&#xff1a;我是菜鸡&#xff0c;我是蒟蒻&#xff0c;我好菜…

PrimeFaces Extensions中的全新JSF组件

PrimeFaces扩展团队很高兴宣布即将推出的3.0.0主要版本的几个新组件。 我们的新提交人Francesco Strazzullo为该项目提供了“ Turbo Boost”&#xff0c;并带来了至少6个已成功集成的 JSF组件&#xff01; 当前的开发状态是OpenShift上的deployet – 请查看展示柜。以下是有关添…

15 个最新的 CSS3 教程

1. 创建一个漂亮的图标 这个教程将教你如何用纯CSS3创建一个图中的图标2. CSS3 图片样式 这个教程将教你如何使用 box-shadow, border-radius和transition。3. CSS3 Transition 的模糊效果4. 实用的CSS3圆角表格5. 创建纯CSS3的票式标签6. 原始的鼠标浮动效果 这个教程将创建缩…

C++内存管理——指针数组

C/C程序中&#xff0c;指针和数组在不少地方可以相互替换着用&#xff0c;让人产生一种错觉&#xff0c;以为两者是等价的。但二者有着本质的区别&#xff1a;数组&#xff1a;要么在静态存储区被创建(如全局数组)&#xff0c;要么在栈上被创建。数组名对应着&#xff08;而不是…

flex弹性盒子

注意事项 1.设为Flex布局之后&#xff0c;子元素的float&#xff0c;clear和vertical-align属性都讲失效 2.采用Flex布局的元素&#xff0c;称为Flex容器&#xff08;Flex container&#xff09;&#xff0c;所有的子元素成为容器成员&#xff0c;称为Flex项目&#xff08;Fle…

开始JBoss BPM流程的3种基本方法

这一集提示和技巧将帮助您了解根据需要启动流程实例的最佳方法。 规划项目可能包括流程项目&#xff0c;但是您是否考虑过可以启动流程的各种方式&#xff1f; 也许您的JBoss BPM Suite在您的体系结构中本地运行&#xff0c;也许您在云中运行&#xff0c;但是无论它在哪里&am…

用asp.net编写冒泡排序法

这里写了一个冒泡排序的函数. int[] a newint[10] { 12,564,95,44,69,499,693,6746,6496,124}; for(inti0;i<a.Length;i) for(intj i1; j <10; j) { int min a[i]; if (a[i] > a[j]) //升序排列 …

7月17日每日一答

1 什么是闭包函数&#xff0c;闭包函数满足什么样的条件&#xff1f;请写一个常见的闭包函数。 所谓的函数闭包本质是函数的嵌套和高阶函数。我们来看看要实现函数闭包要满足什么条件&#xff08;缺一不可&#xff09;&#xff1a; 1)必须嵌套函数 2)内嵌函数必须引用一个定义在…

BZOJ1706奶牛接力跑

这个东西思路还是不错的。 解法就是把矩阵幂的加法改成取min&#xff0c;乘法改成加法就好&#xff0c;和floyed是一样的。这样的话&#xff0c;矩阵操作一次就相当于松弛了一次最短路。 建矩阵的过程也比较简单&#xff0c;可以离散化&#xff0c;当然下面有另一种更优秀的打法…

EJB 3.x:生命周期和并发模型(第2部分)

这是两部分系列的第二篇。 第一部分介绍了有状态和无状态EJB的生命周期以及并发行为。 在这篇文章中&#xff0c;我将介绍Singleton EJB 。 Singleton模式可以说是最常用&#xff08;有时被滥用&#xff01;&#xff09;的模式。 单吨又爱它&#xff01; Java EE使我们无需编…

MVC2中Area的路由注册实现

今天碰到了一个不可思议的bug&#xff0c;新增的controller中任何action都无法访问&#xff0c;都是返回404错误。一般这种错误要么是拼写错误&#xff0c;要么是不小心给action加了post属性&#xff0c;但是经过初步的排查&#xff0c;没有发现问题。而原有的controller中任何…

当Java 8 Streams API不够用时

Java 8与往常一样是妥协和向后兼容的版本。 JSR-335专家组可能无法与某些读者就某些功能的范围或可行性达成一致的发行版。 请参阅Brian Goetz关于为什么…的一些具体解释。 …Java 8默认方法中不允许“最终” …Java 8默认方法中不允许“同步” 但是今天&#xff0c;我们将…

父类作为方法的形参以及父类作为方法返回值

1、父类作为方法的形参 语句&#xff1a;修饰符 父类类型 方法名(){} 2、父类作为方法返回值 语句&#xff1a;修饰符 void/返回值类型 方法名(父类类型 形参名){} 代码例子&#xff1a; 动物类&#xff1a; /*** author Mr.Wang* 宠物类**/ public abstract class Animals {pr…