散列是一种常用的数据存储,可以快速的插入和取用,散列使用的数据结构叫散列表。
1. 散列概述
散列表是基于数组进行设计的,散列函数尽量均匀地将键映射到数组中。
1.1 装填因子
一般情况下,设散列表空间大小为m,填入表中的元素个数是n,则称α=n/m为散列表的装填因子,例如大小为17,元素为11,装填因子为0.65.实用时,常见散列表大小设计使得α=0.5~0.8为宜。
它有以下问题的需要解决:
- 存在将两个键映射到同一个值得可能,这种现象叫做碰撞。我们需要找到方案去解决。
2. HashTable类
定义一个HashTable的构造函数
function HashTable() {
this.table = new Array(137)
}
实现一个简单的散列函数
HashTable.prototype.simpleHash = function(data) {
var total = 0
for(let i = 0; i < data.length; i ++) {
total += data.charCodeAt(i)
}
return total % this.table.length
}
实现一个put和showDistro方法
HashTable.prototype.put = function(data) {
var pos = this.simpleHash(data)
this.table[pos] = data
}
HashTable.prototype.showDistro = function(data) {
for(var i = 0; i < this.table.length; i ++) {
if(this.table[i] != undefined) {
console.log(i + ": " + this.table[i])
}
}
}
测试一下这个简单的散列函数做的散列
var someNames = ['David', 'Jennifer', 'Donnie', 'Raymond', 'Cynthia', 'Mike', 'Clayton', 'Danny', 'Jonathan']
var hTable = new HashTable()
for(let i = 0; i < someNames.length; i ++) {
hTable.put(someNames[i])
}
hTable.showDistro()
输出结果如下:
35: Cynthia
45: Clayton
57: Donnie
77: David
95: Danny
116: Mike
132: Jennifer
134: Jonathan
发现有两个问题
- 分布不均匀
- 数据显示不全,原因是其中两个数据的散列值是一样的,这种情况称之为碰撞
对于分布不均匀问题,我们可以设计更好的散列函数
HashTable.prototype.betterHash = function(string) {
const H = 37;
var total = 0;
for (var i = 0; i < string.length; ++i) {
total += H * total + string.charCodeAt(i);
}
total = total % this.table.length;
if (total < 0) {
total += this.table.length-1;
}
return parseInt(total);
}
3. 碰撞处理
1. 开链法
具体方法如下:
- table创建一个二维数组
- 如果两个键值相等,依然保存在同一个位置,只不过在第二个数组的位置不同。如上图的键值2。
我们修改下HashTable构造函数
function HashTable() {
this.table = new Array(137).fill([])
}
修改一下put函数
HashTable.prototype.put = function(key, data) {
var pos = this.betterHash(key)
var index = 0
if(this.table[pos][index] === undefined) {
this.table[pos][index] = key
this.table[pos][index + 1] = data
} else {
while(this.table[pos][index] !== undefined){
index ++
}
this.table[pos][index] = key
this.table[pos][index + 1] = data
}
}
新增一个get方法
HashTable.prototype.get = function(key) {
var index = 0
var pos = this.betterHash(key)
if(this.table[pos][index] === key) {
return this.table[pos][index + 1]
} else {
while(this.table[pos][index] !== key) {
index += 2
}
return this.table[pos][index + 1]
}
return undefined
}
2. 线性探测法
- 如果发生碰撞,检查下一个位置是否为空,如果为空,将数据存入该位置
- 如果不为空,继续检查下一个位置,直到找到空的位置为止。 我们修改一下构造函数
function HashTable() {
this.table = new Array(137)
this.values = []
}
修改下put方法
HashTable.prototype.put = function(key, data) {
var pos = this.betterHash(key)
if(this.table[pos] === undefined) {
this.table[pos] = key
this.values[pos] = data
} else {
while(this.table[pos] !== undefined) {
pos ++
}
this.table[pos] = key
this.values[pos] = data
}
}
修改下get方法
HashTable.prototype.get = function(key) {
var pos = -1
var pos = this.betterHash(key)
if(pos > -1) {
for(let i = pos; this.table[pos] !== undefined; i ++) {
if(this.table[pos] === key) {
return this.values[pos]
}
}
}
return undefined
}
综述
利用散列表,我们可以更快的去存储和获取值。