【问题标题】:Designing a rehash function... how to avoid the same hash?设计一个重新散列函数......如何避免相同的散列?
【发布时间】:2014-03-17 21:28:54
【问题描述】:

所以我正在开发一个使用哈希表的程序。事情是这样的:

1) 将文本文件读入“符号”对象的向量(包含名称和数字) 2) 散列 Symbol 对象的名称。 3) 将此对象插入到哈希表中。

到目前为止,我一直将唯一生成的哈希键存储为整数数组。然后我循环遍历数组,看看是否有重复。

如果有,我知道有碰撞。然而,这种方法已被证明是成功的,现在我必须编写一个 rehash() 函数,这样我才能获得一个不会导致冲突的新密钥。但我无法弄清楚如何做到这一点。

我已经包含了我的循环,我在其中检查键数组和我当前的哈希函数以及我的输出。任何关于去哪里的建议将不胜感激。

for (int i=0; i < TABLE_SIZE; i++)
        {
              if (i != j)
              {
                if (array[i] == array[j])
                {
                    cout << endl;
                      cout << "Collision occurred at" << array[i] << endl;
                    cout << "Now rehashing..." << endl;
                    // REHASH FUNCTION SHOULD GO HERE --> rec.key = rehash(data);
                    cout << "The new key is: " << rec.key << endl;
                    break;
                }
              }
        }

        dataTable.insert(rec); //inserts the record object into the HashTable.

现有哈希函数:

int hasher (string data)
  // POST: the index of entry is returned
  {       int sum = 0;
          for (int k = 0; k < data.length(); k++)
              sum = sum + int(data[k]);
          return  sum % TABLE_SIZE;
  }

我得到的输出:

计数 2 这个 Symbol 对象的键是:7

  num
  2
  The key for this Symbol object is: 0


  Collision occurred at0
  Now rehashing...
  The new key is: 1
  myFloat
  4
  The key for this Symbol object is: 18

  myDouble
  5
  The key for this Symbol object is: 14

  name
  6
  The key for this Symbol object is: 18


  Collision occurred at18
  Now rehashing...
  The new key is: 1
  address
  6
  The key for this Symbol object is: 7


  Collision occurred at7
  Now rehashing...
  The new key is: 1
  salary
  5
  The key for this Symbol object is: 1

  gpa
  4
  The key for this Symbol object is: 18


  Collision occurred at18
  Now rehashing...
  The new key is: 1
  gdp
  5
  The key for this Symbol object is: 0


  Collision occurred at0
  Now rehashing...
  The new key is: 1
  pi
  5
  The key for this Symbol object is: 7


  Collision occurred at7
  Now rehashing...
  The new key is: 1
  city
  6
  The key for this Symbol object is: 0


  Collision occurred at0
  Now rehashing...
  The new key is: 1
  state
  6
  The key for this Symbol object is: 20

  county
  6
  The key for this Symbol object is: 2

  ch
  0
  The key for this Symbol object is: 14


  Collision occurred at14
  Now rehashing...
  The new key is: 1
  ch2
  0
  The key for this Symbol object is: 1


  Collision occurred at1
  Now rehashing...
  The new key is: 1
  ID
  1
  The key for this Symbol object is: 15

  studentID
  1
  The key for this Symbol object is: 13

  max
  3
  The key for this Symbol object is: 11

  max2
  3
  The key for this Symbol object is: 19

  greeting
  6
  The key for this Symbol object is: 13


  Collision occurred at13
  Now rehashing...
  The new key is: 1
  debt
  5

如您所见,当发生碰撞时,它会成功检测到它。现在我只需要一种重新哈希密钥的方法,这样它就不会在将来发生......因为现在重新哈希也是一个冲突。

