【问题标题】:Need help optimizing solution for Project Euler problem #12需要帮助优化 Project Euler 问题的解决方案 #12
【发布时间】:2011-03-23 21:17:41
【问题描述】:

我再次从 Project Euler 挑战中获得乐趣,我注意到我的 12 号解决方案是我最慢的解决方案之一,每次运行时 ~593.275 ms。这是我在~1254.593 ms 每次运行时的第 10 号解决方案的第二个解决方案。我的所有其他答案在3 ms 下运行的时间都少于1 ms

Problem 12 的我的 Java 解决方案:

main():

int index = 1;
long currTriangleNum = 1;

while (numDivisors(currTriangleNum) <= 500) {
    index++;
    currTriangleNum += index;
}

System.out.println(currTriangleNum);

numDivisors():

public static int numDivisors(long num) {  
    int numTotal = 0;

    if (num > 1)
        if (num % 2 == 0) {
            for (long i = 1; i * i <= num; i++)
                if (num % i == 0)
                    numTotal+=2;
        } else {
            // halves the time for odd numbers
            for (long i = 1; i * i <= num; i+=2)
                if (num % i == 0)
                    numTotal+=2;
    }
    else if (num == 0)
        return 0;
    else if (num == 1)
        return 1;
    else (num < 0)
        return numDivisors(num *= -1);

    return numTotal;
 }

.

翻遍解决方案论坛,有人发现these formulas(n = (p^a)(q^b)(r^c)... & d(n) = (a+1)(b +1)(c+1)...) 为他们工作,但我个人不认为它会更快;手动速度可能更快,但不是在程序中。

.

基本思路如下:

我们要计算48中的除数个数。通过查看下面的因子树,我们可以得出结论48 = (2^4)(3^1) [n = (p^a)(q^b)(r^c)... ].

  48
 /  \
2   24
   /  \
  2   12
     /  \
    2   06
       /  \
      2    3 

知道了这一点,我们构造公式d(48) = (4+1)(1+1) [d(n) = (a+1)(b+1)(c+1)...] 来确定 48 有 10 个因数。

d(n)  = (a+1)(b+1)(c+1)...
d(48) = (4+1)(1+1)
d(48) = (5)(2)
d(48) = 10

.

如何优化我的代码?这些公式是最好的解决方案吗?我觉得找到所有主要因素,然后实施公式会比我已有的程序花费更长的时间。

非常感谢,

贾斯蒂安

编辑:

在任何人开始发布链接之前,我已经在 SO 中查看了类似的问题,但没有任何运气 - 我只是想不出他们的方法的实现会比我已有的方法运行得更快。

EDIT2:

我在 Eratosthenes 筛上的第二次尝试(针对第 10 题):

int p = 3, n = 2000000;
long total = 0;
boolean[] sieve = new boolean[n];

for (int i = 3; i < n; i += 2)
    sieve[i] = true;

sieve[2] = true;

while (p * p < n) {
    for (int i = p; i < n; i++)
        if (sieve[i] && (i % p) == 0)
            sieve[i] = false;
    p++;

    while (!sieve[p])
        p++;
}

for (int i = 0; i < n; i++)
    if (sieve[i])
        total += i;

System.out.println(total);

~985.399 ms 运行 - 没有比其他方法快太多,但尚未优化。但是,它可以工作。

【问题讨论】:

  • 我想我帮不上什么忙……我天真的、蛮力的 PowerShell 解决方案几乎打破了一个小时! :) 虽然这是在 PowerShell v1 中运行的,其中函数调用非常慢。
  • 埃拉托色尼筛算法不应使用除法。见rosettacode.org/wiki/Sieve_of_Eratosthenes#Java
  • @J.F. Sebastian:我尝试过这种方法,但结果证明它与我在埃拉托色尼筛法的第一次失败尝试太相似了。我没有等到它,但我估计它需要 10 分钟,有 2000000 个值,考虑到通过 LinkedList 运行了多少次,这是有道理的。

标签: java optimization primes mathematical-optimization


【解决方案1】:

