JavaScript数据结构与算法4

67 阅读2分钟

参考

哈希表(HashTable)

  • 数组和链表优缺点

    • 因为原来数组的增加、修改、删除元素和根据内容查找元素的操作效率低,但根据下标查找元素的效率高
    • 链表的查找需要遍历链表,效率较低,而其它操作效率较高
  • 哈希表优缺点

    • 查找、插入、删除效率高,但空间利用率低
    • 但不能快速找出哈希表中的最大、最小这些值
  • 什么是哈希表?

    • 通过将关键字转换成相应的数组下标,就可以直接根据此下标来进行增、删、查、改
    • 哈希表元素是无序的,key是不重复的,但不同的key通过哈希函数产生的HashCode有可能相同,因此需要解决此类问题(冲突)
  • 解决冲突的方法

    • 链地址法(拉链法)
      • 数组中每个元素存储数组或者链表
    • 开放地址法
      • 按照线性探测、二次探测、再哈希法(步长不同),去寻找数组中的空白位置进行存储有冲突的元素
        • 线性探测:连续位置填充数据,聚集现象导致探测步长会很长
        • 二次探测:能防止出现连续位置填充数据的现象
        • 再哈希法:stepSize = const - (HashCode % const)
  • 优秀的哈希函数

    • 尽量少用乘法、除法
      • 如:多项式用秦九韶算法来优化
    • 尽可能让数据均匀分步(即常量尽量用质数)
      • 如:容量用质数
// 基于数组来模拟链地址法 来实现哈希表
/*
  [
    [[key1,value1]],
    [[key2,value2]]...
  ]
*/
function HashTable(){
  this.storage = [];
  this.count = 0;// 已存放元素个数
  this.limit = 7// 最多可存储的元素个数
    
  // resize(newLimit)
  // loadFactor:加载因子 > 0.75进行扩容,< 0.25 进行缩小
  HashTable.prototype.resize = function(newLimit){
    // 1、记录旧哈希表 
    var oldStorage = this.storage;
    
    // 2、新哈希表初始化
    this.count = 0;
    this.storage = [];
    this.limit = newLimit;
    
    // 3、将旧哈希表重新插入新的哈希表
    var len = oldStorage.length;
    for(var i = 0; i < len; i ++){
      var buckey = oldStorage[i];
      if(!buckey){
        // 桶空
        continue;
      }else{
        // 桶非空,遍历旧桶的元素,将其插入到新哈希表中
        var b_len = buckey.length;
        for(var j = 0; j < b_len; j ++){
          var tuple = buckey[j];
          this.insert(tuple[0],tuple[1]);
        }
      }  
      
      return this.storage;   
    }
   
  }

  // hashFunc(key,limit):根据key获取相应HashCode,0 ~ limit - 1
  HashTable.prototype.hashFunc = function(key,limit){
    var hashCode = 0;
    // 霍纳算法
    var len = key.length;
    for(var i = 0; i < len; i ++){
      hashCode = 37 * hashCode + key.charCodeAt(i);
    }
    var index = hashCode % limit;  
    return index;
  }
  
  // insert(key,value)
  /* 
     1、已存放key,修改value
     2、未存放key,插入[key,value]
  */
  HashTable.prototype.insert = function(key,value){
    // 1、获取HashCode
    var index = this.hashFunc(key,this.limit);
    
    var buckey = this.storage[index];
    // 2、判断桶是否为空
    if(!buckey){
      // 桶空,创建桶
      buckey = [];
      this.storage[index] = buckey;
    }
    
    // 3、遍历桶的元素,已有key,修改value,无则添加
    var len = buckey.length;
    for(var i = 0; i < len; i ++){
      var tuple = buckey[i];
      if(key === tuple[0]){
        tuple[1] = value;
        return true;
      }
    }
    buckey.push([key,value]);
    this.count ++;
    // 4、判断当前数组是否需要扩容    
    if(this.count > this.limit * 0.75){
      // 获取质数后扩容
      var loadFactor = getPrime(this.limit * 2);
      this.resize(loadFactor);
    }

    return true;
  }
  
  // get(key):根据key获取value
  HashTable.prototype.insert = function(key){
    // 1、获取HashCode
    var index = this.hashFunc(key);
  
    // 2、判断桶是否为空
    var buckey = this.storage[index];
    if(!buckey){
      // 桶空
      return null;
    }else{
      // 桶非空,遍历桶的元素找到相应key
      var len = buckey.length;
      for(var i = 0; i < len; i ++){
        var tuple = buckey[i];
        if(key === tuple[0]){
          return tuple[1];
        }
      }
    } 
    
    return null;  
  }
  
  // remove(key):根据key删除[key,value]
  HashTable.prototype.remove = function(key){
    // 1、根据key获取HashCode
    var index = this.hashFunc(key);
    
    // 2、判断桶是否为null
    var buckey = this.storage[index];
    if(!buckey){
      // 桶null
      return false;
    }else{
      // 桶非null,遍历桶的元素
      var len = buckey.length;
      for(var i = 0; i < len; i ++){
        var tuple = buckey[i];
        if(key === tuple[0]){
          buckey.splice(i,1);
          this.count --;
          // 3、判断是否缩容,最小是7
          if(this.limit > 7 && this.count < this.limit * 0.25){
            var loadFactor = this.getPrime(Math.floor(this.limit / 2));
            this.resize(loadFactor);
          }
          return tuple[1];   
        }  
      }
    }

    return false;    
  }

  // isEmpty()
  HashTable.prototype.isEmpty = function(){
    return this.count === 0;
  }
  // size()
  HashTable.prototype.size = function(){
    return this.count;
  }
  
  // isPrime(num)
  HashTable.prototype.isPrime = function(num){
    if(num === 1)
      return false;
    var sqrt_num = parseInt(Math.sqrt(num));
    for(var i = 2; i <= sqrt_num; i ++){
      if(num % i === 0)
        return false;
    }
    return true;
  }  
  
  HashTable.prototype.getPrime = function(num){
    while (!this.isPrime(num)){
      num++;
    }
    return num;
  };
  
}

// 测试代码
var hashTable = new HashTable();
console.log(hashTable.insert("ac", 97));
console.log(hashTable.insert("b", 97));
console.log(hashTable.insert("a", 64));
console.log(hashTable.get("a"));
console.log(hashTable.remove("a"));


// 测试扩容
hashTable.insert("afs", 65);
hashTable.insert("akl", 23);
hashTable.insert("amk", 49);
hashTable.insert("fd", 46);