【问题讨论】:

  • 你在实现哈希表吗?然后典型的设计是使用一个散列函数,接受会有冲突(注意几个散列函数只能勉强降低冲突的几率),并使用开放寻址或单独链接解决冲突。为什么你认为你需要另一个哈希函数?
  • 您实际上想要完成什么?你不只是使用 std::hash_map/std::unordered_map 有什么原因吗?
  • @delnan 是的,我正在植入一个哈希表,并且我通过单独的链接这样做并且我已经成功完成了。但是当发生冲突时,我需要重新散列字符串并重新插入表中。这是我的决心部分。
  • 重新散列仍然不能保证您不会再次发生冲突。保证这一点的唯一方法是知道在编译时可能必须散列的所有键,并选择一个散列函数,将每个键映射到不同的散列桶。对于直到运行时才知道的任意字符串(或者实际上大多数数据,其中往往变化的位数大于哈希函数结果类型中的数字),这是不切实际的,您必须处理碰撞。
  • @user2704533 如果这是您的冲突解决策略,那么您没有使用单独的链接。实际上,它甚至不是一个合适的冲突解决策略,因为(正如 Tony D 也解释的那样),它实际上不起作用。您应该使用有效的冲突解决策略,一个好的开始是单独的链接或开放寻址。有一些哈希表使用多个哈希函数,但研究人员花了很长时间才让它们正常工作,如果你在他们的工作基础上构建,你会知道它的名字(例如布谷鸟哈希)。所以不要这样尝试。从小处着手。

标签: c++ hashtable


【解决方案1】:

解决哈希冲突的简单方法是separate chaining。基本上你的数据结构有一堆链表,所以当发生冲突时,你只需将结果附加到这里发生冲突的其他值。还有其他方法具有更有效的插入/查找时间,但显然,它们需要更多的努力来实现。

