我将如何改进/使这次运行更快?

时间:2013-06-24 15:39:11

标签: python performance optimization python-2.7

我是Python的初学者,试图变得更好,我偶然发现了以下练习:

  

设n是大于1的整数,s是n(d)的总和   ñ。例如,

s(12) 1 + 2 + 3 + 4 + 6 + 12 = 28
     

此外,

s(s(12)) = s(28) = 1 + 2 + 4 + 7 + 14 + 28 = 56
     

并且

s(s(s(12))) = s(56) = 1 + 2 + 4 + 7 + 8 + 14 + 28 + 56 = 120
     

我们使用符号:

s^1(n) = s(n)
s^2(n) = s(s(n))
s^3(n) = s(s(s(n)))
s^ m (n) = s(s(. . .s(n) . . .)), m times
     

对于整数n,其存在正整数k,以便

 s^m(n) = k * n
     

被称为(m,k) - 完美,例如12是(3,10) - 完美的   s ^ 3(12)= s(s(s(12)))= 120 = 10 * 12

     

特殊类别:

     

对于m = 1,我们有多个完美的数字

     

对于m = 1和k = 2,存在上述特殊情况   完美的数字。

     

对于m = 2且k = 2,我们有完美的数字。

     

编写一个程序,查找并打印所有(m,k) - 完美的数字   m <= MAXM,小于或等于(&lt; =)MAXNUM。如果是整数   属于上述程序中提到的特殊类别之一   应打印相关信息。此外,该程序必须打印如何   找到了许多不同的(m,k) - 完美数字,其中有多少百分比   他们所测试的数字是多少次   不同的(m,k)对,以及每个特殊类别中的多少个   被发现(完美的数字也算作多重完美)。

这是我的代码:

import time
start_time = time.time()
def s(n):
    tsum = 0
    i = 1
    con = n
    while i < con:
        if n % i == 0:
            temp = n / i
            tsum += i
            if temp != i:
                tsum += temp 
            con = temp
        i += 1                    
    return tsum
#MAXM
#MAXNUM

i = 2

perc = 0
perc1 = 0
perf = 0
multperf = 0
supperf = 0
while i <= MAXNUM:
    pert = perc1
    num = i
    for m in xrange(1, MAXM + 1):        
        tsum = s(num)                
        if tsum % i == 0:            
            perc1 += 1
            k = tsum / i            
            mes = "%d is a (%d-%d)-perfect number" % (i, m, k)
            if m == 1:
                multperf += 1
                if k == 2:
                    perf += 1 
                    print mes + ", that is a perfect number"
                else:
                    print mes + ", that is a multiperfect number"               
            elif m == 2 and k == 2:
                supperf += 1
                print mes + ", that is a superperfect number"
            else:
                print mes        
        num = tsum        
    i += 1
    if pert != perc1: perc += 1
print "Found %d distinct (m-k)-perfect numbers (%.5f per cent of %d ) in %d occurrences" % (
perc, float(perc) / MAXNUM * 100, MAXNUM, perc1)
print "Found %d perfect numbers" % perf
print "Found %d multiperfect numbers (including perfect numbers)" % multperf
print "Found %d superperfect numbers" % supperf  
print time.time() - start_time, "seconds"

它工作正常,但我想了解如何让它运行得更快。 例如,使用它更快

I = 1
while I <= MAXM:
    …..
    I += 1

而不是

for I in xrange(1, MAXM + 1)

如果不将s(n)定义为函数我将代码放入主程序中会不会更好?等等 如果您有任何建议让我阅读如何让程序运行得更快,我会很感激。 还有一件事,最初的练习要求程序在C语言中(我不知道),用Python编写它,将它制作成C有多难?

2 个答案:

答案 0 :(得分:9)

最大的改进来自使用更好的算法。像

这样的事情
  

如果不将s(n)定义为函数而将代码放入主程序中,会不会更好?

或者是否使用while循环代替for i in xrange(1, MAXM + 1):没有太大区别,所以在一个人达到算法改进至少的状态之前不应该考虑非常很难得到。

因此,让我们来看看您的算法以及我们如何在不关心诸如while循环或for迭代更快的微不足道的事情的情况下彻底改进它。

def s(n):
    tsum = 0
    i = 1
    con = n
    while i < con:
        if n % i == 0:
            temp = n / i
            tsum += i
            if temp != i:
                tsum += temp 
            con = temp
        i += 1                    
    return tsum

这已经包含了一个好主意,你知道n的除数成对出现,并且一旦你找到它们中较小的一对就加上两个除数。你甚至可以正确处理方块。

对于120这样的数字非常有效:当你找到除数2时,你设置停止条件为60,当你找到3,到40,...时,当你找到8时,你将它设置为15,当你找到10时,你将它设置为12,然后你只有11的除法,并在i增加到12时停止。不错。

但是当n为素数时它不能很好地工作,那么con将永远不会设置为小于n的值,并且您需要迭代所有在找到除数之前到n的路。对于具有素数n = 2*p的{​​{1}}形式的数字也不好,然后您循环到pn/2n = 3*p,除非n/3)等。

通过素数定理,不超过p = 2的素数的数量渐近x(其中x/log x是自然对数),并且你的下限为

log

仅用于计算素数的除数和。那不好。

