哈希表/散列表(Hash Table)

Hash Table是一种用于存储键值对(key value pair)的数据结构,因为Hash Table根据key查询value的速度很快,所以它常用于实现Map、Dictinary、Object等数据结构。如上图所示,Hash Table内部使用一个hash函数将传入的键转换成一串数字,而这串数字将作为键值对实际的key,通过这个key查询对应的value非常快,时间复杂度将达到O(1)。Hash函数要求相同输入对应的输出必须相等,而不同输入对应的输出必须不等,相当于对每对数据打上唯一的指纹。

一个Hash Table通常具有下列方法:

  1. add:增加一组键值对
  2. remove:删除一组键值对
  3. lookup:查找一个键对应的值


 哈希表的优缺点

哈希表通常是基于数组实现的,但相对于数组,它有很多优势:

  • 快速的插入、删除、查找操作, 时间复杂度接近常量值,时间复杂度为 O(1)
  • 哈希表的速度比树还要快,代码相对树来说简单很多
缺点:
  • 哈希表中的数据没有顺序,因此不能通过固定的方式遍历元素
  • 哈希表是通过空间来换取时间是数据结构,通过占用更多的内存空间来提高操作效率

碰撞解决方法

开链法:两个键相同保存位置一样,开辟第二数组,也称第二个数组为链,适合空间小,数据量大

线性探测法属于开放寻址散列,查找散列位置如果当前位置没有继续寻找下一个位置。存储数据较大较适合。数组大小>=1.5*数据(开链法),数组大小>=2*数据(线性探测法)


js实现

 /**
  * 一个简单的散列表
  * @constructor
  */
 function HashTable() {
this.table = new Array(137);          // 定义数组长度
this.simpleHash = simpleHash;         // 简单的散列函数
this.betterHash = betterHash;         // 简单的散列函数
this.showDistro = showDistro;         // 显示元素
this.put = put;                       // 插入元素
this.openPut = openPut;               // 开链法插入元素
this.linkPut = linkPut;               // 线性探测法插入元素
this.get = get;                       // 获取元素
this.bulidTable = bulidTable          //添加二维数组
}
//简单的散列函数 除留余数法
 function simpleHash(data) {
   var total = 0
   for(var i = 0; i < data.length; i++){
     total += data.charCodeAt(i)
   }
   console.log(data + '->total' + total)
   return total % this.table.length
 }
 //显示元素
 function showDistro() {
   for(var i = 0; i < this.table.length; i++) {
     if(this.table[i]  !== undefined) {
       console.log('键值是->' + i + '值是' + this.table[i])
     }
   }
 }
 //插入元素
 function put(data) {
  var pos = this.simpleHash(data)
   this.table[pos] = data
 }

 //获取元素
 function get(data) {
   return this.table[this.simpleHash(data)]
 }

 var ht = new HashTable()
 ht.put('abc')
 ht.put('china')
 ht.put('bbb')
 ht.put('ss')
 ht.put('nicah')
 ht.put('cba')
 ht.showDistro()

可以看到我们插入6个值,最后只显示3个,原因是发生了碰撞


解决方法1:改造hash函数

//改造后的散列函数
function betterHash(data) {
  var h = 31
  var total = 0
  for(var i = 0; i < data.length; i++){
    total += h*total + data.charCodeAt(i)
  }
  console.log(data + '->total' + total)
  return total % this.table.length
}
//更改put
function put(data) {
 var pos = this.betterHash(data) //使用betterHash
  this.table[pos] = data
}

可以看到其他元素可以显示出来了,但是添加相同元素的时候,不显示


解决方法2:开链法

在创建存储散列过的键值数组时,创建一个新的空数组,然后将该数组付给散列表中的每个数组元素,这样创建了一个二维数组,也称第二个数组为链。

//添加二维数组
function bulidTable() {
  for(var i = 0; i<this.table.length; i++){
    this.table[i] = new Array()
  }
}//开链法插入元素
function openPut(data) {
  var pos = this.simpleHash(data)
  var index = 0
  if(this.table[pos][index] === undefined) {
    this.table[pos][index] = data
    index ++
  }else {
    while(this.table[pos][index] !== undefined) {
       ++index
    }
    this.table[pos][index] = data
  }
}
//显示元素更改
function showDistro() {
  for(var i = 0; i < this.table.length; i++) {
    if(this.table[i][0] !== undefined) {
      console.log('键值是->' + i + '值是' + this.table[i])
    }
  }
}
//插入元素更改为simpleHash
function put(data) {
 var pos = this.simpleHash(data)
 this.table[pos] = data
}

var ht = new HashTable()
ht.bulidTable()
ht.openPut('abc')
ht.openPut('china')
ht.openPut('bbb')
ht.openPut('ss')
ht.openPut('nicah')
ht.openPut('cba')
ht.openPut('cba')
ht.showDistro()

可以看到所有元素都被显示出来了


解决方法3:线性探测法(寻址法)

当发生碰撞时,检测下一个位置是否为空。如果为空,就将此数据存入该位置;如果不为空,则继续检查下一个位置,直到找到下一个空的位置为止。

//线性探测法
function linkPut(data) {
  var pos = this.simpleHash(data)
  if(this.table[pos] === undefined) {
    this.table[pos] = data
  }else {
    while(this.table[pos] !== undefined) {
      pos++
    }
    this.table[pos] = data
  }
}
//显示元素
function showDistro() {
  for(var i = 0; i < this.table.length; i++) {
    if(this.table[i]  !== undefined) {
      console.log('键值是->' + i + '值是' + this.table[i])
    }
  }
}

var ht = new HashTable()
// ht.bulidTable()
ht.linkPut('abc')
ht.linkPut('china')
ht.linkPut('bbb')
ht.linkPut('ss')
ht.linkPut('nicah')
ht.linkPut('cba')
ht.linkPut('cba')
ht.showDistro()

可以看到所有元素都被显示出来了




链接: https://fly63.com/course/28_1306