【JS数据结构与算法】哈希表的封装及其相关操作方法

目录

一、哈希表相关操作方法的前提

二、哈希表的封装

三、效率提升

四、方法实现。

1、向哈希表添加元素put(key, value)。

2、获取某个元素的值get(key)。

3、删除操作remove(key)

4、其他方法

5、代码测试

五、完整代码


一、哈希表相关操作方法的前提

如果还不了解什么是哈希表,可以去看博文:认识哈希表实现哈希函数。这里介绍的是链地址法

二、哈希表的封装

JS数据结构中,HashTable的封装,定义三个属性:

  •  this.storage 哈希表的实质是数组,用数组来存放数据。
  •  this.count  记录当前哈希表中已经存放了多少数据。
  • this.limit 数组总长,记录数组中一共可以存放多少个元素,为质数。
function HashTable(){
      // 属性
      this.storage = [];
      this.count = 0;
      this.limit = 7;
 }

在实现各种操作之前,首先需要使用原型将我们封装好的哈希函数引入进来。

// 哈希函数
HashTable.prototype.hashFunc = function (str, size) {
  var hashCode = 0;

  for (var i = 0; i < str.length; i++) {
    hashCode = 37 * hashCode + str.charCodeAt();
  }

  var index = hashCode % size;

  return index;
}

三、效率提升

注意:

count limit 的比值成为装填因子(总数据项 / 哈希表长度)。

  • 在开放地址法中,装填因子最大是1,因为它必须查找到空白的位置才能够将元素放进去,而链地址法的装填因子可能大于1,你可以无限的插入元素,但是当装填因子过大时,也就是随着数据量的增多,每一个index对应的桶bucket会越来越长,我们进行插入查找和删除操作的效率都会变得非常低,这时候就需要扩容
  • 当然当进行删除操作时,我们的数据变得越来越少,这时候的count变小,而装填因子变得越来越小,也会造成效率变低,这时候哦就需要缩容

如何扩容和缩容

在需要扩容的时候,将数组的长度扩大到两倍,但是前面我们提到limit 是数组总长,记录数组中一共可以存放多少个元素,为质数。因此我们需要一个算法来得到扩容后的数组总长,涉及到一个判断并寻找质数扩容可以将原有的数组长度*2,再一次加1寻找质数。缩容可以将原有的数组长度 / 2,再一次1寻找质数。

算法如下:

1、判断是否为质数

      // 判断是否为质数
      HashTable.prototype.isPrime = function (num) {
        var temp = parseInt(Math.sqrt(num));

        for (var i = 2; i <= temp; i++) {
          if (num % i == 0) return false;
        }

        return true;
      }

2、获取质数

      // 获取质数的函数
      HashTable.prototype.getPrime = function (num) {
        while(!this.isPrime(num)){
          num++;
        }
        return num;
      }

3、重新存储

      // 扩容和缩容的重新存储
      HashTable.prototype.resize = function (newLimit) {
        // 1、将旧的数组内容保存到oldStorage中
        var oldStorage = this.storage;

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

        // 3、遍历oldStorage中的所有的桶bucket
        for (var i = 0; i < oldStorage.length; i++) {
            // 3.1取出对应的bucket
            var bucket = oldStorage[i];

            // 3.2判断bucket是否为空
            if (bucket == null) continue;

            // 3.3 bucket不为空,则将元素重新插入到新的数组中
            for (var j = 0; j < bucket.length; j++) {
              var tuple = bucket[j];
              this.put(tuple[0], tuple[1]);
            }
        }
      }

何时需要扩容和缩容

当总数据项 / 哈希表长度 = count / limit,即装填因子大于0.75的时候需要扩容

当哈希表长度大于7,且装填因子小于0.25的时候需要缩容

四、方法实现。

1、向哈希表添加元素put(key, value)。

思路: 1、根据key获取index值,找到相应的位置。
            2、根据对应的index位置,获取对应的bucket。

  •            1)如果桶存在就直接插入或修改。
  •            2)如果桶不存在则创建桶。

            3、判断是该桶中是否存在该key。

  •           1)如果存在则修改元素的value值。
  •           2)如果不存在则遍历到最后将之插入。

