JavaScript数据结构 --- 散列

JavaScript数据结构 --- 散列

散列是一种常用的数据存储技术,散列后的数据可以快速地插入或取用,但查找操作却效率低下,这是可以依靠其他数据结构(例如二叉查找树)。

散列使用的数据结构叫散列表,这里的散列表是基于数组进行设计的。所有元素根据该元素对应的(类型字典中的键),保存在数组的特定位置。使用散列表存储数据时,通过一个散列函数 将键映射为一个长度为 0 到散列表长度的数字。

一般来说,数组的长度是有限的,所以我们让散列函数尽量将键均匀地映射到数组中。
HashTable


1. HashTable 类
使用一个类来表示散列表。


function HashTable() {
   this.table = new Array(137); // 构建一个新数组用来存储 散列表
   this.simpleHash = simpleHash; // 一个简单的散列函数
   this.showDistro = showDistro; // 显示散列表中的数据
   this.put = put; // 将数据插入散列表
}

1.1 选择散列函数
散列函数的选择依赖键值的数据类型。如果键是整形,最简单的散列函数就是以数组的长度对键取余。当很多键值是数组的长度的倍数时,这种方式会导致大规模碰撞(散列值一样)。所以一般将数组的长度设为质数。

针对字符串类型的散列函数比较困难,要更加小心选择。

我们定义的 smpleHash 函数是将每个字符的 ASCII 码相加。如此散列值就是 ASCII 码值之和除以数组长度得到的余数。

function simpleHash(data) {
   var total = 0;
   for (var i = 0; i < data.length; i++) {
      total += data.charCodeAt(i);
   }
   return total % this.table.length;
}

再定义用来将数据存入散列表的方法 put() 和用来显示散列表中的数据的方法 showDistro() 。

function showDistro() {
   var n = 0;
   for (var i = 0; i < this.table.length; i++) {
      if (this.table[i] != undefined) {
         console.log(i + " : " + this.table[i]);
      }
   }
}

测试用例
HashTable1
程序结果:

 35: Cynthia
 45: Clayton
 57: Donnie
 77: David
 95: Danny
 116: Mike
 132: Jennifer
 134: Jonathan

根据结果来看,数据没有均匀分布,有时候还会出现碰撞(散列值一样)的情况,我们需要改善散列函数。


1.2 更好的散列函数

使用 霍纳德算法 得到更好的散列函数。

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);
}

测试用例:
betterHash

结果为:

12: Jennifer
22: Raymond
55: Donnie
58: Clayton
80: Jonathan
82: Mike
103: Cynthia
110: Danny

1.3 散列化整型键

这里使用的数据集是学生的成绩,随机产生一个9位数的键,用来识别学生身份和一门成绩。

function getRandomInt (min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

function genStuData(arr) {
   for (var i = 0; i < arr.length; ++i) {
      var num = "";
      for (var j = 1; j <= 9; ++j) {
         num += Math.floor(Math.random() * 10);
      }
      num += getRandomInt(50,100); //可以指定随机数的最大值和最小值
      arr[i] = num;
   }
}

genStuDate() 函数生成学生的数据。里层的循环生成 ID,后面的循环代码生成一个随机的成绩,并把成绩添加在ID后面。主程序会分离ID和成绩,散列函数将学生ID里的数字相加,使用 simpleHash() 函数计算出散列值。
示例程序:
HashTable2

运行结果为:

 Student data:

 04437992 89
 35054068 96
 34780034 52
 34103157 91
 29752520 74
 96057108 80
 55413450 57
 33664847 61
 33179680 71
 91022430 51


 Data distribution:

 9: 91022430251
 16: 34103157291
 18: 34780034252
 26: 29752520374
 27: 55413450857
 28: 33664847061
 30: 96057108680
 32: 33179680771
 35: 35054068996
 44: 04437992989

使用更好的 散列函数 betterHash(),得到的输出。
示例程序:
betterTable2.sj

 Student data:

 90605894 100
 41483254 96
 42802463 86
 25395996 90
 39102248 97
 52528869 68
 53083791 94
 01904985 63
 51172821 61
 84746600 82


 Data distribution:

 19: 84746600382
 40: 39102248697
 45: 01904985263
 61: 53083791894
 64: 42802463486
 70: 25395996790
 93: 52528869068
 105: 41483254496
 112: 906058948100
 127: 51172821561

通过对比,可以发现无论是字符串还是整型, betterHash() 的散列效果都更胜一筹。

原文地址:https://www.cnblogs.com/zhoufulin/p/5008928.html