简介

散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构。
也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。
这个映射函数称做散列函数,存放记录的数组称做散列表。

  1. const table = []; // 记录的数组,即散列表
  2. // 散列函数
  3. const computeHashCode = function(key) {
  4. // 通过某种算法,算出数值 code
  5. // 算法可以是 md5、SHA-1 等
  6. // JS 中有 charCodeAt 方法可以获取字符的 unicode 编码,也可以作为一种算法。
  7. // ...
  8. return code; // 将数值返回
  9. }
  10. // 设置值
  11. const set = (key, value) => {
  12. const position = computeHashCode(key);
  13. table[position] = value;
  14. }

补充一下,散列表其实也可以是对象,只要算出的 hashCode 是字符串就可以。 在下文,我们以数组 和 JS 的 charCodeAt 来模拟散列表。

散列函数的特点

  • 确定性
    • 输出不同,输入肯定不同
  • 散列碰撞(散列冲突)
    • 输入输出不一定一一对应,多个输入可以对应一个输出
  • 不可逆性
    • 输出内容无法反推输入内容
  • 混淆特性
    • 输入即使变化很小,输出的变化会很大

JS 实现散列表

普通散列表

  1. class HashTable {
  2. table = [];
  3. // 散列函数
  4. static computeHashCode(key) {
  5. let code = 0;
  6. for (let i = 0; i < key.length; i++) {
  7. code += key.charCodeAt(i)
  8. }
  9. // 这里对 37 取余,只是为了配合下面举例,讲解散列碰撞
  10. // 换成其他的也行
  11. return code % 37;
  12. }
  13. // 设置值
  14. set(key, value) {
  15. const position = HashTable.computeHashCode(key);
  16. this.table[position] = value;
  17. }
  18. get(key) {
  19. const position = HashTable.computeHashCode(key);
  20. return this.table[position]
  21. }
  22. remove(key) {
  23. const position = HashTable.computeHashCode(key);
  24. delete this.table[position]
  25. }
  26. }
  27. const hashTable = new HashTable()
  28. // 添加值并打印输出的话可能会得到相同的hash值
  29. hashTable.set('Tyrion', 'aaa@qq.com') // position = 16
  30. hashTable.set('Aaron', 'bbb@qq.com') // position = 16

解决散列碰撞

链表法

使用链表来辅助存储。对于普通的散列表,本身在某个位置存储的是一个值。
而我们知道链表是有序的元素的集合,拥有存储元素本身的值和指向下一个值的引用,那么我们将某个位置本来要存储的值换成链表的形式,这就意味着我们在这个位置可以存放多个值了。

  1. class Node {
  2. constructor(value, next) {
  3. this.value = value;
  4. this.next = next;
  5. }
  6. }
  7. class NodeList {
  8. constructor() {
  9. this.head = new Node(); // 哑节点
  10. this.tail = this.head
  11. }
  12. append(value, next) {
  13. const node = new Node(value, next);
  14. this.tail.next = node;
  15. this.tail = node;
  16. }
  17. getList() {
  18. return this.head;
  19. }
  20. }
  21. module.exports = NodeList;
  1. const NodeList = require('./NodeList')
  2. class HashTable {
  3. table = [];
  4. // 散列函数
  5. static computeHashCode(key) {
  6. let code = 0;
  7. for (let i = 0; i < key.length; i++) {
  8. code += key.charCodeAt(i)
  9. }
  10. // 这里对 37 取余,只是为了配合下面举例,讲解散列碰撞
  11. // 换成其他的也行
  12. return code % 37;
  13. }
  14. // 设置值
  15. set(key, value) {
  16. const position = HashTable.computeHashCode(key);
  17. if (!this.table[position]) this.table[position] = new NodeList();
  18. this.table[position].append({ key, value });
  19. }
  20. get(key) {
  21. const position = HashTable.computeHashCode(key);
  22. let head = this.table[position].getList().next;
  23. while (head) {
  24. if (head.value.key === key) return head.value.value;
  25. head = head.next
  26. }
  27. }
  28. remove(key) {
  29. const position = HashTable.computeHashCode(key);
  30. let pre = this.table[position].getList();
  31. let p = pre.next
  32. while (p) {
  33. if (p.value.key === key) {
  34. pre.next = p.next
  35. }
  36. pre = p;
  37. p = p.next
  38. }
  39. }
  40. }
  41. const hashTable = new HashTable()
  42. // 添加值并打印输出的话可能会得到相同的hash值
  43. hashTable.set('Tyrion', 'aaa@qq.com') // position = 16
  44. hashTable.set('Aaron', 'bbb@qq.com') // position = 16
  45. hashTable.get('Tyrion') // aaa@qq.com
  46. hashTable.get('Aaron') // bbb@qq.com

参考资料

《数据结构之散列表》