【问题标题】:Retrieving the next smallest element of a Python set检索 Python 集合的下一个最小元素
【发布时间】:2012-09-16 15:59:56
【问题描述】:

Sieve of Eratosthenes 是一种相当快的生成素数的方法,其上限为k,如下所示:

  1. p = (2, 3, 4, ..., k)i = 2 集合开始。
  2. i^2 开始,从p 中删除所有i 的倍数。
  3. p 中重复下一个最小的i,直到i >= sqrt(k)

我当前的实现是这样的(预过滤所有偶数的明显优化):

# Compute all prime numbers less than k using the Sieve of Eratosthenes
def sieve(k):
    s = set(range(3, k, 2))
    s.add(2)

    for i in range(3, int(sqrt(k)), 2):
        if i in s:
            for j in range(i ** 2, k, i * 2):
                s.discard(j)

    return sorted(s)

编辑:这是基于 list 的等效代码:

def sieve_list(k):
    s = [True] * k
    s[0] = s[1] = False
    for i in range(4, k, 2):
        s[i] = False

    for i in range(3, int(sqrt(k)) + 2, 2):
        if s[i]:            
            for j in range(i ** 2, k, i * 2):
                s[j] = False

    return [2] + [ i for i in range(3, k, 2) if s[i] ]

这可行,但并不完全正确。行:

for i in range(3, int(sqrt(k)), 2):
    if i in s:
        [...]

通过测试每个奇数的集合成员关系,找到s 的下一个最小元素。理想情况下,实现实际上应该是:

while i < sqrt(k):
    [...]
    i = next smallest element in s

但是,由于set 是无序的,我不知道如何(或者即使可能)以更有效的方式获取下一个最小元素。我考虑过使用listTrue/False 标志作为素数,但您仍然需要通过list 寻找下一个True 元素。您也不能直接从 list 中删除元素,因为这使得在第 2 步中有效地删除复合数字变得不可能。

有没有办法更有效地找到下一个最小的元素?如果没有,是否有其他一些数据结构允许 O(1) 按值删除以及找到下一个最小元素的有效方法?

【问题讨论】:

  • 附注如果您使用的是 Python 2.x,请使用 xrange 以避免每次都创建一个列表。
  • 如果你分析你的算法,它实际上非常有效。你只做 O(n) if i in s 电话。算法的其余部分至少为 O(n),因为您必须构建列表并从中删除 O(n) 元素。因此,找到最小值的“低效”方式不会影响算法运行时间的顺序。
  • O(sqrt(n)) 实际上是调用,所以现在我想它可能与其他过程相比几乎没有成本。我也意识到我无法提高渐近复杂度,我只是在寻找一些小的 % 改进。

标签: python algorithm data-structures


【解决方案1】:

集合是无序的,因为它们在内部实现为哈希集。在这样的数据结构中没有找到最小元素的有效方法; min(s) 将是最 Pythonic 的方式(但它是 O(n))。

您可以使用collections.deque 以及您的设置。使用deque 按排序顺序存储元素列表。每次您需要获得最小值时,从deque 中弹出元素,直到找到您的集合中的元素。这已经在整个输入数组中摊销了 O(1) 成本(因为您只需弹出 n 次)。

我还应该指出,no 数据结构可能有从列表创建 O(n)(或 O(1) 插入)、O(1) 按值删除和 O (1) 最小发现;这样的数据结构可以用来简单地实现 O(n) 的一般排序,这是(信息论的)不可能的。哈希集非常接近,但必须牺牲有效的最小值查找。

【讨论】:

  • 这是不可能的,当你把它放在排序方面时,它是如此明显!不过,我不确定deque 是如何给我带来任何东西的,而不仅仅是遍历奇数并做if i in s。无论如何,hashset 成员资格不是有效 O(1) 吗?
  • 是的。就您而言,我认为您已经达到了合理需要的效率。如果您随机删除元素,双端队列会有所帮助。
  • 谢谢。有趣的是,列表版本更快,即使您只返回集合而不对其进行排序(k = 30M 为 4 秒 vs 7.5 秒)。我希望这组更快。
  • set.discardlist[i] = False 慢,这就是代码中的热循环(因为set.discard 需要散列、桶查找、可能的设置调整大小等,而list[i] 只是列表索引操作)。同样,if i in setif list[i] 慢。
【解决方案2】:

您可以使用列表来代替集合。使用 None 初始化列表以表示未标记。您可以使用元素索引作为数字。

  1. 初始化列表
  2. 从 p = 2 开始
  3. 用'M'标记列表中p的所有倍数
  4. 在列表中找到下一个未标记的元素并将其设为新的p。如果没有,你就完成了。

如果您需要找到下一个未标记的索引,您可以简单地查看索引 p 之后的元素并且等于 None。

【讨论】:

  • 我已经在问题中提到了使用listlist 的问题在于它是按位置而不是值索引的,因此您无法在步骤 2 中有效地删除非素数。
  • 抱歉,我没有仔细阅读。我认为您想避免多次标记非素数。你认为从集合中删除非素数会使算法更快吗?
  • 我最初使用list(见编辑)实现了这个,但遇到了同样的问题——你仍然需要迭代每个奇数才能找到下一个素数。我无法使用listset 解决这个问题。
猜你喜欢
  • 1970-01-01
  • 2013-07-30
  • 1970-01-01
  • 1970-01-01
  • 2018-01-08
  • 2011-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多