我将如何改进/使这个运行更快? [关闭]

Posted

技术标签:

【中文标题】我将如何改进/使这个运行更快? [关闭]【英文标题】:How would I go about improving/making this run faster? [closed] 【发布时间】:2013-06-21 04:42:36 【问题描述】:

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

令 n 为大于 1 的整数,s(n) 为除数之和 n.例如,

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

这是我的代码:

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语言难度有多大?

【问题讨论】:

真的慢吗?这只是一个建议,但作为初学者,你真的必须找到一种方法来“更快”地运行你的代码吗?您首先应该关注拥有更易读和可维护的代码。嗯,事实上,这是我曾经给(更多)更有经验的程序员的建议...... 如果你懂 C,那转换起来会很容易...如果已经是 C 代码,你只需要花括号和分号 :) + 为了提高性能,有时初学者会专注于无关紧要的东西。 Here is the method 我用。它会告诉你哪些代码值得关注。 我认为提高性能的最大关键是最小化除法/模运算。与您在此处采用的方法不同的方法是找到一个数字的素因数分解,然后重新组合这些因数来计算总和。记住因式分解,并注意到 N=p*x 的因数(其中 p 是素数)是 x 的因数的组合列表,并且这些因数中的每一个乘以 p 都是好主意(例如,6 的因数是 1 , 2, 3;12=2*6 的因数是 1, 2, 3 和 2, 4, 6)。 也许你可以加入一些记忆 【参考方案1】:

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

如果不将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找到除数。对于带有素数pn = 2*p 形式的数字也不好,然后循环到n/2n = 3*pn/3,除非p = 2)等。

根据素数定理,不超过x 的素数的个数是渐近的x/log x(其中log 是自然对数),你有一个下界

Ω(MAXNUM² / log MAXNUM)

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

由于您已经成对考虑了 n 的除数 dn/d,请注意两者中较小的一个(当 n 暂时为正方形时,忽略大小写 d = n/d)较小比n 的平方根,所以一旦测试除数达到平方根,你就知道你已经找到并添加了所有除数,你就完成了。任何进一步的循环都是徒劳的浪费工作。

让我们考虑

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 &lt;= n &lt;= MAXNUMΘ(MAXNUM^1.5)。这已经是相当大的进步了。 (当然,您必须计算迭代除数和,对于某些n &lt;= MAXNUMs(n) 可以大于MAXNUM,因此您无法从中推断出整个算法的复杂性界限O(MAXM * MAXNUM^1.5)。但是s(n) 不可能大很多,所以复杂度也不会差很多。)

但我们仍然可以通过使用 twalberg suggested 来改进这一点,使用 n 的素数分解来计算除数。

首先,如果n = p^k是素幂,则n的除数是1, p, p², ..., p^k,除数和很容易计算(几何和的封闭公式是

(p^(k+1) - 1) / (p - 1)

但是否使用它或将pk+1 幂除以n 并不重要)。

接下来,如果n = p^k * m 带有一个素数p 和一个m,使得p 不能除以m,那么

s(n) = s(p^k) * s(m)

查看分解的一种简单方法是将n 的每个除数d 写成d = p^a * g 的形式,其中p 不除g。那么p^a必须除以p^k,即a &lt;= k,而g必须除以m。相反,对于每个0 &lt;= a &lt;= k 和每个gmp^a * gn 的除数。所以我们可以列出n 的除数(其中1 = g_1 &lt; g_2 &lt; ... &lt; g_r = mm 的除数)

 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): [上限并不重要,因为如果它大于 n**0.5,则永远不会达到它] 和得到一个不太慢的因式分解。 (但它可以很容易地通过避免大于 2 的试验除数来加快速度,即使用列表 [2] + [3,5,7...] - 最好作为生成器 - 对于除数,甚至通过跳过 3 的倍数(3 除外), [2,3] + [5,7, 11,13, 17,19, ...] 如果你想要更多的小素数。)

现在,这有帮助,但计算所有 n &lt;= MAXNUM 的除数总和仍然需要 Ω(MAXNUM^1.5 / log MAXNUM) 时间(我还没有分析,这也可能是一个上限,或者 MAXNUM^1.5 仍然可能是一个下限,无论如何,对数因子很少会产生很大的差异[除了一个常数因子])。

并且您不止一次地计算了很多除数(在您的示例中,您在调查 12 时计算 s(56),在调查 28 时再次计算,在调查 56 时再次计算)。为了减轻这种影响,记住s(n) 是个好主意。然后你只需要计算每个s(n) 一次。

现在我们已经用空间换取了时间,因此我们可以使用更好的算法一次性计算所有 1 &lt;= n &lt;= 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)complexity),但这种方法有点复杂,我现在不添加它,也许以后再添加。

然后您可以使用所有除数和的列表来查找s(n) if n &lt;= 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

import time
start_time = time.time()


def s(n):
    tsum = 1
    for p in xrange(2,n):
        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
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

MAXM = 6
MAXNUM = 400000

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

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 = memo_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"

通过记住n &gt; MAXNUM 所需的除数:

d = 
for i in xrange(1, MAXNUM+1):
    d[i] = dsums[i]
def memo_s(n):
    if n in d:
        return d[n]
    else:
        t = s(n)
        d[n] = t
        return t

时间到了

3.33684396744 seconds

【讨论】:

Mind = Blown 非常感谢。【参考方案2】:
from functools import lru_cache

...

@lru_cache
def s(n):
    ...

应该会显着加快速度。

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

【讨论】:

lru_cache 无法导入,不幸的是,我猜是因为我使用的是 Python 2.7。无论如何感谢您的建议。

以上是关于我将如何改进/使这个运行更快? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

短路是不是使程序的执行更快,并且分析在条件语句中首先放置的语句是不是值得? [关闭]

比 JODCONVERTER 更快 [关闭]

如何重写这个嵌套的 SQL 查询以使其更快? [关闭]

如何使批处理文件仅在应用程序关闭后运行

如何使程序仅在使用批处理或 powershell 有 Internet 连接时运行? [关闭]

我怎样才能提高这个网站的性能? [关闭]