为python dicts优化最差情况时间复杂度为O(1)

时间:2013-03-03 22:53:49

标签: python memory dictionary hashtable complexity-theory

我必须在内存(RAM)中存储500M两位数的unicode字符。

我使用的数据结构应该有:

Worst Case Space Complexity: O(n)
Worst Case Time Complexity: O(1) <-- insertion, read, update, deletion

我正在考虑选择dict,这是在python中实现hash,但问题是它确保O(1)的时间复杂度,仅在平均情况下比最坏情况下所需的操作。

我听说如果已知条目数,则在最坏的情况下可以实现O(1)的时间复杂度。

怎么回事?

如果在python中不可能,我可以直接在我的python代码中访问内存地址和数据吗?如果是,那怎么办?

3 个答案:

答案 0 :(得分:4)

大多数情况下,性能命中(通常在碰撞时发生)都会在所有呼叫中摊销。因此,对于最实际的使用,您不会为每次通话获得O(n)。实际上,每次调用都会遇到O(n)命中的唯一情况是在每个键的哈希与现有键的哈希值冲突的情况下(即哈希表的最坏可能(或最不幸)使用) )。

例如,如果你事先知道了你的一组键,并且你知道它们不会有哈希冲突(即它们的所有哈希值都是唯一的),那么你就不会遇到碰撞情况。另一个主要的O(n)操作是哈希表调整大小,但其频率取决于实现(扩展因子/哈希函数/冲突解决方案等),并且它也会根据输入而逐个运行变化集。

在任何一种情况下,如果可以使用所有键预先填充dict,则可以避免突然的运行时间减速。这些值只能设置为None,并在以后填充其实际值。这应该在最初用键“启动”dict时引起唯一明显的性能损失,并且未来的值插入应该是恒定的时间。

一个完全不同的问题是你打算如何阅读/查询结构?你需要附加单独的值并通过密钥访问它们吗?应该订购吗?也许set可能比dict更合适,因为您实际上并不需要key:value映射。

<强>更新

根据您在评论中的描述,这开始听起来更像是数据库要做的工作,即使您正在使用临时集。您可以使用内存中的关系数据库(例如,使用SQLite)。此外,您可以使用像SQLAlchemy这样的ORM以更加热情的方式与数据库进行交互,而无需编写SQL。

甚至听起来你可能正在从数据库中读取数据,所以也许你可以进一步利用它?

存储/查询/更新大量独特键入的类型记录正是RDBMS专门用于数十年开发和研究的内容。使用预先存在的关系数据库(例如SQLite)的内存版本可能是一个更实用和可持续的选择。

尝试使用python的内置sqlite3模块,并通过提供":memory:"作为构建的db文件路径来尝试内存中的版本:

con = sqlite3.connect(":memory:")

答案 1 :(得分:2)

字典在技术上具有O(n)的最坏情况,但它极不可能发生,并且在您的情况下可能不会发生。我试着使用Dictionary而只切换到不同的实现,如果这不足以满足您的目的。

Here is a useful thread on the subject

答案 2 :(得分:2)

您是否有理由关注最差情况而非平均表现?任何合理的哈希表都会给你O(N)的平均表现。

如果你真的想要O(1)的最坏情况表现,这里有两种可能的方法:

  1. 拥有max(charCode)-min(charCode)条目的向量,并直接从unicode字符代码中查找所需的值。如果您的按键位于足够紧凑的范围内,可以将其放入RAM中,那么这将很有效。

  2. 使用强力方法选择哈希函数或字典大小(使用字典的自定义实现,让您控制它),并继续尝试新的函数和/或大小,直到你得到一个没有碰撞。预计这需要很长时间。 我不推荐这个。

  3. 修改

    假设您知道您将看到的最小字符代码是1234,并且您将看到的最大字符数是98765.此外,假设您有足够的RAM来容纳98765-1234个元素。我还假设你愿意使用numpy库或其他一些有效的数组实现。在这种情况下,您可以将值存储在矢量中:

    # configuration info
    max_value = 98765 # replace with your number
    min_value = 1234  # replace with your number
    spread = (max_value - min_value)
    dtype = object # replace with a primitive type if you want to store something simpler
    
    # create the big vector
    my_data = numpy.empty((spread,), dtype=dtype)
    
    # insert elements
    my_char_code              = ...
    my_value_for_my_char_code = ...
    
    assert min_value <= my_char_code < max_value
    my_data[my_char_code - min_value] = my_value_for_my_char_code
    
    # extract elements
    my_char_code              = ...
    assert min_value <= my_char_code < max_value
    my_value_for_my_char_code = my_data[my_char_code - min_value]
    

    这是O(1),因为查找是使用指针算法实现的,并且不依赖于存储在数组中的元素数量。

    如果您实际要存储的元素数量远小于spread,则此方法可能极其浪费RAM。例如,如果spread是40亿(全部是UTF32),那么my_data就会消耗至少40亿* 8字节/指针= 32 GB的RAM(可能更多;我不知道知道Python引用有多大)。另一方面,如果min_value是30亿而max_value = min_value + 100,则内存使用量会很小。