在哈希表中创建字符串的哈希值的时间复杂度

Posted

技术标签:

【中文标题】在哈希表中创建字符串的哈希值的时间复杂度【英文标题】:Time complexity of creating hash value of a string in hashtable 【发布时间】:2015-10-11 12:48:40 【问题描述】:

通常说在哈希表中插入和查找字符串是 O(1)。但是字符串的哈希键是如何产生的呢?为什么不考虑 O(L),字符串的长度? 我很清楚为什么整数是 O(1),而不是字符串。

我确实理解为什么通常插入哈希表是 O(1),但我对将哈希插入表之前的步骤感到困惑:生成哈希值。

在 java 中如何生成字符串的哈希键和在 C++ 中如何生成 unordered_map 之间也有什么区别吗? 谢谢。

【问题讨论】:

为什么关心字符串的长度,却忽略整数的位数? 啊,即使没有任何上下文也具有普遍意义的神奇“O(1)”。 @Matt,由于当数字可以适合 32 位或 64 位时,CPU 可以在 O(1) 内完成大部分操作。此外,大多数时候我们有长字符串,而不是大整数。 (尤其是编程比赛!) 我认为您不太了解 O(1) 在这种情况下的含义。散列一个键所花费的时间与散列表的当前大小完全没有关系。 Java 字符串在第一次计算后缓存其哈希码,因此您不必再次计算。 【参考方案1】:

在哈希表中插入等是 O(1),因为它在 表中元素的数量中是恒定的

在这种情况下,“O(1)”没有声称你可以多快地计算你的哈希值。如果为此付出的努力以某种方式增长,那就是它的方式。但是,我发现一个体面的(即“适合此应用程序”)散列函数的复杂性不太可能比散列对象的“大小”(即我们的字符串示例中的长度)的线性更差。

【讨论】:

那么,有什么方法可以让我意识到在 C++ 和 Java 中计算哈希的速度有多快?理论上(以及编程竞赛和面试题!),它可以在分析算法的时间复杂度方面产生影响。 @MehrdadAP 至少在 C++ 中,不是不看你的哈希函数的实现。但是,我希望为此目的的每个合理的散列函数在它散列的对象的“长度”或“大小”(无论这对您正在散列的对象意味着什么)中都具有线性复杂性。尽管我可以想象在某些情况下“较慢”的哈希值由于某种原因具有优势。 @MehrdadAP 不会用 C++ 说话,但 Java 哈希值是 O(N),N 取决于字符串的大小。在 C++ 中,大多数时候没有散列。例如,std::map 通常是一棵红黑树。 @user4581301 好吧,std::map 确实不是哈希表。例如,std::unordered_set 将使用哈希。 鲍姆米特奥根点。 @MehrdadAP 我有一位教练曾经这样解释过:“可能是 O(1),但 1 仍然可能需要一百万年。”【参考方案2】:

通常说在哈希表中插入和查找字符串是 O(1)。但是字符串的哈希键是如何产生的呢?为什么不是 O(L),字符串的长度?我很清楚为什么整数是 O(1),而不是字符串。

通常引用的 O(1) 表示时间不会随着容器中元素的数量而增长。正如您所说,从字符串生成哈希值的时间本身可能不是 O(1) 字符串长度 - 尽管对于某些实现它是:例如 Microsoft 的 C++ std::hash<std::string>有:

            size_t _Val = 2166136261U;
            size_t _First = 0;
            size_t _Last = _Keyval.size();
            size_t _Stride = 1 + _Last / 10;

            if (_Stride < _Last)
                    _Last -= _Stride;
            for(; _First < _Last; _First += _Stride)
                    _Val = 16777619U * _Val ^ (size_t)_Keyval[_First];
            return (_Val);

_Stride 是字符串长度的十分之一,因此 固定 个相距很远的字符将包含在哈希值中。这样的哈希函数在字符串长度上是O(1)

GCC 的 C++ 标准库采用了不同的方法:至少在 v4.7.2 中,它通过 _Hash_impl 支持类调用 static 非成员函数 _Hash_bytes,该函数执行包含每个字节的 Murmur 哈希.因此,GCC 的 hash&lt;std::string&gt; 在字符串长度上是 O(N)

GCC 对冲突最小化的更高优先级也体现在它对std::unordered_setstd::unordered_map 使用质数的桶中,MS 的实现没有这样做 - 至少在 VS2013/VC12 之前;总而言之,MS 的方法对于不易发生冲突且负载系数较低的键来说会更轻/更快,但会更早且更大幅度地降级。