代码实现:

      HashTable.prototype.put = function (key, value) {

          // 1、根据key获取index值,找到相应的位置
          var index = this.hashFunc(key, this.limit);

          // 2、根据对应的index位置,获取对应的bucket
          var bucket = this.storage[index];

          // 如果桶不存在则创建桶
          if (bucket == null) {
            bucket = [];
            this.storage[index] = bucket;
          }

          // 3、判断是该桶中是否存在该key
          // 1)如果存在则修改元素的value值
          for (var i = 0; i < bucket.length; i++) {
            var tuple = bucket[i];
            if (tuple[0] == key) {
              tuple[1] = value;
              return ;
            }
          }

          // 2)如果不存在则遍历到最后将之插入
          bucket.push([key, value]);

          this.count += 1;
          
          // 4、判断是否需要扩容
          if (this.count > this.limit * 0.75) {
            var newSize = this.limit * 2;
            var newPrime = this.getPrime(newSize);
            this.resize(newPrime);
          }
      }

2、获取某个元素的值get(key)。

思路:

         1、根据key获取index值,找到相应的位置。

          2、根据对应的index位置,获取对应的桶bucket。

  •            如果桶bucket不存在则返回null。
  •            如果桶bucket存在,则遍历查找是否存在该key值。
  •                  存在则返回value值。
  •                  不存在返回null。

代码实现:

        // 获取操作
        HashTable.prototype.get = function (key) {

        // 1、根据key获取index值,找到相应的位置
        var index = this.hashFunc(key, this.limit);

        // 2、根据对应的index位置,获取对应的bucket
        var bucket = this.storage[index];


        // 如果桶不存在则返回null
        if (bucket == null) return null;

        // 如果桶存在,则遍历查找是否存在该key值,存在则返回value值
        for (var i = 0; i < bucket.length; i++) {
          var tuple = bucket[i];
          if (tuple[0] == key) {
            return tuple[1];
          }
        }

        // 不存在返回null
        return null;
      }

3、删除操作remove(key)

该操作与get方法很相似。

 思路:

            1、根据key获取index值,找到相应的位置
            2、根据对应的index位置,获取对应的bucket

  •               如果桶bucket不存在则返回null
  •               如果桶bucket存在,则遍历查找是否存在该key值。
  •                     存在进行删除操作,并返回其值
  •                     不存在返回null

代码实现:

      // 删除操作
       HashTable.prototype.remove = function (key) {
        // 1、根据key获取index值,找到相应的位置
        var index = this.hashFunc(key, this.limit);

        // 2、根据对应的index位置,获取对应的bucket
        var bucket = this.storage[index];


        // 如果桶不存在则返回null
        if (bucket == null) return null;


        // 如果桶存在,则遍历查找是否存在该key值,存在进行删除操作,并返回其值
        for (var i = 0; i < bucket.length; i++) {
          var tuple = bucket[i];
          if (tuple[0] == key) {
            bucket.splice(i, 1);
            this.count -= 1;

            // 3、判断是否需要缩容
            if (this.limit > 7 && this.count < this.limit * 0.25) {
              var newSize = Math.floor(this.limit / 2);
              var newPrime = this.getPrime(newSize);
              this.resize(newPrime);
            }

            return tuple[1];
          }
        }

        // 不存在返回null
        return null;
      }

4、其他方法

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

      // 返回哈希表的长度
      HashTable.prototype.size = function () {
        return this.count;
      }

5、代码测试

    // 测试代码
    // 1、创建哈希表
    var ht = new HashTable();

    // 2、插入数据
    ht.put('abc', '123');
    ht.put('cba', '321');
    ht.put('hjg', '564');
    ht.put('kuo', '854');

    // 3、获取数据
    alert(ht.get('abc')); //123

    // 4、修改操作
    ht.put('abc', '222');
    alert(ht.get('abc'));  //222


    // 5、删除操作
    ht.remove('abc');
    alert(ht.get('abc'));  //null

五、完整代码

<!DOCTYPE html>
<html>
<head>
  <title>哈希表相关的函数</title>
