打印给定范围内的所有素数,算法太慢.如何改进?(code-challenge)



这是一个关于代码挑战的问题,请不要提供太多代码。我想尽可能自己解决这个问题。

我最近开始参加代码挑战,并将其与学习Python结合起来(我白天是前端javascript开发人员;))。到目前为止,一切都很顺利,我相信这是学习一门新语言的最好方法(至少对我来说)。

我目前遇到了一个挑战,要求我打印给定范围内的所有素数,这都是通过简单的Stdin和Stdout完成的。

到目前为止,我已经尝试了两种方法,都是工作,但太慢了。下面是一个链接和代码,我最快的实现到目前为止。也许我错过了一些非常明显的东西,导致我的python脚本变慢。目前,单个测试用例使用1.76s,范围为1, 100000

http://ideone.com/GT6Xxk(您也可以在这里调试脚本)

from sys import stdin
from math import sqrt, ceil
next(stdin) # skip unecessary line that describes the number of test cases
def is_prime(number):
    initial_divider = sqrt(number)
    if number == 2:
      return True
    elif number % 2 == 0 or int(initial_divider) == initial_divider:
      return False
    for divider in range(ceil(initial_divider), 1, -1):
        if number % divider == 0:
            return False
    return True
for line in stdin:
    low, high = [int(x) for x in line.split(' ')]
    primes = [number for number
                     in range(max(low, 2), high+1)
                     if is_prime(number)]
    for prime in primes:
        print (prime)
    print('')

"任务"/挑战的描述如下:

输入

输入以单行中测试用例的数量t开始(t<=10)。在接下来的t行中,每一行有两个数字m和n (1 <= m<= n <=>1000000000, n-m<=100000),中间用一个空格分隔。

输出

对于每个测试用例打印所有素数p,使得m <= p <= n,每行一个数字>,测试用例用空行分隔。

更新1:我清理了最后一个块的逻辑,其中收集质数并打印完成:

for line in stdin:
    low, high = [int(x) for x in line.split(' ')]
    for number in range(max(low, 2), high+1):
        if is_prime(number):
            print (number)
    print('')

1)它可能由控制台IO主导,打印输出。我更改了输出,因此它使用生成器收集素数,将数字转换为字符串,并用换行符将数字连接起来。这将在构建列表时节省一些内存,并将一些Python列表迭代推入Python运行时。在我的PC上进行的不科学的仓促测试中,这让它的速度提高了30%,但在ideone上却没有多大区别。(这可能是因为我把它移植到Python 2中运行,Python 2有一些非常不同的迭代/列表/生成器工作方式,但在ideone上使用了Python 3)。

2)每次都运行if number == 2: return True测试;在前10万个数字中,大多数不是2。在打印其余质数之前,我把它提取出来打印2。非常小的变化,不值得。

3)你的范围计数 - range(ceil(initial_divider), 1, -1) -这真的很奇怪。一个数被3整除的概率比被19整除的概率大得多。每第三个数能被3整除,只有第19个数能被19整除。为了快速返回函数,先试试小除法,对吧?我将它设置为向上计数。速度明显提高,希望还能用

这是原始运行时间的50%,在一个随意且完全不可比较的情况下。代码现在看起来像这样:

from sys import stdin
from math import sqrt, ceil
next(stdin) # skip unecessary line
def is_prime(number):
    initial_divider = sqrt(number)
    if number % 2 == 0 or int(initial_divider) == initial_divider:
      return False
    for divider in range(2, ceil(initial_divider)+1):
        if number % divider == 0:
            return False
    return True
for line in stdin:
    low, high = [int(x) for x in line.split(' ')]
    primes = 'n'.join(str(number) for number
                     in range(max(low, 3), high+1)
                     if is_prime(number))
    if low <= 2: print(2)
    print (primes)
    print('')

更改列表理解为生成器,脚本将运行得更快。

for number in range(max(low, 2), high+1):
    if is_prime(number):
        yield number