Java 中的 hashTable 和 C++ 中的 unordered_map 生成字符串哈希键的方式有什么区别?

C++ 标准没有指定字符串的散列方式 - 由各个编译器实现决定。因此,不同的编译器会做出不同的妥协——甚至是同一编译器的不同版本。

文档 David Pérez Cabrera 的答案链接解释了 Java 中的 hashCode 函数:

返回此字符串的哈希码。 String 对象的哈希码计算为

 s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

使用int算术,其中s[i]是字符串的ith字符,n是字符串的长度,^表示求幂。 (空字符串的哈希值为零。)

字符串的长度显然是 O(N)。

快速返回...

通常说在哈希表中插入和查找字符串是 O(1)。

...一个“关键”;-P 洞察力是,在许多问题领域中,已知字符串的实际长度不会有显着变化,或者最坏情况长度的散列仍然足够快。考虑一个人或公司的名称、街道地址、来自某些源代码的标识符、编程语言关键字、产品/书籍/CD 等名称:您可以预期十亿个键需要大约一百万倍的内存来存储第一千。使用哈希表,对整个数据集的大多数操作预计会花费一百万倍的时间。这将在 100 年后和今天一样真实。重要的是,如果某些请求与单个密钥相关,则执行时间不会比过去使用一千个密钥的时间长(假设有足够的 RAM,并忽略 CPU 缓存影响)——当然,如果它是一个长密钥它可能需要比短键更长的时间,如果您有超低延迟或硬实时要求,您可能会关心。但是,尽管拥有一百万倍的数据,使用随机键的请求的平均吞吐量将保持不变。

仅当您的问题域的密钥大小差异很大,并且考虑到您的性能需求,密钥散列时间很重要,或者您预计平均密钥大小会随着时间的推移而增加(例如,如果密钥是视频流,并且每隔几年人们就会提高分辨率和帧速率,从而导致密钥大小呈指数增长),您是否需要密切关注散列(和密钥比较)成本。

【讨论】:

"_Stride 是字符串长度的十分之一,所以固定数量的相距很远的字符将被包含在哈希值中。这样的哈希函数在长度上是 O(1)细绳。”我想说正确的时间复杂度,在这种情况下,是 O(log N),N 是字符串的长度。不是吗? @AliSalehi:很抱歉,但不是这样。如果 N 是字符串的长度,并且您只需沿字符串选择 10 个位置,那么字符串的长度是 10 个字符还是 1000 万个字符都没有关系 - 您可以直接访问这 10 个字符而无需遍历其余字符,由于内存是随机访问的。无论字符串的长度如何,都有一个固定的上层处理工作,因此 O(1)。 是的,我认为在你从函数中复制的 for 循环中:for(; _First &lt; _Last; _First += _Stride) 它会以 10 的步幅一直上升到 _Last,我没有注意_Stride = 1 + _Last / 10; @AliSalehi 是的 - 步幅是 N/10。但是,即使它跨 10 个字符,那也是 O(N) 而不是 O(log N)。例如,要散列一个 10,000 个字符的字符串,您仍然会比处理 10 个字符的字符串多做一千倍的工作 - 随着字符串长度的增加,它以相同的 10,000:10 = 1000:1 的比例增加,因此 O (N)。【参考方案3】:

根据Java的实现,Hashtable使用key(String或Integer)的hashCode方法。 Hashtable String.hashCode Integer.hashCode

而C++根据http://en.cppreference.com/w/cpp/utility/hash使用std::hash&lt;std::string&gt;std::hash&lt;int&gt;,并且实现在函数文件中(/path/to/c++.../include/c++/4.8/functional)

【讨论】:

看 Java 实现很有趣...谢谢!【参考方案4】:

散列函数的复杂度永远不会是 O(1)。如果字符串的长度是 n,那么复杂度肯定是 O(n)。但是,如果您计算给定数组中的所有哈希值,则不必进行第二次计算,并且您始终可以通过比较预先计算的哈希值在 O(1) 时间内比较两个字符串。

【讨论】:

以上是关于在哈希表中创建字符串的哈希值的时间复杂度的主要内容,如果未能解决你的问题,请参考以下文章

哈希结构(图文详解)哈希表,哈希桶,位图,布隆过滤器

在 Powershell 中创建多维哈希表数组

字符串哈希

什么是哈希表以及如何在 C 中创建它? [关闭]

是否可以在 PowerShell 中创建不可修改的哈希表?

数据结构与算法--------哈希表