JavaScript:实现哈希表

119 阅读2分钟

本文内容取此教程中的P55~P76,如需了解哈希表理论知识请先观看视频

以下代码采用链地址法来实现哈希表

  • 实现的哈希表(基于storage的数组)每个index对应的是一个数组(bucket).(当然基于链表也可以)
  • bucket中存放什么呢?我们最好将key和value都放进去,我们继续使用一个数组.(其实其他语言使用元组更好)
  • 最终我们的哈希表的数据格式是这样: [[ [k,v], [k,v], [k,v]], [[k,v], [k,v], [k,v]]]

注:完整代码见最后

一、插入和修改数据

image.png

// 插入和修改
HashTable.prototype.put = function(key, value) {
  // 1、根据key获取对应的index
  let index = this.hashFunc(key, this.limit)

  // 2、根据index获取对应的bucket(桶)
  let bucket = this.storage[index]

  // 3、判断bucket是否存在
  if (!bucket) {
    bucket = []
    this.storage[index] = bucket
  }

  // 4、判断是否是修改数据
  for (let element of bucket) {
    if (element[0] === key) {
      element[1] = value
      return
    }
  }

  // 5、添加操作
  bucket.push([key, value])
  this.count += 1

  // 6、判断是否需要扩容操作
  if (this.count > this.limit*0.75) {
    this.resize(this.getPrime(this.limit*2))
  }
}

二、获取数据

思路

  1. 根据key获取对应的index
  2. 根据index获取对应的bucket
  3. 判断bucket是否为null;如果为null,直接返回null
  4. 线性查找bucket中每一个key是否等于传入的key,如果等于,那么直接返回对应的value
  5. 遍历玩后,依然没有找到对应的key大 直接return null即可
HashTable.prototype.get = function(key) {
  let index = this.hashFunc(key, this.limit)

  let bucket = this.storage[index]

  if (!bucket) {
    return null
  }

  for (let element of bucket) {
    if (element[0] === key) {
      return element[1]
    }
  }

  return null
}

三、删除数据

思路

  1. 根据key获取对应的index
  2. 根据index获取bucket
  3. 判断bucket是否存在,如果不存在,那么直接返回null
  4. 线性查找bucket,寻找对应的数据,并且删除
  5. 依然没有找到,那么返回nul
HashTable.prototype.remove = function(key) {
  let index = this.hashFunc(key, this.limit)

  let bucket = this.storage[index]

  if (!bucket) return null

  for (let i=0; i<bucket.length; i++) {
    let tuple = bucket[i]
    if (tuple[0] === key) {
      bucket.splice(i, 1)
      this.count -= 1

      // 6、判断是否需要扩容操作
      if (this.limit >7 && this.count < this.limit*0.25) {
        this.resize(this.getPrime(Math.floor(this.limit/2)))
      }

      return tuple[1]
    }
  }

  return null

}

四、其他方法

// 判断哈希表是否为空
HashTable.prototype.isEmpty = function() {
  return this.count === 0
}

// 获取哈希表数据总数
HashTable.prototype.size = function() {
  return this.count
}

// 判断是否为质数:扩容需要
HashTable.prototype.isPrime = function(num) {
  for (let i=2; i<=parseInt(Math.sqrt(num)); i++){
    if (num % i === 0) {
      return false
    }

    console.log(i)
  }

  return true
}

高效判断质数的方法

五、扩容

HashTable.prototype.resize = function(newLimit) {
  // 1、保存旧的数组内容
  let oldStorage = this.storage

  // 2、重置所有属性
  this.storage = []
  this.count = 0
  this.limit = newLimit

  // 3、遍历oldStorage中的所有bucket
  for (let bucket of oldStorage) {
    if (bucket) {
      for (let tuple of bucket) {
        this.put(tuple[0], tuple[1])
      }
    }
  }
}

// 获取质数:为了实现容量恒为质数
HashTable.prototype.getPrime = function(num) {
  while (!this.isPrime(num)) {
    num++
  }
  return num
}

六、完整代码

function HashTable() {
  // 属性
  this.storage = []
  this.count = 0 // 当前已存放的元素
  this.limit = 7 // 当前数组总长度

  // 方法
  HashTable.prototype.hashFunc = function(str, size) {
    let hashCode = 0

    // 霍纳算法,来计算hashCode的值
    for (let i=0; i<str.length; i++) {
      hashCode = 37 * hashCode + str.charCodeAt(i)
    }

    // 取余操作
    const index = hashCode % size

    return index
  }

  // 插入和修改
  HashTable.prototype.put = function(key, value) {
    // 1、根据key获取对应的index
    let index = this.hashFunc(key, this.limit)

    // 2、根据index获取对应的bucket(桶)
    let bucket = this.storage[index]

    // 3、判断bucket是否存在
    if (!bucket) {
      bucket = []
      this.storage[index] = bucket
    }

    // 4、判断是否是修改数据
    for (let element of bucket) {
      if (element[0] === key) {
        element[1] = value
        return
      }
    }

    // 5、添加操作
    bucket.push([key, value])
    this.count += 1

    // 6、判断是否需要扩容操作
    if (this.count > this.limit*0.75) {
      this.resize(this.getPrime(this.limit*2))
    }
  }

  HashTable.prototype.get = function(key) {
    let index = this.hashFunc(key, this.limit)

    let bucket = this.storage[index]

    if (!bucket) {
      return null
    }

    for (let element of bucket) {
      if (element[0] === key) {
        return element[1]
      }
    }

    return null
  }

  HashTable.prototype.remove = function(key) {
    let index = this.hashFunc(key, this.limit)

    let bucket = this.storage[index]

    if (!bucket) return null

    for (let i=0; i<bucket.length; i++) {
      let tuple = bucket[i]
      if (tuple[0] === key) {
        bucket.splice(i, 1)
        this.count -= 1

        // 6、判断是否需要扩容操作
        if (this.limit >7 && this.count < this.limit*0.25) {
          this.resize(this.getPrime(Math.floor(this.limit/2)))
        }

        return tuple[1]
      }
    }

    return null

  }

  HashTable.prototype.isEmpty = function() {
    return this.count === 0
  }

  HashTable.prototype.size = function() {
    return this.count
  }

  HashTable.prototype.resize = function(newLimit) {
    // 1、保存旧的数组内容
    let oldStorage = this.storage

    // 2、重置所有属性
    this.storage = []
    this.count = 0
    this.limit = newLimit

    // 3、遍历oldStorage中的所有bucket
    for (let bucket of oldStorage) {
      if (bucket) {
        for (let tuple of bucket) {
          this.put(tuple[0], tuple[1])
        }
      }
    }
  }

  HashTable.prototype.getPrime = function(num) {
    while (!this.isPrime(num)) {
      num++
    }
    return num
  }

  HashTable.prototype.isPrime = function(num) {
    for (let i=2; i<=parseInt(Math.sqrt(num)); i++){
      if (num % i === 0) {
        return false
      }

      console.log(i)
    }

    return true
  }
}

const ht = new HashTable()
ht.put('a', 1)
alert(ht.get('a')) // 输出: 1
ht.remove('a')
alert(ht.get('a')) // 输出: null