我想实现一个hashmap,但我不允许它扩展。因为我知道我需要存储大多数N
个元素,所以我可以为哈希表的每个桶预先分配一个带有N
元素的数组,这样我仍然可以存储N
在最坏的情况下,所有密钥都在同一个桶上进行散列的元素。但是我需要存储的元素相当大,所以对于大N
来说,这是非常低效的内存使用。
是否可以使用固定数量的内存实现具有效率(就内存而言)的散列图,例如:通过实现智能散列函数?
(PS:密钥是一个无符号的32位整数,除了我将收到的键值在该范围的一个相当小的子集中,我之前没有关于键的知识,并且这个子集移动得非常缓慢在范围内。)
我现在有一个实现,其中我有两个长度为N
的数组,一个包含元素,另一个包含与两个数组中位置i
的元素对应的键。我使用模运算作为哈希函数来确定元素应该插入/存在的位置,并使用线性探针来查找碰撞时最近的空白点。我认为这是复杂的O(N),我认为这对于我期望的数据量来说会相当快。我问了这个问题,看看能否做得更好。
答案 0 :(得分:3)
对于散列,您可以使用以下代码段,Linux内核使用它来散列PID:
unsigned long hash_long(unsigned long val, unsigned int bits)
{
unsigned long hash = val * 0x9e370001UL;
return hash >> (32 - bits);
}
幻数0x9e370001UL
是一个大素数。以下是了解Linux内核的摘录,解释了幻数:
您可能想知道0x9e370001常量(= 2,654,404,609)的来源 从。此哈希函数基于索引的乘法 一个合适的大数,使结果溢出和值 保留在32位变量中可以认为是a的结果 模数运算。 Knuth认为可以获得良好的结果 当大乘数是一个近似黄金比例的素数时 232(32位是80×86寄存器的大小)。现在, 2,654,404,609是靠近的一个素数,也可以很容易地成倍增加 通过加法和位移,因为它等于2 ^ 31 + 2 ^ 29 - 2 ^ 25 + 2 ^ 22 - 2 ^ 19 - 2 ^ 16 + 1。
右移hash >> (32 - bits);
只是说哈希值中的位位数。其他位将被清零。在您的情况下,位将由限制N
确定。为了使其按原样工作,N
需要使其在其最重要的设置位之后的所有位都被设置,例如,对于N = 7
(其中最后三位都已设置且所有其他位均为零)且位将为3.或N = 63
其中最低有效6位全部设置且所有其他位都为零。这里位将为6。
hash_long
函数返回的值将在您的数组中形成索引。
处理冲突
要处理冲突,只保留一个数组,但要使其成为链表节点数组。因此,数组中的每个元素都指向一个链表。发生冲突时,只需将新条目附加到与阵列中该插槽对应的链接列表的末尾。
处理冲突(更新)
如果你不能动态分配新的内存那么你发布的解决方案似乎没问题,虽然我不确定只包含键的数组的目的是什么(键不应该是它所属的元素的成员? )。以下是对您的解决方案的建议:
拥有1-D数组意味着在发生碰撞时,我们在插入和检索时都执行线性探测。另一种方法是使用二维数组,其中内部数组充当链表。我们需要在每个内部数组中插入最后一个元素的索引。与1-D数组相比较的缺点是,如果在同一个索引上发生太多碰撞,那么我们可能会在其中一个内部数组中耗尽空间,除非我们使每个内部数组长度为N,这将导致浪费了很多空间。优点是插入时,我们不需要执行线性探测。我们只检查内部数组中最后一个元素的索引并将其递增1以获得插入新元素的下一个插槽。