散列表

95 阅读1分钟

散列是一种常用的数据存储,可以快速的插入和取用,散列使用的数据结构叫散列表。

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. 开链法

image.png

具体方法如下:

  • 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
}

综述

利用散列表,我们可以更快的去存储和获取值。