美文网首页数据结构数据结构与算法
数据结构(十)之哈希表实现

数据结构(十)之哈希表实现

作者: coderwhy | 来源:发表于2018-03-28 18:31 被阅读323次

    如需转载, 请咨询作者, 并且注明出处.
    有任何问题, 可以关注我的微博: coderwhy, 或者添加我的微信: 372623326

    前面, 我们使用了大量的篇幅来解析哈希表的理论知识.

    现在, 我们进入代码的实施阶段, 但是实施之前, 先来深入一个比较重要的话题: 哈希函数.

    一. 哈希函数

    讲了很久的哈希表理论知识, 你有没有发现在整个过程中, 一个非常重要的东西: 哈希函数呢?

    我们这里来探讨一下, 设计好的哈希函数应该具备哪些优点.

    快速的计算

    • 好的哈希函数应该尽可能让计算的过程变得简单, 应该可以快速计算出结果.
      • 哈希表的主要优点是它的速度, 所以在速度上不能满足, 那么就达不到设计的目的了.
      • 提高速度的一个办法就是让哈希函数中尽量少的有乘法和除法. 因为它们的性能是比较低的.
    • 在前面, 我们计算哈希值的时候使用的方式
      • cats = 3*27³+1*27²+20*27+17= 60337
      • 这种方式是直观的计算结果, 那么这种计算方式会进行几次乘法几次加法呢? 当然, 我们可能不止4项, 可能有更多项
      • 我们抽象一下, 这个表达式其实是一个多项式: a(n)xn+a(n-1)x(n-1)+…+a(1)x+a(0)
      • 现在问题就变成了多项式有多少次乘法和加法:
        • 乘法次数: n+(n-1)+…+1=n(n+1)/2
        • 加法次数: n次
    • 多项式的优化: 霍纳法则
      • 解决这类求值问题的高效算法――霍纳法则。在中国,霍纳法则也被称为秦九韶算法。
      • 通过如下变换我们可以得到一种快得多的算法,即Pn(x)= anx n+a(n-1)x(n-1)+…+a1x+a0=((…(((anx +an-1)x+an-2)x+ an-3)…)x+a1)x+a0,这种求值的安排我们称为霍纳法则。
      • 变换后, 我们需要多少次乘法, 多少次加法呢?
        • 乘法次数: N次
        • 加法次数: N次.
      • 如果使用大O表示时间复杂度的话, 我们直接从O(N²)降到了O(N).

    均匀的分布

    • 均匀的分布
      • 在设计哈希表时, 我们已经有办法处理映射到相同下标值的情况: 链地址法或者开放地址法.
      • 但是, 为了提供效率, 最好的情况还是让数据在哈希表中均匀分布.
      • 因此, 我们需要在使用常量的地方, 尽量使用质数.
      • 哪些地方我们会使用到常量呢?
    • 质数的使用:
      • 哈希表的长度.
      • N次幂的底数(我们之前使用的是27)
      • 下面我们简单来说一下为什么.
    • 哈希表的长度使用质数:
      • 这个在链地址法中事实上重要性不是特别明显, 明显的是在开放地址法中的再哈希法中.
      • 再哈希法中质数的重要性:
        • 假设表的容量不是质数, 例如: 表长为15(下标值0~14)
        • 有一个特定关键字映射到0, 步长为5. 探测序列是多少呢?
        • 0 - 5 - 10 - 0 - 5 - 10, 依次类推, 循环下去.
        • 算法只尝试着三个单元, 如果这三个单元已经有了数据, 那么会一直循环下去, 知道程序崩溃.
        • 如果容量是一个质数, 比如13. 探测序列是多少呢?
        • 0 - 5 - 10 - 2 - 7 - 12 - 4 - 9 - 1 - 6 - 11 - 3, 一直这样下去.
        • 不仅不会产生循环, 而且可以让数据在哈希表中更加均匀的分布.
      • 链地址法中质数没有那么重要, 甚至在Java中故意是2的N次幂
        • Java中的哈希表采用的是链地址法.
        • HashMap的初始长度是16, 每次自动扩展(我们还没有聊到扩展的话题), 长度必须是2的次幂.
        • 这是为了服务于从Key映射到index的算法.
        • HashMap中为了提高效率, 采用了位运算的方式.
          • HashMap中index的计算公式: index = HashCode(Key) & (Length - 1)
          • 比如计算book的hashcode,结果为十进制的3029737,二进制的101110001110101110 1001
          • 假定HashMap长度是默认的16,计算Length-1的结果为十进制的15,二进制的1111
          • 假定HashMap长度是默认的16,计算Length-1的结果为十进制的15,二进制的1111
          • 把以上两个结果做与运算,101110001110101110 1001 & 1111 = 1001,十进制是9,所以 index=9
          • 这样的方式相对于取模来说性能是高的, 因为计算机更运算计算二进制的数据.
        • 但是, 我个人发现JavaScript中进行较大数据的位运算时会出问题, 所以我的代码实现中还是使用了取模.
    • N次幂的底数, 使用质数:
      • 这里采用质数的原因是为了产生的数据不按照某种规律递增.
      • 比如我们这里有一组数据是按照4进行递增的: 0 4 8 12 16, 将其映射到成都为8的哈希表中.
      • 它们的位置是多少呢? 0 - 4 - 0 - 4, 依次类推.
      • 如果我们哈希表本身不是质数, 而我们递增的数量可以使用质数, 比如5, 那么 0 5 10 15 20
      • 它们的位置是多少呢? 0 - 5 - 2 - 7 - 4, 依次类推. 也可以尽量让数据均匀的分布.
      • 我们之前使用的是27, 这次可以使用一个接近的数, 比如31/37/41等等. 一个比较常用的数是37.

    哈希函数实现

    • 现在, 我们就给出哈希函数的实现:

      function hashFunc(str, max) {
          // 1.初始化hashCode的值
          var hashCode = 0
      
          // 2.霍纳算法, 来计算hashCode的数值
          for (var i = 0; i < str.length; i++) {
              hashCode = 37 * hashCode + str.charCodeAt(i)
          }
      
          // 3.取模运算
          hashCode = hashCode % max
          return hashCode
      }
      
    • 代码解析:

      • 理解了前面所有的内容, 其实代码就非常简单了.
      • 不再多做解释, 有不懂的可以留言或者查看前面的内容.
    • 代码测试:

      alert(hashFunc("abc", 7)) // 4
      alert(hashFunc("cba", 7)) // 3
      alert(hashFunc("nba", 7)) // 5
      alert(hashFunc("mba", 7)) // 1
      

    二. 哈希表

    经过前面那么多内容的学习, 我们现在可以真正实现自己的哈希表了.

    可能你学到这里的时候, 已经感觉到数据结构的一些复杂性, 但是如果你仔细品味, 你也会发现它在设计时候的巧妙和优美, 当你爱上它的那一刻, 你也真正爱上了编程.

    我们这里采用链地址法来实现哈希表:

    实现的哈希表(基于storage的数组)每个index对应的是一个数组(bucket).(当然基于链表也可以.)

    bucket中存放什么呢? 我们最好将key和value都放进去, 我们继续使用一个数组.(其实其他语言使用元组更好)

    最终我们的哈希表的数据格式是这样: [[ [k,v], [k,v], [k,v] ] , [ [k,v], [k,v] ], [ [k,v] ] ]

    创建哈希表

    • 我们像封装其他数据结构一样, 先来创建一个哈希表的类: HashTable

      // 创建HashTable构造函数
      function HashTable() {
          // 定义属性
          this.storage = []
          this.count = 0
          this.limit = 8
      
          // 定义相关方法
          // 哈希函数
          HashTable.prototype.hashFunc = function(str, max) {
              // 1.初始化hashCode的值
              var hashCode = 0
      
              // 2.霍纳算法, 来计算hashCode的数值
              for (var i = 0; i < str.length; i++) {
                  hashCode = 37 * hashCode + str.charCodeAt(i)
              }
            
              // 3.取模运算
              hashCode = hashCode % max
              return hashCode
          }
      }
      
    • 代码解析:

      • 我们定义了三个属性:
      • storage作为我们的数组, 数组中存放相关的元素.
      • count表示当前已经存在了多少数据.
      • limit用于标记数组中一共可以存放多少个元素.
      • 另外, 我们直接将哈希函数定义在了HashTable中.

    插入&修改数据

    • 现在, 我们来做向哈希表中插入数据

      // 插入数据方法
      HashTable.prototype.put = function (key, value) {
          // 1.获取key对应的index
          var index = this.hashFunc(key, this.limit)
      
          // 2.取出数组(也可以使用链表)
          var bucket = this.storage[index]
      
          // 3.判断这个数组是否存在
          if (bucket === undefined) {
              // 3.1创建桶
              bucket = []
              this.storage[index] = bucket
          }
          alert(bucket)
          
          // 4.判断是新增还是修改原来的值.
          var override = false
          for (var i = 0; i < bucket.length; i++) {
              var tuple = bucket[i]
              if (tuple[0] === key) {
                  tuple[1] = value
                  override = true
              }
          }
          
          // 5.如果是新增, 前一步没有覆盖
          if (!override) {
              bucket.push([key, value])
              this.count++
          }
      }
      
    • 代码解析:

      • 步骤1: 根据传入的key获取对应的hashCode, 也就是数组的index
      • 步骤2: 从哈希表的index位置中取出桶(另外一个数组)
      • 步骤3: 查看上一步的bucket是否为null
        • 为null, 表示之前在该位置没有放置过任何的内容, 那么就新建一个数组[]
      • 步骤4: 查看是否之前已经放置过key对应的value
        • 如果放置过, 那么就是依次替换操作, 而不是插入新的数据.
        • 我们使用一个变量override来记录是否是修改操作
      • 步骤5: 如果不是修改操作, 那么插入新的数据.
        • 在bucket中push新的[key, value]即可.
        • 注意: 这里需要将count+1, 因为数据增加了一项.

    获取数据

    • 有插入和修改数据, 就应该有根据key获取value

      // 获取存放的数据
      HashTable.prototype.get = function (key) {
          // 1.获取key对应的index
          var index = this.hashFunc(key, this.limit)
      
          // 2.获取对应的bucket
          var bucket = this.storage[index]
      
          // 3.如果bucket为null, 那么说明这个位置没有数据
          if (bucket == null) {
              return null
          }
      
          // 4.有bucket, 判断是否有对应的key
          for (var i = 0; i < bucket.length; i++) {
              var tuple = bucket[i]
              if (tuple[0] === key) {
                  return tuple[1]
              }
          }
          
          // 5.没有找到, return null
          return null
      }
      
    • 代码解析:

      • 步骤1: 根据key获取hashCode(也就是index)
      • 步骤2: 根据index取出bucket.
      • 步骤3: 因为如果bucket都是null, 那么说明这个位置之前并没有插入过数据.
      • 步骤4: 有了bucket, 就遍历, 并且如果找到, 就将对应的value返回即可.
      • 步骤5: 没有找到, 返回null

    删除数据

    • 我们根据对应的key, 删除对应的key/value

      // 删除数据
      HashTable.prototype.remove = function (key) {
          // 1.获取key对应的index
          var index = this.hashFunc(key, this.limit)
          
          // 2.获取对应的bucket
          var bucket = this.storage[index]
          
          // 3.判断同是否为null, 为null则说明没有对应的数据
          if (bucket == null) {
              return null
          }
          
          // 4.遍历bucket, 寻找对应的数据
          for (var i = 0; i < bucket.length; i++) {
              var tuple = bucket[i]
              if (tuple[0] === key) {
                  bucket.splice(i, 1)
                  this.count--
              }
              return tuple[1]
          }
          
          // 5.来到该位置, 说明没有对应的数据, 那么返回null
          return null
      }
      
    • 代码解析:

      • 代码思路和查询基本一致, 不再给出解析过程. 也可以查看注释.

    其他方法

    • 判断哈希表是否为空: isEmpty

      // isEmpty方法
      HashTable.prototype.isEmpty = function () {
          return this.count == 0
      }
      
    • 获取哈希表中数据的个数

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

    哈希表测试

    • 我们来简单测试一下上面的代码

      // 测试哈希表
      // 1.创建哈希表
      var ht = new HashTable()
      
      // 2.插入数据
      ht.put("abc", "123")
      ht.put("cba", "321")
      ht.put("nba", "521")
      ht.put("mba", "520")
      
      // 3.获取数据
      alert(ht.get("abc"))
      ht.put("abc", "111")
      alert(ht.get("abc"))
      
      // 4.删除数据
      alert(ht.remove("abc"))
      alert(ht.get("abc"))
      

    三. 哈希表扩容

    我们在来将讲一个哈希表的概念: 哈希表扩容.

    哈希表扩容的思想

    • 为什么需要扩容?
      • 目前, 我们是将所有的数据项放在长度为8的数组中的.
      • 因为我们使用的是链地址法, loadFactor可以大于1, 所以这个哈希表可以无限制的插入新数据.
      • 但是, 随着数据量的增多, 每一个index对应的bucket会越来越长, 也就造成效率的降低.
      • 所以, 在合适的情况对数组进行扩容. 比如扩容两倍.
    • 如何进行扩容?
      • 扩容可以简单的将容量增加大两倍(不是质数吗? 质数的问题后面再讨论)
      • 但是这种情况下, 所有的数据项一定要同时进行修改(重新哈希化, 来获取到不同的位置)
      • 比如hashCode=12的数据项, 在length=8的时候, index=4. 在长度为16的时候呢? index=12.
      • 这是一个耗时的过程, 但是如果数组需要扩容, 那么这个过程是必要的.
    • 什么情况下扩容呢?
      • 比较常见的情况是loadFactor>0.75的时候进行扩容.
      • 比如Java的哈希表就是在装填因子大于0.75的时候, 对哈希表进行扩容.

    哈希表扩容的实现

    • 我们来实现扩容函数

      // 哈希表扩容
      HashTable.prototype.resize = function (newLimit) {
          // 1.保存旧的数组内容
          var oldStorage = this.storage
      
          // 2.重置属性
          this.limit = newLimit
          this.count = 0
          this.storage = []
      
          // 3.遍历旧数组中的所有数据项, 并且重新插入到哈希表中
          oldStorage.forEach(function (bucket) {
              // 1.bucket为null, 说明这里面没有数据
              if (bucket == null) {
                  return
              }
      
              // 2.bucket中有数据, 那么将里面的数据重新哈希化插入
              for (var i = 0; i < bucket.length; i++) {
                  var tuple = bucket[i]
                  this.put(tuple[0], tuple[1])
              }
          }.bind(this))
      }
      
    • 代码解析:

      • 步骤1: 先将之前数组保存起来, 因为我们待会儿会将storeage = []
      • 步骤2: 之前的属性值需要重置.
      • 步骤3: 遍历所有的数据项, 重新插入到哈希表中.
    • 在什么时候调用扩容方法呢?

      • 在每次添加完新的数据时, 都进行判断. (也就是put方法中)
    • 修改put方法

      • 代码第5步中的内容
      // 插入数据方法
      HashTable.prototype.put = function (key, value) {
          // 1.获取key对应的index
          var index = this.hashFunc(key, this.limit)
      
          // 2.取出数组(也可以使用链表)
          // 数组中放置数据的方式: [[ [k,v], [k,v], [k,v] ] , [ [k,v], [k,v] ]  [ [k,v] ] ]
          var bucket = this.storage[index]
      
          // 3.判断这个数组是否存在
          if (bucket === undefined) {
              // 3.1创建桶
              bucket = []
              this.storage[index] = bucket
          }
      
          // 4.判断是新增还是修改原来的值.
          var override = false
          for (var i = 0; i < bucket.length; i++) {
              var tuple = bucket[i]
              if (tuple[0] === key) {
                  tuple[1] = value
                  override = true
              }
          }
      
          // 5.如果是新增, 前一步没有覆盖
          if (!override) {
              bucket.push([key, value])
              this.count++
              // 数组扩容
              if (this.count > this.limit * 0.75) {
                  this.resize(this.limit * 2)
              }
          }
      }
      
    • 如果我们不断的删除数据呢?

      • 如果不断的删除数据, 当loadFactor < 0.25的时候, 最好将数量限制在一半.
    • 修改remove方法

      • 代码第4步中的内容
      // 删除数据
      HashTable.prototype.remove = function (key) {
          // 1.获取key对应的index
          var index = this.hashFunc(key, this.limit)
      
          // 2.获取对应的bucket
          var bucket = this.storage[index]
      
          // 3.判断同是否为null, 为null则说明没有对应的数据
          if (bucket == null) {
              return null
          }
      
          // 4.遍历bucket, 寻找对应的数据
          for (var i = 0; i < bucket.length; i++) {
              var tuple = bucket[i]
              if (tuple[0] === key) {
                  bucket.splice(i, 1)
                  this.count--
                  
                  // 缩小数组的容量
                  if (this.limit > 8 && this.count < this.limit * 0.25) {
                      this.resize(Math.floor(this.limit / 2))
                  }
              }
              return tuple[1]
          }
      
          // 5.来到该位置, 说明没有对应的数据, 那么返回null
          return null
      }
      

    四. 容量质数

    我们前面提到过, 容量最好是质数.

    虽然在链地址法中将容量设置为质数, 没有在开放地址法中重要, 但是其实链地址法中质数作为容量也更利于数据的均匀分布. 所以, 我们还是完成一下这个步骤.

    判断质数

    • 我们这里先讨论一个常见的面试题, 判断一个数是质数.

    • 质数的特点:

      • 质数也称为素数.
      • 质数表示大于1的自然数中, 只能被1和自己整除的数.
    • OK, 了解了这个特点, 应该不难写出它的算法:

      function isPrime(num) {
          for (var i = 2; i < num; i++) {
              if (num % i == 0) {
                  return false
              }
          }
          return true
      }
      
      // 测试
      alert(isPrime(3)) // true
      alert(isPrime(32)) // false
      alert(isPrime(37)) // true
      
    • 但是, 这种做法的效率并不高. 为什么呢?

      • 对于每个数n,其实并不需要从2判断到n-1
      • 一个数若可以进行因数分解,那么分解时得到的两个数一定是一个小于等于sqrt(n),一个大于等于sqrt(n).
      • 比如16可以被分别. 那么是2*8, 2小于sqrt(16), 也就是4, 8大于4. 而4*4都是等于sqrt(n)
      • 所以其实我们遍历到等于sqrt(n)即可
      function isPrime(num) {
          // 1.获取平方根
          var temp = parseInt(Math.sqrt(num))
      
          // 2.循环判断
          for (var i = 2; i <= temp; i++) {
              if (num % i == 0) {
                  return false
              }
          }
          return true
      }
      

    扩容的质数

    • 首先, 将初始的limit为8, 改成7

    • 前面, 我们有对容量进行扩展, 方式是: 原来的容量 x 2

      • 比如之前的容量是7, 那么扩容后就是14. 14还是一个质数吗?
      • 显然不是, 所以我们还需要一个方法, 来实现一个新的容量为质数的算法.
    • 那么我们可以封装获取新的容量的代码(质数)

      // 判断是否是质数
      HashTable.prototype.isPrime = function (num) {
          var temp = parseInt(Math.sqrt(num))
          // 2.循环判断
          for (var i = 2; i <= temp; i++) {
              if (num % i == 0) {
                  return false
              }
          }
          return true
      }
      
      // 获取质数
      HashTable.prototype.getPrime = function (num) {
          while (!isPrime(num)) {
              num++
          }
          return num
      }
      
    • 修改插入和删除的代码:

    • 插入数据的代码:

      // 扩容数组的数量
      if (this.count > this.limit * 0.75) {
          var primeNum = this.getPrime(this.limit * 2)
          this.resize(primeNum)
      }
      
    • 删除数据的代码:

      // 缩小数组的容量
      if (this.limit > 7 && this.count < this.limit * 0.25) {
          var primeNum = this.getPrime(Math.floor(this.limit / 2))
          this.resize(primeNum)
      }
      

    五. 完整代码

    • 最后, 还是给出实现哈希表的完整代码:

      // 创建HashTable构造函数
      function HashTable() {
          // 定义属性
          this.storage = []
          this.count = 0
          this.limit = 8
      
          // 定义相关方法
          // 判断是否是质数
          HashTable.prototype.isPrime = function (num) {
              var temp = parseInt(Math.sqrt(num))
              // 2.循环判断
              for (var i = 2; i <= temp; i++) {
                  if (num % i == 0) {
                      return false
                  }
              }
              return true
          }
      
          // 获取质数
          HashTable.prototype.getPrime = function (num) {
              while (!isPrime(num)) {
                  num++
              }
              return num
          }
      
          // 哈希函数
          HashTable.prototype.hashFunc = function(str, max) {
              // 1.初始化hashCode的值
              var hashCode = 0
      
              // 2.霍纳算法, 来计算hashCode的数值
              for (var i = 0; i < str.length; i++) {
                  hashCode = 37 * hashCode + str.charCodeAt(i)
              }
      
              // 3.取模运算
              hashCode = hashCode % max
              return hashCode
          }
      
          // 插入数据方法
          HashTable.prototype.put = function (key, value) {
              // 1.获取key对应的index
              var index = this.hashFunc(key, this.limit)
      
              // 2.取出数组(也可以使用链表)
              // 数组中放置数据的方式: [[ [k,v], [k,v], [k,v] ] , [ [k,v], [k,v] ]  [ [k,v] ] ]
              var bucket = this.storage[index]
      
              // 3.判断这个数组是否存在
              if (bucket === undefined) {
                  // 3.1创建桶
                  bucket = []
                  this.storage[index] = bucket
              }
      
              // 4.判断是新增还是修改原来的值.
              var override = false
              for (var i = 0; i < bucket.length; i++) {
                  var tuple = bucket[i]
                  if (tuple[0] === key) {
                      tuple[1] = value
                      override = true
                  }
              }
      
              // 5.如果是新增, 前一步没有覆盖
              if (!override) {
                  bucket.push([key, value])
                  this.count++
      
                  if (this.count > this.limit * 0.75) {
                      var primeNum = this.getPrime(this.limit * 2)
                      this.resize(primeNum)
                  }
              }
          }
      
          // 获取存放的数据
          HashTable.prototype.get = function (key) {
              // 1.获取key对应的index
              var index = this.hashFunc(key, this.limit)
      
              // 2.获取对应的bucket
              var bucket = this.storage[index]
      
              // 3.如果bucket为null, 那么说明这个位置没有数据
              if (bucket == null) {
                  return null
              }
      
              // 4.有bucket, 判断是否有对应的key
              for (var i = 0; i < bucket.length; i++) {
                  var tuple = bucket[i]
                  if (tuple[0] === key) {
                      return tuple[1]
                  }
              }
      
              // 5.没有找到, return null
              return null
          }
      
          // 删除数据
          HashTable.prototype.remove = function (key) {
              // 1.获取key对应的index
              var index = this.hashFunc(key, this.limit)
      
              // 2.获取对应的bucket
              var bucket = this.storage[index]
      
              // 3.判断同是否为null, 为null则说明没有对应的数据
              if (bucket == null) {
                  return null
              }
      
              // 4.遍历bucket, 寻找对应的数据
              for (var i = 0; i < bucket.length; i++) {
                  var tuple = bucket[i]
                  if (tuple[0] === key) {
                      bucket.splice(i, 1)
                      this.count--
      
                      // 缩小数组的容量
                      if (this.limit > 7 && this.count < this.limit * 0.25) {
                          var primeNum = this.getPrime(Math.floor(this.limit / 2))
                          this.resize(primeNum)
                      }
                  }
                  return tuple[1]
              }
      
              // 5.来到该位置, 说明没有对应的数据, 那么返回null
              return null
          }
      
          // isEmpty方法
          HashTable.prototype.isEmpty = function () {
              return this.count == 0
          }
      
          // size方法
          HashTable.prototype.size = function () {
              return this.count
          }
      
          // 哈希表扩容
          HashTable.prototype.resize = function (newLimit) {
              // 1.保存旧的数组内容
              var oldStorage = this.storage
      
              // 2.重置属性
              this.limit = newLimit
              this.count = 0
              this.storage = []
      
              // 3.遍历旧数组中的所有数据项, 并且重新插入到哈希表中
              oldStorage.forEach(function (bucket) {
                  // 1.bucket为null, 说明这里面没有数据
                  if (bucket == null) {
                      return
                  }
      
                  // 2.bucket中有数据, 那么将里面的数据重新哈希化插入
                  for (var i = 0; i < bucket.length; i++) {
                      var tuple = bucket[i]
                      this.put(tuple[0], tuple[1])
                  }
              }).bind(this)
          }
      }
      

    相关文章

      网友评论

      • boxshadow:bind的绑定位置错了,应该绑定到匿名函数而不是forEach上
        coderwhy:@boxshadow 感谢指正, 已经修复.
      • ptlCoder:/**
        再哈希法中质数的重要性:
        假设表的容量不是质数, 例如: 表长为15(下标值0~14)
        有一个特定关键字映射到0, 步长为5. 探测序列是多少呢?
        0 - 5 - 10 - 0 - 5 - 10, 依次类推, 循环下去.
        算法只尝试着三个单元, 如果这三个单元已经有了数据, 那么会一直循环下去, 知道程序崩溃.
        如果容量是一个质数, 比如13. 探测序列是多少呢?
        0 - 5 - 10 - 2 - 7 - 12 - 4 - 9 - 1 - 6 - 11 - 3, 一直这样下去.
        */
        王老师, 问一个初学者的问题:
        长度为15时,第一次 0-5-10完了之后,为什么后面不从其他数开始而还是 0-5-10一直循环?:smiley:
        长度为13时0-5-10过后,可不可以1-6-11勒?
        :pray:
        coderwhy:@ptlCoder 因为步长是固定的,10加5是15
        并没有15,取余后就是0
        下一个步长又是5,下一个又是10
      • 咬尾巴的妖精猫:早上刷微博没看到你的动态
        coderwhy:@咬尾巴的妖精猫 有的吧,今天早上有打卡啊:smile:

      本文标题:数据结构(十)之哈希表实现

      本文链接:https://www.haomeiwen.com/subject/onyucftx.html