我使用python使用以下方法解决了项目euler problem 21。
ans = 0;
for i in range(1,10000):
s = 0;
su = 0;
for j in range(1,i):
if(i%j==0):
s = s + j;
if(i!=s):
for k in range(1,s):
if(s%k==0):
su = su+k;
if(su == i):
ans=ans+i;
print(ans);
花了大约35秒。我想知道是否有更好的方法。如何改进算法
答案 0 :(得分:2)
要获得更快的速度,您需要更好的算法。让我们从一个函数开始,该函数使用素轮找到 n 的因子,这比天真的试验分割更快,并且当 n 很小时适合:
def factors(n):
wheel = [1,2,2,4,2,4,2,4,6,2,6]
f, fs, next = 2, [], 0
while f*f <= n:
while n % f == 0:
fs.append(f)
n /= f
f += wheel[next]
next += 1
if next == 11: next = 4
if n > 1: fs.append(n)
return fs
然后我们使用 n 的因子来计算 n 的除数之和,而不计算除数本身;请注意,总和包括 n 本身:
def d(n):
mult, sum, prev = 2, 1, 0
for fact in factors(n):
if fact == prev:
mult += 1
elif prev > 0:
sum *= (prev**mult - 1) // (prev - 1)
mult = 2
prev = fact
return sum * (prev**mult - 1) // (prev - 1)
现在只需计算所有除数的总和小于限制,将它们保存在数组中。如果 n 的除数之和小于 n ,我们检查先前计算的除数之和的除数之和,并报告相等的除数:
def euler21a(limit):
sum, sumDiv = 0, [0] * limit
for n in range(2,limit):
sumDiv[n] = d(n) - n
if sumDiv[n] < n and sumDiv[sumDiv[n]] == n:
print sumDiv[n], n
sum += sumDiv[n] + n
return sum
现在我们可以运行它了:
>>> euler21a(10000)
220 284
1184 1210
2620 2924
5020 5564
6232 6368
31626
我没有时间,但显然只需不到一秒钟。有五个友好对不到一万,其中220和284在这个例子中使用。
更快的方法是使用类似于Eratosthenes筛子的筛子来积累一系列数字的除数:
def sieve(n):
sumDiv = [0] * n
for i in range(1,n):
for j in range(i,n,i):
sumDiv[j] += i
return sumDiv
计算 sumDiv 数组,可以在解决方案中使用:
def euler21b(limit):
sum, sumDiv = 0, sieve(limit)
for n in range(2,limit):
sumDiv[n] -= n
if sumDiv[n] < n and sumDiv[sumDiv[n]] == n:
print sumDiv[n], n
sum += sumDiv[n] + n
return sum
这是解决方案,当我的手指按下Enter按钮时立即出现:
>>> euler21b(10000)
220 284
1184 1210
2620 2924
5020 5564
6232 6368
31626
如果您想了解更多信息,请在我的博客中解释wheel factorization,sum of divisors计算和sieving method。
答案 1 :(得分:1)
您可以通过注意不需要循环遍历小于i
的所有可能数字来改进算法来找到友好的对。
请注意,找到友好对的问题可以重新拟定为以下两个条件:
sum(factors(sum(factors(i)))) = i
sum(factors(i)) != i
我不认为以下是最快的,但这是一个超过35秒的改进:
def factorize(n):
factors = [1]
for i in xrange(2, int(n**0.5)+1):
if n%i==0:
factors.append(i)
factors.append(n/i)
factors.append(n)
return factors
def amicable(N=10000):
s = 0
for i in range(1, N):
a = sum(factorize(i)) - i
b = sum(factorize(a)) - a
if b==i and i!=a:
s+=i
print 'Found pair: ', i, a
print 'Sum is: ', s
注意:我们需要从i
中减去sum(factorize(i)) - i
,因为我的因子化函数还包括因子列表中的数字本身,而友好数字只需要“适当的除数”
答案 2 :(得分:1)
其他答案效果很好,但这是另一种算法,它触及了您可能会在以后的PE系列中找到的主题。这是user448810发布的筛选优化。这是算法大纲。
初始代码看起来像这样
UPPERLIM = 10001
is_prime = [True] * UPPERLIM
sum_divs = [1] * UPPERLIM
for p in itertools.chain([2], range(3, UPPERLIM, 2)):
# Iterate over possible primes, ie. 2 + all odd numbers
if not is_prime[p]:
continue
for m in range(p, UPPERLIM, p):
# Mark multiples of p as composite
is_prime[m] = False
v = times_p_divides_m(p, m)
sum_divs[m] *= (p ** (v + 1) - 1) // (p - 1)
# We marked p as composite in the loop above
# remark as prime
is_prime[p] = True
sum_divisors = [m-n for m, n in enumerate(sum_divisors)]
到目前为止一切顺利,除了我们如何计算v
,p
被定义为m
分成def time_p_divides_m(p, m):
result = 0
while m % p == 0:
m = m // p
result += 1
return result
的次数。天真的解决方案是写:
p 2
m 2 4 6 8 10 12 14 16 18 20 ...
v 1 2 1 3 1 2 1 4 1 2 ...
但是极大会增加我们程序的时间复杂度。相反,我们利用了这样一个事实:我们正在计算 p的所有倍数。
真的,我想要p,2p,3p,4p中有多少p的幂...有时也称为广义ruler function。例如,如果p = 2,我们想要找到下面的v。
def ruler(p, n):
# Generates the values of the ruler function for a prime p
# n is the value of ruler function at which to stop
# ie. ruler(2, 4) will generate [1, 2, 1, 3, 1, 2, 1] and
# stop because the next value is 4.
if n == 1:
# All rulers are "seeded" with 1 repeated p-1 times
yield from (1 for m in range(p - 1))
raise StopIteration
# the next "chunk" of a ruler is always
# chunk(n-1) n .. repeat p-1 times .. chunk(n-1)
for i in range(p - 1):
yield from ruler(p, n - 1)
yield n
yield from ruler(p, n - 1)
值得庆幸的是,生成此序列比计算分割数字的次数要容易得多。所以我们定义一个标尺生成器如下
UPPERLIM = 10001
is_prime = [True] * UPPERLIM
sum_divs = [1] * UPPERLIM
def ruler(p, n):
if n == 1:
yield from (1 for m in range(p-1))
raise StopIteration
for i in range(p-1):
yield from ruler(p, n-1)
yield n
yield from ruler(p, n-1)
for p in itertools.chain([2], range(3, UPPERLIM, 2)):
if not is_prime[p]:
continue
multiples = range(p, UPPERLIM, p)
powers = ruler(p, int(math.log(UPPERLIM, p)+1))
for m, v in zip(multiples, powers):
is_prime[m] = False
sum_divs[m] *= (p ** (v + 1) - 1) // (p - 1)
is_prime[p] = True
sum_divisors = [(n-m) for m, n in enumerate(sum_divisors)]
result = set()
for n in range(2, UPPERLIM):
b = sum_divs[n]
if b < UPPERLIM and b != n and n == sum_divisors[b]:
print('Amicable pair\t{0}\t{1}'.format(n, b))
result.add(n)
print('Total: {0}'.format(sum(result)))
所以最终的代码变成了:
Amicable pair 220 284
Amicable pair 284 220
Amicable pair 1184 1210
Amicable pair 1210 1184
Amicable pair 2620 2924
Amicable pair 2924 2620
Amicable pair 5020 5564
Amicable pair 5564 5020
Amicable pair 6232 6368
Amicable pair 6368 6232
Total: 31626
给出输出
{{1}}