</head>
<body>

  <script type="text/javascript">
    function HashTable(){


      // 属性
      this.storage = [];
      this.count = 0;
      this.limit = 7;

      // 哈希函数
      HashTable.prototype.hashFunc = function (str, size) {
        var hashCode = 0;

        for (var i = 0; i < str.length; i++) {
          hashCode = 37 * hashCode + str.charCodeAt();
        }

        var index = hashCode % size;

        return index;
      }

      // 方法
      // 添加操作
      HashTable.prototype.put = function (key, value) {

        // 1、根据key获取index值,找到相应的位置
        // 2、根据对应的index位置,获取对应的bucket
          // 1)如果桶存在就直接插入或修改
          // 2)如果桶不存在则创建桶
        // 3、判断是该桶中是否存在该key
          // 1)如果存在则修改元素的value值
          // 2)如果不存在则遍历到最后将之插入

          // 1、根据key获取index值,找到相应的位置
          var index = this.hashFunc(key, this.limit);

          // 2、根据对应的index位置,获取对应的bucket
          var bucket = this.storage[index];

          // 如果桶不存在则创建桶
          if (bucket == null) {
            bucket = [];
            this.storage[index] = bucket;
          }

          // 3、判断是该桶中是否存在该key
          // 1)如果存在则修改元素的value值
          for (var i = 0; i < bucket.length; i++) {
            var tuple = bucket[i];
            if (tuple[0] == key) {
              tuple[1] = value;
              return ;
            }
          }

          // 2)如果不存在则遍历到最后将之插入
          bucket.push([key, value]);

          this.count += 1;
          
          // 4、判断是否需要扩容
          if (this.count > this.limit * 0.75) {
            var newSize = this.limit * 2;
            var newPrime = this.getPrime(newSize);
            this.resize(newPrime);
          }
      }

      // 获取操作
      HashTable.prototype.get = function (key) {

        // 1、根据key获取index值,找到相应的位置
        var index = this.hashFunc(key, this.limit);

        // 2、根据对应的index位置,获取对应的bucket
        var bucket = this.storage[index];


        // 如果桶不存在则返回null
        if (bucket == null) return null;

        // 如果桶存在,则遍历查找是否存在该key值,存在则返回value值
        for (var i = 0; i < bucket.length; i++) {
          var tuple = bucket[i];
          if (tuple[0] == key) {
            return tuple[1];
          }
        }

        // 不存在返回null
        return null;
      }

      // 删除操作
      HashTable.prototype.remove = function (key) {
        // 1、根据key获取index值,找到相应的位置
        var index = this.hashFunc(key, this.limit);

        // 2、根据对应的index位置,获取对应的bucket
        var bucket = this.storage[index];


        // 如果桶不存在则返回null
        if (bucket == null) return null;


        // 如果桶存在,则遍历查找是否存在该key值,存在进行删除操作,并返回其值
        for (var i = 0; i < bucket.length; i++) {
          var tuple = bucket[i];
          if (tuple[0] == key) {
            bucket.splice(i, 1);
            this.count -= 1;

            // 3、判断是否需要缩容
            if (this.limit > 7 && this.count < this.limit * 0.25) {
              var newSize = Math.floor(this.limit / 2);
              var newPrime = this.getPrime(newSize);
              this.resize(newPrime);
            }

            return tuple[1];
          }
        }

          // 不存在返回null
          return null;
      }

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

      // 返回哈希表的长度
      HashTable.prototype.size = function () {
        return this.count;
      }

      // 判断是否为质数
      HashTable.prototype.isPrime = function (num) {
        var temp = parseInt(Math.sqrt(num));

        for (var i = 2; i <= temp; i++) {
          if (num % i == 0) return false;
        }

        return true;
      }

      // 获取质数的函数
      HashTable.prototype.getPrime = function (num) {
        while(!this.isPrime(num)){
          num++;
        }
        return num;
      }

      // 扩容和缩容的重新存储
      HashTable.prototype.resize = function (newLimit) {
        // 1、将旧的数组内容保存到oldStorage中
        var oldStorage = this.storage;

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

        // 3、遍历oldStorage中的所有的桶bucket
        for (var i = 0; i < oldStorage.length; i++) {
            // 3.1取出对应的bucket
            var bucket = oldStorage[i];

            // 3.2判断bucket是否为空
            if (bucket == null) continue;

            // 3.3 bucket不为空,则将元素重新插入到新的数组中
            for (var j = 0; j < bucket.length; j++) {
              var tuple = bucket[j];
              this.put(tuple[0], tuple[1]);
            }
        }
      }
    }



    // 测试代码
    // 1、创建哈希表
    var ht = new HashTable();

    alert(ht.isPrime(11)); //true
    alert(ht.isPrime(123)); //false
    alert(ht.isPrime(17)); //true
    alert(ht.isPrime(9)); //false

    // 2、插入数据
    ht.put('abc', '123');
    ht.put('cba', '321');
    ht.put('hjg', '564');
    ht.put('kuo', '854');

    // 3、获取数据
    alert(ht.get('abc')); //123

    // 4、修改操作
    ht.put('abc', '222');
    alert(ht.get('abc'));  //222


    // 5、删除操作
    ht.remove('abc');
    alert(ht.get('abc'));  //null


  </script>

</body>
</html>

猜你喜欢

转载自blog.csdn.net/weixin_42339197/article/details/99630150