在 Python 中 set() 中的操作"add"还是 dict() 中的"insert"操作实际上是 O(n),其中 n 是键字符串的长度?



在dict((上的插入操作还是set((中的添加操作是o(n(或o(1(,其中 n 是字符串的长度。

假设我们的字符串在长度上有所不同,即n1,n2,... n_x。然后执行以下时间的时间复杂性:

s = set()
d = dict()
for x in {N}: # where N = [n1, n2, ... n_x]
  s.add(x)
  d[x] = 1

O(len(N) * Z)其中 Z = len(n_1) + len(n_2) + ... len(n_x)如果我们假设添加或插入是O(1(操作,则时间复杂度为O(Len(n((。

上述是真的吗?

来自:http://svn.python.org/projects/python/trunk/objects/stringobject.c我们看到哈希的计算取决于字符串的长度,这就是我假设在下面的len:

static long string_hash(PyStringObject *a)
{
    register Py_ssize_t len;
    register unsigned char *p;
    register long x;
    if (a->ob_shash != -1)
        return a->ob_shash;
    len = Py_SIZE(a);
    p = (unsigned char *) a->ob_sval;
    x = *p << 7;
    while (--len >= 0)
        x = (1000003*x) ^ *p++;
    x ^= Py_SIZE(a);
    if (x == -1)
        x = -2;
    a->ob_shash = x;
    return x;
}

这里(python词典中长(str(键的效率(有人表明改变字符串的长度不会影响计算哈希的时间。但这与上述代码相矛盾。

从以下链接中我们知道,一旦计算的哈希值存储在对象中。这意味着查找将是恒定的时间o(1(。获取无重新计算的词典键哈希但是,插入/添加是在完成哈希的计算时应是线性的。

insert 的性能依赖的情况有很多。对于长度k的字符串,哈希函数的计算确实是o(k(,但在通常的情况下,它只是无趣。

如果您仅考虑一个长度为8个字节的字符串键,则有1844467444073709551616不同的组合和8个是a stands ,8字节键的哈希计算为o(8((。

,但在18446744073709551616项目中,插入哈希表仍可能需要1 µs。对于列表,插入开始的位置为o(n(,而项目的插入/复制仅在列表的末端仅插入一个纳秒,插入列表的开头许多项目可能需要585年。

otoh,虽然可以想象您可能有4294967296甚至1844467444073709551616项目,如果您有一个4294967296或4294967296或184444444444444444444444444073709555161616旁观者的 key 需要重新考虑您的体系结构。

我在您给出的链接上添加了一个新答案,然后在这里再次链接。https://stackoverflow.com/a/70252226/800337

证明为什么键的长度上是o(n(,以及为什么缓存的哈希值并不重要,请想象这种情况...

您有一个文件系统,其中包含文件,每个文件的平均大小为30 kbytes。您想查找重复的文件,您的策略是一次读取文件,然后做类似的事情:

with open(filespec,'rb') as f:
    contents = f.read()
# If file contents are new, insert the contents as a key
#  and the filespec as the value
# If file contents are a duplicate, don't change the
#  dictionary, and return the first filespec where we
#  saw these contents.
spec = filedict.setdefault(contents, filespec)
if spec != filespec:
    print(f'{filespec} is a duplicate of {spec}')

我并不是说这是查找重复文件的最佳方法;有显然更好的方法。但是让我们无论如何都要分析。

对于我们阅读的每个文件,呼叫setDefault((方法确实需要哈希文件内容。这是因为我们没有得到内容字节来自已经被哈希的某个地方的对象,我们明白了从文件中读取。(如果我们正在查找相同的文件内容字节是的,在多个词典中,第二个和随后的查找可以做到在o(1(中散布,但是在这种用例中,这不适用。(

因此,查找新生成的字节对象是o(n(,只是因为我们还没有缓存哈希值。

现在假设查找实际上是在字典中列出的 - 我们以前看过一个文件具有相同的内容。查找不仅仅是因为我们在正确的哈希桶中找到了一个项目 - 实现现在将对新字节对象与字典中的字节对象进行字节比较。因为它们匹配,所以将比较整个字节对象 - 所有30个kbytes或其他。

因此,对于此用例,查找重复的文件会导致两个操作相对于文件的长度,这些操作是o(n( - 阅读文件后,我们需要哈希,然后在查找时,我们需要对新密钥和现有密钥进行完整的字节比较。

从中的关键要点是,如果您要查找已经在字典中的冗长的键词典 - 除非两个键是相同的确切对象,否则python通常足够聪明,可以微不足道地决定(a as b(,则(a == b(。

然而,正如我在其他链接答案上给出的时序统计所暗示的那样,您可以假设对于长度约为1k的小键,o(n(对哈希的贡献和钥匙比较足够小,以至于被忽略。直到钥匙长度开始显着攀升至1K以上,它才真正开始以明显的方式出现。

相关内容

最新更新