使用底层数学结构,这将极大地改变程序的运行时间。顺便说一句,这也适用于问题 10;如果你不能在几毫秒内完成,你就使用了一个非常低效的算法。事实上,我建议你先解决问题 10,因为问题 12 建立在它之上。

我将为下面的问题 12 提供一个更好的算法,但首先,这里有一个可以显着加快程序速度的观察结果。如果两个数 x 和 y 互质(即它们没有除 1 之外的公约数),则 d(x·y) = d(x)·d(y)。特别地,对于三角形数,d(n·(n+1)) = d(n)·d(n+1)。因此,不是迭代三角形数 n·(n+1),而是迭代 n:这将显着减小传递给 d(n) 的参数的大小。

如果您进行该优化,您会注意到您连续计算 d(n) 两次(一次为 d((n-1)+1),一次为 d(n))。这表明缓存 d 的结果是一个好主意。下面的算法可以做到这一点,但也计算 d 自下而上而不是自上而下,这更有效,因为乘法比因式分解要快得多。


Problem 10 可以通过sieve of Eratosthenes 的简单应用来解决。填充大小为 2000000 的布尔数组(即位向量),如果 i 是素数,则使用 sieve[i]==true;然后将sieve[i]==true的数字相加。

Problem 12 可以通过 Eratosthenes 筛的推广来解决。不要将sieve[i] 设为一个布尔值来表示i 是否为素数,而是将其设为一个数字,表示它是非素数的方式数,即i 的除数数。修改 Eratosthenes 的基本筛子很容易做到这一点:与其将 sieve[x*y] 设置为 false,不如将​​其加 1。

随后的几个项目欧拉问题受益于类似的方法。

您可能遇到的一个问题是,在问题 12 中,不清楚何时停止计算筛子。您可以采取两种方式:
1. 按需按块计算筛子,这本身就是一个有价值的编程练习(这将需要比第二种方法更复杂的代码)
2. 或者从高估一个界限开始:找到一个有超过 500 个除数的三角形数,你知道你会在那个数字之前或那个数字处停下来。

如果您意识到您只需要关心奇数,您可以获得更多时间,因为如果 n 是奇数,则 d(2^k·n) = (k+1)·d(n),并且找到 k 和仅给定 (2^k·n) 的 n 在二进制计算机上速度很快。我将把优化的细节留作练习。

【讨论】:

  • 我将研究第一个观察结果,但我已经用 Eratosthenes 的筛子尝试了问题 10,但运气不佳。事实上,该实现在我的机器上运行需要 10 分钟。当然,这可能是我的编程错误,但我按照维基百科文章中的说明进行了 T.+1 的整体精彩(如吉姆所说,非剧透)解释。你肯定没看错我的问题。
  • 下次有时间我会尝试这些算法。
  • @Justian:我建议你弄清楚你的筛子实现做错了什么,这是一个重要的垫脚石(无论如何解决项目欧拉问题)。健全性检查:您是否注意到维基百科文章的循环重复直到 p²>n,而不是 p>n?
  • 我的原始实现看起来与这里的类似:rosettacode.org/wiki/Sieve_of_Eratosthenes#Java。我的新解决方案比我的第一次尝试运行得快得多,并且比我的旧解决方案快一点。我只需要优化我的新解决方案,这似乎更有可能。
【解决方案2】:

您是否考虑过分解素数并跟踪素数以便不必重新计算它们?

【讨论】:

  • @Thorbjoern:我考虑过编译一个.txt文件,因为它比重新计算要快得多,但我觉得这好像是在欺骗系统。
  • 您可以按需计算并随时保存结果。
【解决方案3】:

我之前做过这个,所以我不记得所有的优化,这里有一些:

  1. 对 sum(1...n) 使用求和公式
  2. 用方法查找素因数 在问题 7 和问题中描述 10
  3. 根据素数分解确定 n 有多少个除数*

*如果您不知道“规则”,请将此视为概率问题。例如,您有四种口味可以添加到咖啡中,您有多少种选择?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-23
    • 1970-01-01
    • 2022-11-29
    • 1970-01-01
    • 2010-09-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多