【问题标题】:Understanding of hash tables and collision detection了解哈希表和冲突检测
【发布时间】:2015-11-24 19:26:03
【问题描述】:

下面是我使用“桶”进行冲突检测的哈希表实现。我试图确保我能够完全理解哈希表背后的逻辑并将其可视化。这就是我的哈希表的样子:

[[[]

元组中的键值对根据散列函数的输出放置在桶中,,桶索引。一旦您位于散列存储桶索引处,您就可以将键值对放在存储桶内。如果有任何内容与该确切密钥匹配(一旦在存储桶内),它将在我的实现中被覆盖。当您找到与以前相同的键时会发生碰撞检测,并覆盖其值。

我可以做一些不同的事情吗?也许将具有不同值的键添加到元组的末尾(而不是覆盖值)或者键必须始终是唯一的?是否存在只需要唯一值的情况?

    var makeHashTable = function() {



     var max = 4;

      return {
        _storage: [],
    retrieve: function(key) {
      //when we retrieve, we want to access the bucket in the same way as insert, but we don't need to set it to storage since that is already taken
      //care of in the insert function.  if there's nothing in the bucket, the loop won't run.
      //this function will return null by default.
      var bucketIndex = hashFn(key, max);
      var bucket = this._storage[bucketIndex];

      for (var i = 0; i < bucket.length; i++) {
        var tuple = bucket[i];
        if (tuple[0] === key) {
          return tuple[1];
        };
      };
      return null;
    },

    insert: function(key, value) {
      //hash function gives you the right index
      var bucketIndex = hashFn(key, max)
        //where you need to put the bucket. if there's no bucket, initialize it.
      var bucket = this._storage[bucketIndex] || [];
      //now you need to actually store the bucket there. 
      this._storage[bucketIndex] = bucket;
      //implement a collission detection scheme whereby you overwrite the respective value if the key matches, otherwise, add it to the end. 
      // the for loop won't execute if there is nothing in the bucket if so, jump to line 45 instead
      // here is what's happening. If the key doesn't already exist, the key value pair gets added to the end of the bucket.
      // if the key matches, IT MUST BE THE SAME VALUE that the hashed key associated with that value previously, so, overwrite it. 
      for (var i = 0; i < bucket.length; i++) {
        var tuple = bucket[i];
        if (tuple[0] === key) {
          tuple[1] = value;
          return;
        };
      };



      bucket.push([key, value]);
    }

  };
};


HashTable.prototype.remove = function(key) {
  var bucketIndex = hashFn(key, max);
  var bucket = this._storage[bucketIndex];

  for (var i = 0; i < bucket.length; i++) {
    var tuple = bucket[i];
    if (tuple[0] === k) {
      bucket.splice(i, 1);
    };
  };

};

//don't worry about this generic hashing function please, not the point of my question
var hashFn = function(str, max) {
  var hash = 0;
  for (var i = 0; i < str.length; i++) {
    var letter = str[i];
    hash = (hash << 5) + letter.charCodeAt(0);
    hash = (hash & hash) % max;
  }
  return hash;
};

【问题讨论】:

  • 与您的问题相切,但 FWIW 最新(全新)版本的 JavaScript 内置了 Mapdeveloper.mozilla.org/en/docs/Web/JavaScript/Reference/… 以上看起来主要是学习的东西,所以它可能不是相关的,但如果是这样的话,我应该提到它。有用于 ES6 之前的浏览器的 shims(现在是大多数浏览器)。

标签: javascript algorithm data-structures hash


【解决方案1】:

哈希表中的冲突通常通过让每个键实际表示一个数组(或任何最能满足您需求的数据结构)来处理。这样,当您有两个具有相同键的值时,您只需将其推送到与键对应的数组中,然后您只需搜索该数组中的元素即可。这通常不是问题,因为它仍然比搜索整个哈希表要好得多。

如果数组中只有一个元素,仍然需要恒定的时间才能找到该元素。

【讨论】:

    【解决方案2】:

    您的哈希表实现是正确的。我应该指出,您在问题中描述的不是碰撞检测,而是使用新值更新键的操作。冲突是两个不同的键映射到同一个存储桶时,而不是当您插入一个键并发现存在具有相同键的先前条目时。您已经通过同一存储桶中的 chaining 条目处理了冲突。

    无论如何,您已经正确地更新了条目。假设您已将 (key, value) 对 ('a', 'ant') 插入到哈希表中。这意味着“a”映射到“ant”。如果插入 ('a', 'aardvark'),目的是覆盖 'a' 条目,以便它现在映射到 'aardvark'。因此,您遍历条目链并检查存储桶中的键“a”。你找到了它,所以你将值 'ant' 替换为 'aardvark'。现在'a'映射到'aardvark'。很好。

    假设您没有遍历条目链。如果你盲目地将 ('a', 'aardvark') 附加到链的末尾会发生什么?结果是,当您查找键 'a' 并遍历存储桶中的条目时,您首先遇到 ('a', 'ant'),因此返回 'ant'。这是一个不正确的结果。您最近插入了 ('a', 'aardvark'),因此您应该返回了 'aardvark'。

    啊,但是如果你总是从末端开始搜索呢?换句话说,您将其视为堆栈。要插入条目,请将其推到链的末端。要查找密钥,您从末尾开始搜索。具有给定键的第一个条目是最近插入的条目,因此它是正确的,您无需进一步搜索即可返回该值。

    该实现是正确的,但它也会使链变得比必要的更长。考虑一下如果您使用哈希表来计算文本文件中的字母频率会发生什么。最初,您在表中插入 ('a', 0)。当您在文本中找到第一次出现“a”时,您从表中读取 0,将其加 1,然后将 ('a', 1) 插入到哈希表中。现在您在链中有两个条目,键为“a”,只有更接近末尾的条目有效。当您找到下一个出现的“a”时,将第三个条目添加到链中,依此类推。具有相同键的数千次插入会导致链中的数千个条目。

    这不仅会占用内存,还会减慢其他键的插入速度。例如,假设您的哈希函数将相同的索引分配给键“a”和“q”。这意味着“q”条目与“a”条目在同一个桶中。如果您在链的末尾有一大堆“a”条目,那么在找到带有“q”的最新条目之前,您可能必须经过其中的许多条目。出于这些原因,最好按照您的做法去做。

    再想一想:如果每个条目都是一个元组 (key, values),而 values 是一个值数组,该怎么办?然后,按照您的建议,您可以在 values 的末尾附加一个新值,以防发生键冲突。但是如果你这样做,values的含义是什么?它包含使用该键插入的值,按插入时间的顺序排列。如果将其视为堆栈并始终返回列表中的最后一个值,那么您就是在浪费空间。您也可以覆盖单个值。

    有没有这样一种情况,您可以将新值放入存储桶而不检查现有键?是的,如果你有一个perfect hash function,你可以这样做,这保证没有冲突。每个键都映射到不同的存储桶。现在您不需要条目链。每个存储桶中最多有一个值,因此您可以将哈希表实现为一个数组,该数组在每个索引处包含undefined 或该索引处最近插入的值。这听起来不错,但想出一个完美的哈希函数并不容易,特别是如果您希望哈希表不包含不必要的存储桶。您必须提前知道可能使用的每个可能的键,以便设计一个哈希函数,将n 可能的键映射到n 不同的桶。

    【讨论】:

    • 谢谢,这很有帮助。所以在我的插入函数示例中,我得到了一个键值对。键被散列到bucketIndex,键值对覆盖匹配键的值。如果桶是空的(因此没有匹配的键),键值对被添加到桶的末尾。所以基本上冲突是当哈希函数输出相同的bucketIndex时?所以在我的代码中,数组末尾的 .push() 是实际的碰撞(最后拼写正确!)分辨率?
    • 没错。准确地说,冲突是哈希函数为两个不同的键输出相同的索引。可以把它想象成两个不同的键在同一个索引处发生碰撞,就像两辆车被分配到同一个停车位。
    • 至于第二个问题,你在两种情况下将一个条目推到链的末端。一种情况是当您使用以前从未见过的键插入条目时。另一种情况是键冲突:您正在插入一个键,该键的键与之前看到的键相同。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多