改善我对PE 21的解决方案?

时间:2013-11-29 12:58:34

标签: python python-3.x

我使用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秒。我想知道是否有更好的方法。如何改进算法

3 个答案:

答案 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 factorizationsum 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发布的筛选优化。这是算法大纲。

  • 使用筛子查找所有数字的 prime 因子
  • 使用Ruler Function
  • 计算每个因子的多重性
  • 使用this formula汇总除数。

初始代码看起来像这样

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)]

到目前为止一切顺利,除了我们如何计算vp被定义为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}}