由于您已经考虑Ω(MAXNUM² / log MAXNUM) 成对nd的除数,请注意两者中的较小者(忽略案例n/d {{1} }是一个正方形的正方形)小于d = n/d的平方根,所以一旦测试除数达到平方根,你知道你已经找到并添加了所有除数,并且你已经完成了。任何进一步的循环都是徒劳的浪费。

所以让我们考虑一下

n

作为第一个改进。然后你总是循环到平方根,n的{​​{1}}计算是def s(n): tsum = 0 root = int(n**0.5) # floor of the square root of n, at least for small enough n i = 1 while i < root + 1: if n % i == 0: tsum += i + n/i i += 1 # check whether n is a square, if it is, we have added root twice if root*root == n: tsum -= root return tsum 。这已经相当不错了。 (当然,您必须计算迭代除数和,s(n)对于某些1 <= n <= MAXNUM可能大于Θ(MAXNUM^1.5),因此您无法推断s(n)的复杂性界限1}}对于总算法而言。MAXNUM不能大得多,因此复杂性也可能更差。)

但我们仍然可以通过使用twalberg suggested使用n <= MAXNUM的素数因子来计算除数和来改进。

首先,如果O(MAXM * MAXNUM^1.5)是主要权力,则s(n)的除数为n,并且可以很容易地计算除数和(几何和的闭合公式为

n = p^k

但是,是否使用n 1, p, p², ..., p^k (p^(k+1) - 1) / (p - 1) 除以k+1的权力并不重要。

接下来,如果p具有素数nn = p^k * m使p不分m,那么

p

查看分解的一种简单方法是在m形式s(n) = s(p^k) * s(m) 中写d的每个除数n,其中d = p^a * g不分p。然后g必须划分p^a,即p^ka <= k必须划分g。相反,对于每个m和每0 <= a <= k除以gmp^a * g的除数。所以我们可以列出n的除数(其中n1 = g_1 < g_2 < ... < g_r = m的除数)

m

,每行的总和为 1*g_1 1*g_2 ... 1*g_r p*g_1 p*g_2 ... p*g_r : : : p^k*g_1 p^k*g_2 ... p^k*g_r

如果我们有一个方便的素数列表,我们可以写

p^a * s(m)

试验部门是def s(n): tsum = 1 for p in primes: d = 1 # divide out all factors p of n while n % p == 0: n = n//p d = p*d + 1 tsum *= d if p*p > n: # n = 1, or n is prime break if n > 1: # one last prime factor to account for tsum *= 1 + n return tsum [如果n是复合的]的第二大素数因子或最大素因子n的平方根,以较大者为准。对于n尝试的最大除数,它具有最坏情况的约束,这对于素数来说是达到的,但是对于大多数复合材料来说,该部门会更早地停止。

如果我们没有方便的素数列表,我们可以用n**0.5替换行for p in primes: [上限并不重要,因为如果它更大则永远不会达到而不是for p in xrange(2, n):]并且得到一个不太慢的因子分解。 (但是,即使避免大于2的试验除数,也可以很容易地加速,即使用列表n**0.5 - 最好作为生成器 - 对于除数,甚至更多也可以跳过3的倍数(3除外) ),[2] + [3,5,7...],如果你想要一些更小的素数。)

现在,这有帮助,但计算所有[2,3] + [5,7, 11,13, 17,19, ...]的除数总和仍然需要n <= MAXNUM时间(我没有分析,也可能是上限,或{{1无论如何,对数因子很少会产生很大差异[超出常数因子])。

你计算了不止一次的除数和(在你的例子中,你在调查时计算Ω(MAXNUM^1.5 / log MAXNUM) 12,再次调查时再计算28,再次调查56)。为了减轻这种影响,记住MAXNUM^1.5将是一个好主意。然后,您只需计算一次s(56)

现在我们已经将空间交换了一段时间,因此我们可以使用更好的算法一次计算所有s(n)的除数和,具有更好的时间复杂度(以及更小的常数因子)。我们可以直接只标记倍数,而不是尝试每个足够小的(素数)数字,从而避免留下余数的所有除法 - 这是绝大多数。

这样做的简单方法是

s(n)

时间复杂度1 <= n <= MAXNUM。你可以通过使用素数因子分解来做得更好(n复杂度),但是这种方法有点复杂,我现在不加,也许以后加入。

然后,您可以使用所有除数和的列表来查找def divisorSums(n): dsums = [0] + [1]*n for k in xrange(2, n+1): for m in xrange(k, n+1, k): dsums[m] += k return dsums O(n * log n),以及O(n * log log n)的上述实现来计算大于{{1的值的除数和} [或者您可能想要将值记住到更大的限制]。

s(n)

那不是太破旧,

n <= MAXNUM

s(n)

通过记忆MAXNUM所需的除数总和:

dsums = divisorSums(MAXNUM)
def memo_s(n):
    if n <= MAXNUM:
        return dsums[n]
    return s(n)

时间下降到

Found 414 distinct (m-k)-perfect numbers (0.10350 per cent of 400000 ) in 496 occurrences
Found 4 perfect numbers
Found 8 multiperfect numbers (including perfect numbers)
Found 7 superperfect numbers
12.709428072 seconds

答案 1 :(得分:1)

from functools import lru_cache

...

@lru_cache
def s(n):
    ...

应该让它明显加快。

[更新]哦,对不起,根据文档在3.2中添加了。但任何缓存都可以。见Is there a decorator to simply cache function return values?