访问 Python dict 的时间复杂度

Posted

技术标签:

【中文标题】访问 Python dict 的时间复杂度【英文标题】:Time complexity of accessing a Python dict 【发布时间】:2010-12-30 03:04:54 【问题描述】:

我正在编写一个简单的 Python 程序。

我的程序似乎受到字典线性访问的影响, 即使算法是二次的,它的运行时间也会呈指数增长。 我使用字典来记忆值。这似乎是一个瓶颈。

我正在散列的值是点的元组。 每个点是:(x,y), 0 字典中的每个键是:2-5个点的元组:((x1,y1),(x2,y2),(x3,y3),(x4,y4))

读取密钥的次数比写入次数多很多倍。

我是否正确地认为 python dicts 会受到此类输入的线性访问时间的影响?

据我所知,集合保证了对数访问时间。 如何在 Python 中使用集合(或类似的东西)模拟 dicts?

edit根据要求,这里有一个(简化)版本的记忆功能:

def memoize(fun):
    memoized = 
    def memo(*args):
        key = args
        if not key in memoized:
            memoized[key] = fun(*args)
        return memoized[key]
    return memo

【问题讨论】:

你有什么证据证明这一点?你能提供你的实际性能数字吗?简介结果?你很可能在错误的地方寻找你的问题。因此,请在猜测原因之前记录您的问题。 你能给我们发一些记忆功能的示例代码吗?您是否也可以尝试编写一个快速测试应用程序,为您的数据生成大量哈希并计算冲突次数(应该不会花费很长时间,这取决于 python 中哈希的工作方式) 请粘贴实际的记忆功能,不是简化版。您可能在简化时隐藏了该错误。 -1 用于提出无法回答的问题。需要一个要测试的数据集以及完整的可分析代码(带有分析结果)。 -1 用于在没有足够信息来正确评估问题的情况下提出问题,然后暗示其他人要求更多信息是“粗鲁的”。你是那个寻求帮助的人,当人们提供帮助你询问更多信息时,他们并不“粗鲁”,但你做出这种人身攻击肯定是粗鲁的。您显然不知道回答您提供的问题所需的所有信息,或者您已经自己回答了。 【参考方案1】:

见Time Complexity。 python dict是一个hashmap,因此如果hash函数不好并导致很多冲突,它最坏的情况是O(n)。然而,这是一种非常罕见的情况,其中添加的每个项目都具有相同的哈希值,因此被添加到同一个链中,这对于主要的 Python 实现来说是不可能的。平均时间复杂度当然是 O(1)。

最好的方法是检查并查看您正在使用的对象的哈希值。 CPython Dict 使用 int PyObject_Hash (PyObject *o),相当于 hash(o)

经过快速检查,我还没有找到两个哈希值相同的元组,这表明查找是 O(1)

l = []
for x in range(0, 50):
    for y in range(0, 50):
        if hash((x,y)) in l:
            print "Fail: ", (x,y)
        l.append(hash((x,y)))
print "Test Finished"

CodePad(24小时可用)

【讨论】:

感谢您的回答,但我已经知道了。请尝试回答我的特定问题。 嘿,好主意。我没有想到在如此小的范围内进行详尽的测试是可能的。 @Martin - 这是一个看似很大的范围。我对它进行了高达 200 x 200 的测试,它通过了。 那个小范围无关紧要...... OP没有使用(x, y)点作为键——他使用((x0,y0),(x1, y1))直到((x0,y0), ..., (x4, y4))。他有sum(51**(n*2) for n in range(2,6))(即119088209375236404)可能的密钥,而不是51**2 @Chris:什么是看似很大的范围?【参考方案2】:

你说得不对。 dict 访问不太可能是您的问题。几乎可以肯定是 O(1),除非您有一些非常奇怪的输入或非常糟糕的散列函数。粘贴您应用程序中的一些示例代码,以便更好地进行诊断。

【讨论】:

索取示例代码并不粗鲁。字典访问 is 几乎总是 O(1),所以我们需要查看示例代码来提出其他可能的瓶颈。【参考方案3】:

如果您提供示例代码和数据,提出建议会更容易。

访问字典不太可能成为问题,因为该操作是O(1) on average, and O(N) amortized worst case。内置散列函数可能会遇到数据冲突。如果您对内置哈希函数有疑问,您可以提供自己的。

Python 的字典实现 降低平均复杂度 字典查找到 O(1) 通过 要求关键对象提供 “哈希”函数。这样的哈希函数 获取关键对象中的信息 并用它来产生一个整数, 称为哈希值。这个哈希值 然后用于确定哪个 “桶”这个(键,值)对应该 放入。

您可以覆盖类中的 __hash__ 方法来实现自定义哈希函数,如下所示:

def __hash__(self):    
    return hash(str(self))

根据您的数据的实际情况,您可能会想出一个比标准函数冲突更少的更快的哈希函数。然而,这不太可能。请参阅Python Wiki page on Dictionary Keys 了解更多信息。

【讨论】:

怎么这么粗鲁?【参考方案4】:

回答您的具体问题:

第一季度: “我是否纠正了 python dicts 受到此类输入的线性访问时间的影响?”

A1:如果您的意思是平均查找时间为 O(N),其中 N 是字典中的条目数,那么您很可能是错误的。如果你是正确的,Python 社区非常想知道在什么情况下你是正确的,以便可以缓解或至少警告问题。 “示例”代码和“简化”代码都没有用。请显示重现问题的实际代码和数据。代码应该配备诸如字典项目数和每个 P 的字典访问数之类的东西,其中 P 是键中的点数 (2

第二季度: “据我所知,集合保证了对数访问时间。 如何在 Python 中使用集合(或类似的东西)模拟 dicts?”

A2:集合在什么情况下保证对数访问时间? Python 实现没有这样的保证。最近的 CPython 版本实际上使用了精简的 dict 实现(只有键,没有值),所以期望是平均 O(1) 行为。你如何用任何语言模拟带有集合或类似内容的字典?简短的回答:非常困难,如果您想要dict.has_key(key) 之外的任何功能。

【讨论】:

【参考方案5】:

正如其他人所指出的,在 Python 中访问字典很快。考虑到它们的核心作用,它们可能是该语言中最好的数据结构。问题出在其他地方。

你要记住多少个元组?你考虑过内存占用吗?也许您将所有时间都花在内存分配器或分页内存上。

【讨论】:

【参考方案6】:

我的程序似乎受到对字典的线性访问的影响,即使算法是二次的,它的运行时间也呈指数增长。

我使用字典来记忆值。这似乎是一个瓶颈。

这是您的记忆方法中存在错误的证据。

【讨论】:

以上是关于访问 Python dict 的时间复杂度的主要内容,如果未能解决你的问题,请参考以下文章

python基本数据类型的时间复杂度

python内置数据类型列表list和字典dict的性能

字典和哈希表空间复杂度

1. 时间复杂度(大O表示法)以及使用python实现栈

python sorted排序:使用lambda应对各种复杂情况的排序,包括list嵌套dict

记一次Python后端开发面试的经历