【讨论】:

    【解决方案2】:

    假设你的数组是:

    #define N ...
    struct element table[N];
    

    然后您可以定义 两个 散列函数(独立!),假设int h(data); int g(data); 其中0 &lt;= h &lt; N1 &lt;= g &lt; N。确保g 返回的值与N 互质。然后,要插入一个新元素,您可以:

    int i = h(data);
    if(table[i] is free)
         /* Go ahead! */
    else {
         /* Was occupied, try alternatives */
         int j = g(data);
         for(k = i + j; k != i; k = (k + j) % N)
             if(table[k] is free) {
                 /* Found a free space, go ahead */
                 break;
             }
         if(k == i) {
             /* Table is full */
         }
    }
    

    搜索类似。

    获得g 的值始终与N 互质的最简单方法是只取1。稍微困难的是确保N 是质数,并且g 始终小于N .

    使用g == 1 会使数据聚集在一起,从而减慢搜索速度; g 的不同值可以避免这种情况。

    【讨论】:

    • 这似乎在功能上等同于开放寻址。
    【解决方案3】:

    当您查看实现没有冲突的哈希函数时,您需要进行多年的研究。总而言之,您正在尝试实现完美的散列。

    您实施的方法称为通过链接解决冲突。 这个过程的缺点是,每当发生冲突时,搜索需要 O(n) 的最坏情况时间,其中 n 是子链中元素的数量

    选择哈希函数

    以下是选择has函数的方法

    1) 划分方法
    2) 乘法
    3) 通过开放寻址解决冲突
    4) 探测策略
    5) 通用哈希。 (碰撞概率无限低)
    6) 完美哈希。 (这仅在某些情况下是可能的)。

    请通过
    http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-046j-introduction-to-algorithms-sma-5503-fall-2005/video-lectures/
    阅读第 7 讲和第 8 讲 为了更深入地了解上述方法,如果您有兴趣编写自己的通用哈希函数,我认为您从讲座中获得的知识将是一个很好的起点。

    一切顺利

    【讨论】:

      【解决方案4】:

      所以我正在开发一个使用哈希表的程序。事情是这样的:

      1) 将文本文件读入“符号”对象的向量中(包含名称和数字) 2) 散列符号对象的名称。 3) 将此对象插入到哈希表中。

      到目前为止,我一直将唯一生成的哈希键存储为整数数组。然后我循环遍历数组,看看是否有重复。

      如果有,我知道有碰撞。这种方法已被证明是成功的,但是现在我必须编写一个 rehash() 函数,这样我才能得到一个不会导致冲突的新密钥。

      我不确定您是在尝试实现 NIST 所称的 double-hashing2-choice hashing 还是 cuckoo hash。我认为您说的是双重哈希。

      如果您知道所有可能的输入,您可以选择两个具有您正在寻找的属性的哈希值。当然,如果您知道所有可能的输入,您可以选择一个没有任何冲突的哈希。

      如果您不关心性能,您可以选择两种不同的加密安全哈希函数,例如 SHA3 和Skein。但是,我非常怀疑你会想要那个。当博客讨论the meet-in-the-middle vulnerability in a widely-used hash function 时,我认为最简单的解决方案是使用一个函数——例如加密安全的哈希函数——设计为抗碰撞。我所知道的最简单的哈希函数TEA 比它要替换的函数慢十倍以上。请注意:TEA 对其他任何东西都不安全,而且由于它对于哈希映射来说是一个糟糕的选择,因此很难找到它的任何用途。

      您可以简单地选择两个狂野的different hash functions 并希望最好。

      SipHash 是由密码学家设计的,即使它在密码学上并不安全,也可以防碰撞。但是,not everyone's happy with its performance(“动态语言的哈希函数”部分)。此外,我不熟悉任何关于 SipHash 竞争的类似分析,所以虽然我很乐意将 SipHash 指向你,但我不愿意推荐第二个哈希。

      我更愿意推荐使用不同的方法来处理碰撞。你想做的事情并没有错,但它是不走寻常路的,而且很难找到好的建议。

      所以,我的建议(按优先顺序)是:

      • 使用std::unordered_map。不幸的是,虽然您可以替换 std::unordered_map 使用的散列函数,但这很痛苦。
      • 使用单个哈希函数,并通过单独的链接处理冲突(使用 std::vector(对 CPU 缓存很好,添加/删除元素更麻烦)或 std::list 用于您的列表)。
      • 使用单个哈希函数,并处理与 linear probing 的冲突,这至少可以很好地与 CPU 的缓存配合使用。
      • 如果所有其他方法都失败了,请使用双重哈希,为第一个哈希选择一个您知道容易发生冲突的快速哈希算法,并使用 SipHash 来处理冲突。如果冲突很少,您将拥有一个快速的哈希映射,如果有很多冲突,则依靠 SipHash 来处理它们是合理的。如果你对冲突有疑虑,你应该假设每次插入或检索都会调用这两个哈希函数。您必须决定这是否可以接受。

      这种方法已被证明是成功的,但是现在我必须编写一个 rehash() 函数,这样我才能获得一个不会导致冲突的新密钥。但我不知道该怎么做。

      我已经包含了我的循环,我在其中检查键数组和我当前的哈希函数以及我的输出。任何关于去哪里的建议将不胜感激。

      简而言之:

      • 插入时,检查槽是否为空:
        • 如果槽是空的,把物品放在那里(你就完成了)
        • 如果槽里已经有东西,调用rehash()方法在原键上,如果rehash()建议的槽是空的,把物品放在那里;否则,请致电rehash(rehash(key)),并继续这样做,直到找到一个空槽(至少,这就是我理解 NIST 页面关于双重哈希的方式)
      • 检索时,您必须先找到一个空槽,然后才能说某个项目不在地图中;例如,如果hash(key) 返回一个空槽,那么你就完成了;如果hash(hey) 返回一个包含您要查找的项目的插槽,那么您就完成了;它hash(key) 返回一个包含不同项目的插槽,然后您必须调用 rehash(key)rehash(rehash(key)) 等,直到找到您要查找的内容或找到一个空插槽
      • 从哈希映射中删除项目时,您可能希望使用墓碑说“我删除了这里的内容,但您需要继续调用 rehash() 以查看您要查找的元素是否是在地图中”
      • 当增加哈希值时,你基本上是创建一个更大的容器,并一次插入一个元素

      如果这看起来令人生畏,请重新考虑 std::unordered_map 是否符合要求。


      我现在可以推荐一个除 SipHash 之外的抗冲突哈希(或者,事实上,several)。

      【讨论】:

        【解决方案5】:

        rehash 可以是一些简单的事情,比如将一个相对于表大小相对质数的数字添加到索引中(开放寻址)。向哈希表添加键时,可能需要多次重新哈希才能在哈希表中找到可用索引。

        【讨论】:

          猜你喜欢
          • 2011-07-03
          • 2016-11-09
          • 1970-01-01
          • 2017-07-21
          • 1970-01-01
          • 1970-01-01
          • 2017-10-13
          • 2014-07-27
          • 1970-01-01
          相关资源
          最近更新 更多