【问题标题】:Improve code that factorizes in a faster way?改进以更快方式分解的代码?
【发布时间】:2015-03-30 20:17:03
【问题描述】:

我想让这段代码更快。它返回一个长数的所有因子(质数)。如果longNumber 是特定的,显然需要几分钟才能执行。

int div = 2;
String factors = "";

while (longNumer != 1)
{
    if (longNumer % div == 0)
    {
        longNumer /= div;
        factors += div + " ";
    }
    else { div++; }
}

//longNumber = 10, gives: 2 5. 
//longNumber = 150, gives: 3 5 7.
//longNumber = 10523, gives: 17 619.

“7544222046562688368”这样的数字太长了,而且不好,你有什么建议?

【问题讨论】:

  • 对素数使用memoization,代码的性能会有提升。
  • 如果你的算法真的把3,5,7作为150的因子,我建议你在浪费时间让它更快之前把它做好。

标签: java algorithm math optimization factorization


【解决方案1】:

对于大数,您可以使用Sieve of Eratosthenes algorithm 首先找到直到 sqrt(n) 的素数,然后您必须检查这些素数是否是因数

【讨论】:

  • 这对 64 位数字没有多大帮助,2^32 下面还有很多素数需要检查,无论如何生成它们都会增加收益递减。
  • 使用 Eratosthenes 筛获得第一个 sqrt(n) 素数大约需要 sqrt(n) loglog n 操作。试除法需要 sqrt(n) 操作。您通过筛分获得了什么?
  • @DouglasZare 是的,筛子比试除法需要更多的操作。但是所讨论的操作非常不同。对于 CPU,除法比加法慢大约 10 倍,log(log(n)) 在您可以放入 long 的值范围内实际上是恒定的。 (并且所说的常数明显小于 10。)您选择的语言可能会增加足够的开销来掩盖这种性能差异。但用一种有效的语言来说,一个实施良好的筛子很可能会在任何可以放入很长时间的东西上击败试验划分。
  • @btilly:我不认为几乎优化的筛子是一个公平的比较,因为优化筛子比这个问题中提出的问题要困难得多。对于相同的限制范围,您可以通过简单地避免用可被小素数整除的复合物进行试除(使筛子有效的成分,因此更简单)来获得筛子的大部分好处。此外,您可以通过提前停止来获得收益,而无需将素数集缩小到您需要的范围之外。我看到的筛子实现并不是为了这样做而设计的,尽管它们可以被修改。
  • @DouglasZare 几年前我在处理 Project Euler 问题时,我做了一个 sieve 实现,它在 Perl 中以流的形式返回,并以明显的数量击败了相同语言的 Trial Division。而且代码写起来也不难。
【解决方案2】:

您可以改用以下步骤 -

1. 查找所有素数 primes。
2. 现在逐渐使用数组的元素 - primes 作为除数来找到因子。

【讨论】:

  • 在性能方面不是循环遍历数组很糟糕吗?
  • @HashMap 不是。这取决于数组是否有 10 个或 1m 个元素来决定性能的好坏。
  • 我会说,当且仅当它们没有存储在数组中时,您才应该寻找质数。您可以使用二分搜索进行快速查找(假设您将素数以排序方式存储在数组中)。
  • sqrt(longNumber) 仍然是一个长数字。这对 64 位数字没有多大帮助,2^32 下面还有很多素数需要检查,生成它们无论如何都会增加收益递减。
【解决方案3】:

建议埃拉托色尼筛法的答案对于您描述的数字没有多大作用。对于64 位数sqrt(2^64) = 2^32,还是很多的。

对于那些,你最好的选择是Pollard's Rho algorithm 或更复杂的整数分解方法here

代数群分解算法,其中有 Pollard 的 p − 1 算法、Williams 的 p + 1 算法和 Lenstra 椭圆曲线分解

费马分解法

欧拉分解法

特殊编号字段筛

【讨论】:

    【解决方案4】:

    在您实现比试除法更快的因子算法之一之前,一个容易纠正的错误是避免在最后一块的 sqrt 之后进行试除法。

    while (longNumber != 1) {
        if (longNumber % div == 0) {
            longNumber /= div;
            factors += div + " ";
        }
        else {
            if (div*div>longNumber) {
                if (longNumber > 1)
                    factors += longNumber + " ";
                break; // leave the while loop.  
            } 
            div++; 
        }
    }
    

    设两个最大的质因子是 P1 和 P2。在您的版本中,您执行 c P1 操作。在修改后的版本中,您执行 c Max(sqrt(P1),P2)。在 7544222046562688368 上,改进应该是 45 倍。

    另一个改进是更改了 div++ 行。您不需要通过大于 2 的偶数或可被大于 3 的 3 整除的数字进行试除法。避免这些会使计算速度加快 2 倍以上,并且通过避免测试倍数可以做得更好其他小素数。但是,您不想花时间通过小素数对 div 进行试除。相反,您可以跟踪当前和允许的余数 mod 2*3*5*7,比如说。这称为使用小素数的轮子。

    其他一些答案已经谈到使用筛子找到所有小素数,然后仅对这些小素数使用试除法。如果您只考虑一个数字,这将无济于事,因为筛选素数需要很长时间。生成直到 sqrt(n) 的素数列表大约需要 c sqrt(n) loglog n 操作,而直到 sqrt(n) 的所有内容的试除需要大约 c sqrt(n) 操作。如果您需要分解许多大数,则执行一次筛子并存储结果会有所帮助。

    【讨论】:

    • 我认为div*div > longNumber 不可能为真,因为在这种情况下,您已经找到了所有的质因数,而longNumber 已减少到 1。
    • @Anonymous:当您只剩下一个素数时,div*div 可以大于 longNumber。假设 longNumber 从 7 开始。您测试 div=2,然后 div=3 和 3*3>7,然后您知道 7 是素数,因此您不需要测试任何更高的 div 值。如果 longNumber 从 14 开始,你会找到 2 的因数,将其减少到 7,再次测试 2,然后在 3 处停止。
    【解决方案5】:

    一种分解 64 位整数的好方法,它既易于编程,又在实践中相当有效,它结合了试除法和 Pollard 的 rho 算法。这是伪代码:

    function factors(n)
        wheel := [1,2,2,4,2,4,2,4,6,2,6]
        w, f, fs := 0, 2, []
        while f*f <= n and f < 10000
            while n % f == 0
                fs, n := f :: fs, n / f
            f, w := f + wheel[w], w+1
            if w = 11 then w = 3
        if n == 1 return fs
        h, t, g, c := 1, 1, 1, 1
        while not isPrime(n)
            repeat
                h := (h*h+c) % n # the hare runs
                h := (h*h+c) % n # twice as fast
                t := (t*t+c) % n # as the tortoise
                g := gcd(t-h, n)
            while g == 1
            if isPrime(g)
                while n % g == 0
                    fs, n := g :: fs, n / g
            h, t, g, c := 1, 1, 1, c+1
        return n :: fs
    

    这使用 2,3,5 轮进行最多 10000 的试验划分,然后是 rho 算法的简单实现;它应该在几毫秒内将您的样本编号视为 7544222046562688368 = 2 * 2 * 2 * 2 * 7 * 7 * 14618561 * 658254407。改进是可能的,但这应该足以让您开始。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-11-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多