设计一个重新散列函数......如何避免相同的散列?

Posted

技术标签:

【中文标题】设计一个重新散列函数......如何避免相同的散列?【英文标题】: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 也解释的那样),它实际上不起作用。您应该使用有效的冲突解决策略,一个好的开始是单独的链接或开放寻址。有一些哈希表使用多个哈希函数,但研究人员花了很长时间才让它们正常工作,如果你在他们的工作基础上构建,你会知道它的名字(例如布谷鸟哈希)。所以不要这样尝试。从小处着手。 【参考方案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-hashing、2-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 可以是一些简单的事情,比如将一个相对于表大小相对质数的数字添加到索引中(开放寻址)。向哈希表添加键时,可能需要多次重新哈希才能在哈希表中找到可用索引。

【讨论】:

以上是关于设计一个重新散列函数......如何避免相同的散列?的主要内容,如果未能解决你的问题,请参考以下文章

HashMap中的散列函数冲突解决机制和rehash

散列表

信息摘要

HBase Rowkey的散列与预分区设计

为啥这个特定的函数是一个糟糕的散列函数?

如何将扫描的图像缩小为一致的散列?