【问题标题】:How can I implement a fixed size hashmap?如何实现固定大小的哈希图?
【发布时间】:2014-06-13 11:05:01
【问题描述】:

我想实现一个hashmap,但我不能让它扩展。因为我知道我最多需要存储 N 元素,所以我可以为我的哈希表的每个桶预先分配一个包含 N 元素的数组,这样我仍然可以在最坏的情况下存储 N 元素所有键都在同一个桶上散列。但是我需要存储的元素相当大,所以对于大的N,这是非常低效的内存使用。

是否可以使用固定的内存量有效地(在内存方面)实现 hashmap,例如通过实现智能哈希函数?

(P.S.: 键是一个无符号的 32 位整数,我对键没有任何先验知识,除了我将收到的键值在范围的一个相当小的子集中,这个子集向上移动非常缓慢范围内。)


我现在有一个实现,其中我有两个长度为N 的数组,一个包含元素,一个包含与两个数组中i 位置的元素对应的键。我使用模运算作为散列函数来确定元素应该插入/存在的位置,并使用线性探针在发生碰撞时找到最近的空点。我认为这具有 O(N) 的复杂性,并且我认为这对于我期望的数据量来说会相当快。我问了这个问题,看看它是否可以做得更好。

【问题讨论】:

    标签: c hash hashmap


    【解决方案1】:

    对于散列,您可以使用以下 sn-p,顺便说一句,Linux 内核使用它来散列 PID:

    unsigned long hash_long(unsigned long val, unsigned int bits)
    {
    unsigned long hash = val * 0x9e370001UL;
    return hash >> (32 - bits);
    }
    

    幻数0x9e370001UL 是一个大素数。以下是 Understanding Linux Kernel 的摘录,解释了这个神奇的数字:

    您可能想知道 0x9e370001 常量 (= 2,654,404,609) 的来源 从。此哈希函数基于索引乘以 一个合适的大数,使结果溢出,值 剩余在 32 位变量中可以被认为是一个结果 模运算。 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); 只是说在哈希值中保留bits 位数。其他位将被清零。在您的情况下,bits 将由限制 N 确定。要使其按原样工作,N 需要使其最高有效设置位之后的所有位也被设置,例如对于N = 7(其中最后三位均已设置,所有其他位为零),bits 将为 3。或N = 63,其中最低有效六位均已设置,所有其他位均已设置零。这里 bits 将是 6。

    hash_long 函数返回的值将形成数组的索引。

    处理碰撞

    要处理冲突,只保留一个数组,但将其设为链表节点数组。所以数组中的每个元素都指向一个链表。当发生冲突时,只需将新条目附加到与数组中该插槽对应的链表末尾即可。

    处理碰撞(更新)

    如果您不能动态分配新内存,那么您发布的解决方案似乎很好,尽管我不确定仅包含键的数组的目的是什么(键不应该是它所属元素的成员吗? )。以下是对您的解决方案的建议:

    拥有一维数组意味着在发生冲突的情况下,我们在插入和检索时都执行线性探测。另一种方法是使用二维数组,其中内部数组充当链表。我们需要索引插入到每个内部数组中的最后一个元素。与一维数组相比,不利的一面是,如果在同一个索引上发生太多冲突,那么我们可能会用完一个内部数组中的空间,除非我们也将每个内部数组的长度设为 N,这将导致大量浪费空间。优点是在插入时,我们不需要执行线性探针。我们只需检查内部数组中最后一个元素的索引并将其加一以获取下一个插入新元素的插槽。

    【讨论】:

    • 我认为引文末尾缺少某些内容。另外,我不确定我是否理解这如何解决我的问题;你能详细说明一下吗?
    • 感谢您的澄清。我将深入研究散列函数的输出并进行一些性能测试。我不能将您的建议用于碰撞处理,因为附加到链表会要求我分配新的内存,我不允许这样做(不是我的想法;但我必须接受这个要求)。
    • Re“一个键不应该是它所属元素的成员吗?”:是的,但我认为我可以使用第二个数组作为实际的哈希映射:它的元素会是 (key,index) 对,将索引放入第一个数组。第一个数组的大小为N*sizeof(really_big_struct),而第二个数组的大小要小得多。这样,您建议的解决方案实际上是可行的,而无需分配大量内存(仅N*N*sizeof(key_index_pair),即使使用N=1000 也很容易处理)。谢谢!
    猜你喜欢
    • 2016-01-25
    • 2018-08-08
    • 2012-04-01
    • 2012-04-09
    • 2011-01-09
    • 2013-01-06
    • 1970-01-01
    • 2023-04-04
    • 2016-03-29
    相关资源
    最近更新 更多