字典和散列表

创建字典

  1. function Dictionary() {
  2. let items = {};
  3. this.has = function(key) {
  4. return key in items;
  5. };
  6. this.set = function(key, value) {
  7. items[key] = value;
  8. };
  9. this.delete = function(key){
  10. if(this.has(key)){
  11. delete items[key];
  12. return true;
  13. }
  14. return false;
  15. };
  16. this.get = function(key){
  17. return this.has(key) ? items[key] :undefined;
  18. };
  19. this.values = function(){
  20. let values = [];
  21. for(let k in items){
  22. if(this.has(k)){
  23. values.push(items[k]);
  24. }
  25. }
  26. return values;
  27. };
  28. this.keys = function(){
  29. return Object.keys(items);
  30. };
  31. this.getItems = function(){
  32. return items;
  33. }
  34. }

散列表

散列算法的作用是尽可能快地在数据结构中找到一个值。使用散列函数就能知道值的具体位置,因此能够快速检索到该值。散列函数的作用是给定一个键值,然后返回值在表中的地址。

  1. function HashTable() {
  2. let table = [];
  3. //实现一个散列函数,它是类的一个私有方法
  4. //给定一个key参数,就能根据组成key的每个字符的ASCII码值的和得到一个数字。
  5. //为了得到比较小的数值,我们会使用hash值和一个任意数做除法的余数。
  6. let loseloseHashCode = function(key) {
  7. let hash = 0;
  8. for(let i = 0; i<key.length; i++) {
  9. hash += key.charCodeAt(i);
  10. }
  11. return hash % 37;
  12. };
  13. //首先根据给定的key,我们需要根据散列函数计算出它在表中的位置
  14. this.put = function(key, value) {
  15. let position = loseloseHashCode(key);
  16. // console.log(position + ' - ' + key);
  17. table[position] = value;
  18. };
  19. this.get = function(key) {
  20. return table[loseloseHashCode(key)];
  21. };
  22. //不需要将位置也移除。由于元素分布于整个数组范围内,一些位置会没有任何元素占据,并默认为undefined。
  23. //不能将位置本身从数组中移除(这会改变其他元素位置),否则当下次需要获得或移除一个元素时,这个元素会不在
  24. //我们用散列函数求出的位置上。
  25. this.remove = function(key) {
  26. table[loseloseHashCode(key)] = undefined;
  27. };
  28. this.print = function() {
  29. for(let i = 0; i<table.length; ++i) {
  30. if(table[i] !== undefined) {
  31. console.log(i + ": " + table[i]);
  32. }
  33. }
  34. };
  35. }
  36. let hash = new HashTable();
  37. hash.put('Candalf', 'gandalf@email.com');
  38. hash.put('John', 'johnsnow@email.com');
  39. hash.put('Tyrion', 'tyrion@email.com');
  40. console.log(hash.get('Candalf')); //gandalf@email.com

处理散列表中的冲突

有时候,一些键会有相同的散列值。不同的值在散列表中对应相同位置的时候,我们称其为冲突。后面添加的元素会覆盖前面的。处理冲突主要有:分离链接、线性探查、双散列法。

分离链接

分离链接法包括为散列表的每一个位置创建一个链表并将元素存储在里面。是处理冲突的简单方法,但是还需要额外的存储空间。

要创建一个新的辅助类来表示将其加入LinkedList实例的元素,叫他ValuePair类:

  1. let ValuePair = function(key, value) {
  2. this.key= key;
  3. this.value = value;
  4. this.toString = function(){
  5. return '[' + this.key + ' - ' + this.value + ']';
  6. }
  7. };
  1. //put方法
  2. this.put = function(key, value) {
  3. let position = loseloseHashCode(key);
  4. if(table[position] == undefined) {
  5. table[position] = new LinkedList();
  6. }
  7. table[position].append(new valuePair(key, value));
  8. };
  9. //get方法
  10. this.get = function(key) {
  11. let position = loseloseHashCode(key);
  12. if(table[position] !== undefined){
  13. let current = table[position].getHead();
  14. //遍历链表来寻找键/值
  15. while(current.next) {
  16. if(current.element.key === key) {
  17. return current.element.value;
  18. }
  19. current = current.next;
  20. }
  21. //检查元素在链表第一个或最后一个节点的情况
  22. if(current.element.key === key){
  23. return current.element.value;
  24. }
  25. }
  26. return undefined
  27. }
  28. //remove方法
  29. this.remove = function(key) {
  30. let position = loseloseHashCode(key);
  31. if(table[position] !== undefined) {
  32. let current = table[position].getHead();
  33. while(current.next){
  34. if(current.element.key === key) {
  35. table[position].remove(current.element);
  36. if(table[position].isEmpty()){
  37. table[position] = undefined;
  38. }
  39. return true
  40. }
  41. current = current.next;
  42. }
  43. //检查是否为第一个或最后一个元素
  44. if(current.element.key === key) {
  45. table[position].remove(current.element);
  46. if(table[position].isEmpty()) {
  47. table[position] = undefined;
  48. }
  49. return true;
  50. }
  51. }
  52. return false;
  53. }

线性探查

当向表中某个位置加入一个新元素的时候,如果索引为index的位置已经被占,就尝试index+1,+2。。。

  1. //put方法
  2. this.put = function(key,value){
  3. let position = loseloseHashCode(key);
  4. if(table[position] == undefined) {
  5. table[position] = new ValuePair(key, value);
  6. } else {
  7. let index = ++position;
  8. while (table[index] != undefined){
  9. index++;
  10. }
  11. table[index] = new ValuePair(key, value);
  12. }
  13. };
  14. //get方法
  15. this.get = function(key){
  16. let position = loseloseHashCode(key);
  17. if(table[position] !== undefined) {
  18. if(table[position].key === key) {
  19. return table[position].value;
  20. } else {
  21. let index = ++position;
  22. while(table[index] === undefined || table[index].key !== key) {
  23. index++;
  24. }
  25. if(table[index].key === key) {
  26. return table[index].value;
  27. }
  28. }
  29. }
  30. return undefined;
  31. };
  32. //remove方法
  33. this.get = function(key){
  34. let position = loseloseHashCode(key);
  35. if(table[position] !== undefined) {
  36. if(table[position].key === key) {
  37. table[index] = undefined;
  38. } else {
  39. let index = ++position;
  40. while(table[index] === undefined || table[index].key !== key) {
  41. index++;
  42. }
  43. if(table[index].key === key) {
  44. table[index] = undefined;
  45. }
  46. }
  47. }
  48. return undefined;
  49. };

创建更好的散列函数

我们实现的loselose散列函数并不是一个良好的散列函数,因为它会产生太多的冲突。下面实现一个djb2:

  1. let djb2HashCode = function(key) {
  2. let hash = 5381;
  3. for(let i = 0; i<key.length; i++) {
  4. hash = hash * 33 + key.charCodeAt(i);
  5. }
  6. return hash % 1013;
  7. }

他包括初始化一个hash变量并赋值为一个质数(大多数实现都使用5381),然后迭代参数key,将hash与33相乘(用来当做一个魔力数),并和当前迭代到的字符的ASCII码值相加。最后使用相加的和与另一个随机质数相除的余数。