我将如何改进/使这个运行更快? [关闭]
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
找到除数。对于带有素数p
的n = 2*p
形式的数字也不好,然后循环到n/2
或n = 3*p
(n/3
,除非p = 2
)等。
根据素数定理,不超过x
的素数的个数是渐近的x/log x
(其中log
是自然对数),你有一个下界
Ω(MAXNUM² / log MAXNUM)
仅用于计算素数的除数和。这不好。
由于您已经成对考虑了 n
的除数 d
和 n/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 <= n <= MAXNUM
是Θ(MAXNUM^1.5)
。这已经是相当大的进步了。 (当然,您必须计算迭代除数和,对于某些n <= MAXNUM
,s(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)
但是否使用它或将p
的k+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 <= k
,而g
必须除以m
。相反,对于每个0 <= a <= k
和每个g
除m
,p^a * g
是n
的除数。所以我们可以列出n
的除数(其中1 = 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):
[上限并不重要,因为如果它大于 n**0.5
,则永远不会达到它] 和得到一个不太慢的因式分解。 (但它可以很容易地通过避免大于 2 的试验除数来加快速度,即使用列表 [2] + [3,5,7...]
- 最好作为生成器 - 对于除数,甚至通过跳过 3 的倍数(3 除外), [2,3] + [5,7, 11,13, 17,19, ...]
如果你想要更多的小素数。)
现在,这有帮助,但计算所有 n <= MAXNUM
的除数总和仍然需要 Ω(MAXNUM^1.5 / log MAXNUM)
时间(我还没有分析,这也可能是一个上限,或者 MAXNUM^1.5
仍然可能是一个下限,无论如何,对数因子很少会产生很大的差异[除了一个常数因子])。
并且您不止一次地计算了很多除数(在您的示例中,您在调查 12 时计算 s(56)
,在调查 28 时再次计算,在调查 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)
complexity),但这种方法有点复杂,我现在不添加它,也许以后再添加。
然后您可以使用所有除数和的列表来查找s(n)
if 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
为
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 > 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。无论如何感谢您的建议。以上是关于我将如何改进/使这个运行更快? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章
短路是不是使程序的执行更快,并且分析在条件语句中首先放置的语句是不是值得? [关闭]