在像C或c++这样的语言中,SPOJ PRIME 1问题可以很容易地通过暴力破解来解决,即通过编写代码在不到一秒的时间内筛除1000,000,000以内的所有数字,从而保持在时间限制之内。甚至可能在Java、c#或Delphi中。但是如果在Python中是可能的,那么它可能是非常困难的,并且需要一些严肃的fu。

但是,请注意,SPOJ PRIME 1并没有要求筛选10亿个数字;它要求筛选几个不超过100001数字的小范围,并且它可能只查询少数范围。假设范围的数量是100(可能少得多)平均宽度是100000(可能少得多)那么仍然只有1000000个数字。在这种情况下,对全部十亿进行筛选需要做两个数量级的工作,这就是为什么SPOJ PRIME 1能够如此精确地剔除糠,尽管专家们使用了各种各样的语言。

因此,诀窍是只做被要求做的事情——筛选作为输入提供的范围。即使是最简单、最直接的代码也可以用大量的时间来完成(c++:总共大约一毫秒)。这个原理和我对从一个上界为1000,000,000的范围内随机抽取100个素数的问题的回答完全一样,同样的解决方案也适用。关键是编写一个可以筛选给定范围(窗口)的函数,而不必筛选下面的所有数字。

此外,如何击败SPOJ PRIME 1的问题已经被问过很多次了,给出的答案仍然有效。小选择:

  • 如何有效地筛选选定的质数范围?
  • 获取两个大数之间质数的高效算法
  • 生成m和n之间的素数
  • spoj prime1: tle
  • Erastothenes c++ SPOJ的分段筛
  • 使用eratosthenes筛的Spoj PRIME1 (in C)
def PrimesBelow(limit):
    np=set()
    p=[2]
    for i in xrange(1,limit+1,2):
            if i == 1: continue
            if i in np: continue
            beg=2 if i % 2 == 0 else 0
            for j in xrange(beg,int(limit)+1,i):
                    np.add(j)
            p.append(i)
    return i

LetzerWille是正确的。上面的函数将返回(limit)以下的素数列表。我认为这个函数将比检查每个数字是否是素数运行得更快,因为这个函数将删除每个数字的乘法并将它们添加到(np)。旁注:这个函数只测试奇数。

简单的改进。看到这么简单的代码真让人尴尬。我的要长得多,慢得多:(…但是我学到了很多:)

还增加了测量时间的简单函数mt()

def PrimesBelow(limit):
    np=set()
    p=[2]
    for i in range(3,limit+1,2):
            if i in np: continue
            beg = i*i
            for j in range(beg,int(limit)+1,i):
                    np.add(j)
            p.append(i)
    return p
def mt(n):
    import time
    t = time.time()
    pr = PrimesBelow(n)
    print("#-Primes: {}, Time: {}".format(len(pr), round(time.time()-t, 4)))
    return pr
pr = mt(100000000)

在16GB的i7-3770上大约是49秒

这将是执行次数较少的优化代码,它可以在一秒钟内计算和显示10000个素数。它利用了质数性质如果一个数不能被小于其平方根的数整除,则该数为素数。*我们可以在35次迭代内结束循环,而不是检查到最后(意味着1000次迭代来确定1000是否是素数)。* break如果在开始时被任何数字除,则循环(如果是偶数,则循环将在第一次迭代时中断,如果它可被3整除则迭代2次),因此我们迭代直到结束,仅针对素数

记住一件事,你仍然可以通过使用属性*来优化迭代如果一个数字不能被小于它的素数整除那么它就是素数,但是代码会太大,我们必须跟踪计算的素数,而且很难找到一个特定的数字是否是素数,所以这将是最好的逻辑或代码

import math
number=1
count = 0

while(count<10000):
    isprime=1
    number+=1
    for j in range(2,int(math.sqrt(number))+1):
        if(number%j==0):
            isprime=0   
            break
    if(isprime==1):
        print(number,end=" ")
        count+=1    
print("nCount "+str(count))

最新更新