- 参考书:《学习JavaScript数据结构与算法》
- GitHub demo:https://github.com/nebulium/HashTable
- 相比于“字典”来说,HASH表实际上也是通过名/值对进行存储。但是存储的数据结构有所不同。待存储的信息的“名(key)”通过特定的处理(散列函数)映射成数字。这些数字作为数组的索引指向一个数组的某个位置,而信息的“值(value)”就存储在数组的这个位置上。
- 散列表的原理是比较简单的,但设计散列表的时候需要注意:散列函数、冲突的解决。其中散列函数决定了key映射成数字导致的存储效率以及冲突的多寡;而“冲突”在不同key映射成同一个数字、指向同一个位置的时候出现,根据《学习JavaScript数据结构与算法》一书指出,解决冲突有:分离链接、线性探查和双散列法。
- 由于只是为了对必要的算法和结构的基本原理有所了解,所以完全按照参考书进行实现:
a. loseloseHash.js: 不考虑冲突的散列表+“lose lose”散列函数
b. djb2Hash.js: 不考虑冲突的散列表+djb2散列函数
c. linkedHash.js: 分离链接解决冲突的散列表+“lose lose”散列函数
d. linearHash.js: 线性探查的散列表+“lose lose”散列函数
loseloseHash.js
- function loseloseHashCode(key): key映射成数值,用ascii码的转换进行处理,借助key.charCodeAt(i)方法。
- put(key,value)
- remove(key)
- get(key)
djb2Hash.js
- put/remove/get方法没有变。
- function djb2Hash(key):同样是利用key的每个字母的ascii码进行处理,不过hash的初值、每次线性相乘的参数、以及最后取余操作的基数发生了改变。这些参数是经过研究的可以使散列表效率更高的数值。
linkedHash.js
- 解决冲突的最简单的方法,使散列表的每一个位置(数组的每一个位置)成为一个链表,这样当产生冲突的时候,就可以以链表的形式在同一个位置存储不同key的值了。但是显然,这个key也需要封装在链表的元素之中,以区分同一个位置(同一个索引)对应的不同信息。
- 这种方法显然需要额外的空间(数组以外的链表空间)
- 利用新加的辅助类表示要加入LinkedHash的实例,用以进行上面提到过的区分行为:
function ValuePair(key,value){ this.key = key; this.value = value; this.toString = function(){ return '['+this.key+'-'+this.value+']'; //方便进行观察 } }
- 需要重写put、get、remove这三个方法。利用链表的知识进行重写,并且把每一个元素都对ValuePair实例化进行存储。原理较为简单。
- 注意remove(key)方法,注意链表构造函数的remove()方法。
- 用到两个辅助的构造函数:链表构造函数以及ValuePair的构造函数。这两个函数完全可以放在HashTable构造函数之外实现。因为HashTable内部在实例化链表的时候会在全局环境中找需要的构造函数,且这两个构造函数不需要用到HashTable中的值,即二者是相对独立的。但是为了保证这个数据结构的完整性,将这两个构造函数都放在hash表的构造函数中当做私有函数。
linearHash.js 线性探查。
- 这种冲突的解决方法,在向表中添加一个新的元素的时候,如果索引为index的位置已经被占据了,则尝试index+1的位置,如果index+1的也被占据了,则放在index+2的位置,以此类推。
- 同样需要重写put/get/remove,主要是有一个查找的过程。
- 同样需要用ValuePair的实例来表示值。
- 移除以及查找的算法显然有不足,但这个不足依赖于插入算法弥补。不是完美的方法。