【问题标题】:What is the fastest way in Java to get the amount of factors a number hasJava中获取数字所具有的因子数量的最快方法是什么
【发布时间】:2012-04-25 17:01:53
【问题描述】:

我正在尝试用 Java 编写一个函数,该函数将返回特定数字所具有的因子数。

应考虑以下限制。

  1. 应该用 BigInteger 来完成
  2. 不允许存储以前生成的数字,因此需要更多的处理和更少的内存。(不能像this 那样使用“阿特金筛子”)
  3. 可以忽略负数。

这是我目前所拥有的,但速度非常慢。

public static int getNumberOfFactors(BigInteger number) {
    // If the number is 1
    int numberOfFactors = 1;

    if (number.compareTo(BigInteger.ONE) <= 0)  {
        return numberOfFactors;
    }

    BigInteger boundry = number.divide(new BigInteger("2"));
    BigInteger counter = new BigInteger("2");

    while (counter.compareTo(boundry) <= 0) {
        if (number.mod(counter).compareTo(BigInteger.ZERO) == 0) {
            numberOfFactors++;
        }

        counter = counter.add(BigInteger.ONE);
    }

    // For the number it self
    numberOfFactors++;

    return numberOfFactors;
}

【问题讨论】:

  • 如果有重复的因素,这也会失败,例如2*2*2 将返回 2(1 代表 2,1 代表 4)
  • @PeterLawrey 不正确 - 他正在迭代所有因素,从不进行除法。
  • 因式分解可能非常慢,以至于它被用于某些加密算法中。
  • Euler's totient function,9000是对的,不是简单的问题。
  • @PeterLawrey 这个问题从来没有说过它应该只返回素数的数量。我假设从问题的措辞方式来看,8 的因子数应该返回 4 (1, 2, 4, 8)

标签: java algorithm math


【解决方案1】:

我可以提出更快的解决方案,尽管我感觉它还不够快。您的解决方案在 O(n) 中运行,而我的解决方案将在 O(sqrt(n)) 中运行。

我将使用这样一个事实,如果 n = xi1p1 * xi2p2 * xi3p3 * ... xikpkn 的素数分解(即 xij 都是不同的素数),那么 n 总共有 (p1 + 1) * (p2 + 1) * ... * (pk + 1) 个因数。 p>

现在解决方法来了:

BigInteger x = new BigInteger("2");
long totalFactors = 1;
while (x.multiply(x).compareTo(number) <= 0) {
    int power = 0;
    while (number.mod(x).equals(BigInteger.ZERO)) {
        power++;
        number = number.divide(x);
    }
    totalFactors *= (power + 1);
    x = x.add(BigInteger.ONE);
}
if (!number.equals(BigInteger.ONE)) {
    totalFactors *= 2;
}
System.out.println("The total number of factors is: " + totalFactors);

如果您单独考虑 2 的情况,然后让 x 的步长等于 2 而不是 1(仅迭代奇数),则可以进一步优化。

另请注意,在我的代码中我修改了number,您可能会发现保留number 并使用另一个等于number 的变量来迭代更合适。

我认为对于不大于 264 的数字,此代码将运行得相当快。

编辑为了完整起见,我将在答案中添加合理快速的措施。从下面的 cmets 中可以看出,我对 Betlista 提出的测试用例 1000000072 的建议算法的性能进行了几次测量:

  • 如果按原样使用算法,在我的机器上花费的时间是 57 秒。
  • 如果我只考虑奇数,时间会减少到 28 秒
  • 如果我将检查 while 的结束条件更改为与我使用二分搜索发现的 number 的平方根进行比较,则所需时间减少到 22 秒。
  • 最后,当我尝试用long 切换所有BigIntegers 时,时间减少到2 秒。由于所提出的算法对于大于long 范围的number 运行速度不够快,因此将实现切换到long 可能是有意义的

【讨论】:

  • 相当快?该代码在您的计算机上为100000007^2 运行了多长时间?
  • @Betlista 使用当前解决方案 57 秒,而我只迭代奇数 - 28 秒。当我为平方根添加二进制搜索(这样我避免乘法)时,我将它减少到 22。可能你是对的,在极端情况下它并没有我声称的那么快,但我想不出更好的解决方案。
  • @Betlista btw 用 long 替换所有大整数将时间减少到 3 秒。很遗憾,BigInteger 在这里受到了很大的限制。
  • 为了稍微加快速度,第一次添加一个,然后添加两个。即 2,3,5,7,.... 这将运行时间减半。
  • 看起来这将返回 4 换 9...您不是将平方根计算为一个因素吗?
【解决方案2】:

一些改进:

  1. 您只需要检查到 sqrt(n),而不是 n/2。这使得你的算法 O(sqrt(n)) 而不是 O(n)。
  2. 你只需要在检查2之后检查奇数,这样速度应该会提高一倍。
  3. 虽然你不能使用以前的数字,但你可以构造一个已知素数和少量存储的筛子:2、3 是素数,所以只需要检查(例如)11、13、17、19、23 和不是 12、14、15、16、18。因此,您可以存储 3 的增量模式:[+2,+4],每 6 重复一次:
var deltas = [2,4];
var period = 6;
var val = 3;
var i=0;
while(val<sqrt(n)) {
    var idx = i%deltas.length; // i modulo num deltas
    val += deltas[idx];
    count += isFactor(n,val);
    // if reached end of deltas, add period
    if(idx == deltas.length-1) {
        val += period - deltas[idx];
    }
    ++i;
}

一旦你得到这个结果,你显然必须加上 2 和/或 3 如果它们是因子。

当我在学校无聊的时候,我把上面的模式想出来了。您可以计算出任何素数列表的模式,但有一个收益递减规律;您添加的每个素数都会增加周期,并大大增加增量列表的长度。因此,对于一长串已知素数,您会得到一个非常长的增量列表,并且速度只有很小的提高。不过,一定要测试一下加速是否值得。

因为它只是剔除已知分数的一部分(使用所示的 2 值增量的 2/3),所以仍然是 O(sqrt(n))。

将筛子与 sqrt 界限相结合,您应该得到 4/(3*sqrt(n)) 的加速。

[编辑:将句点添加到最后一个值,而不是句点-lastdelta。谢谢@Betlista]

【讨论】:

  • 我认为您的代码至少在 n = 9 或 n = 49 时不能正常工作。关于句点的想法可能是“跳过以 0、2、4、5、6 之一结尾的数字, 8", 所以增量是 4, 2, 2, 2 从 3 开始 - 检查 10 个数字中的 4 个...
  • 9 可被 3 整除,因此应在循环外检查 3。 49 可以被 7 整除,并且在第一次迭代中选择 7 作为 3 的 +4 增量。在第 8 次迭代中,+4 增量也将击中 49。
  • deltas[0] 为 2,因此您正在检查 5、9、11、15...(跳过质数 7 和 13)如果存在 val &lt; sqrt(n) 条件,如何命中 49?缺少 count 的初始化,但我猜是 2
  • @Betlista:感谢您检查,我没有在列表末尾添加正确的内容。我应该添加 6-4(最后的增量),而不是添加 6。这样你得到 5,7, 11,13, 17,19
【解决方案3】:

Boris Strandjev 提出的最快解决方案存在一些问题,即在 Java 中生成大量输出。 它是Java中查找非常大整数的除数的最快算法。

这是我将成功运行的代码:

import java.math.BigInteger;
import java.util.Scanner;

class ProductDivisors {

    public static BigInteger modulo=new BigInteger("1000000007");
    public static BigInteger solve=new BigInteger("1");
    public static BigInteger two=new BigInteger("2");
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Scanner sc=new Scanner(System.in);
        int N=sc.nextInt();
        BigInteger prod=new BigInteger("1");
        while(N-->0){
            prod=sc.nextBigInteger();
            solve=solve.multiply(prod);
        }
        BigInteger x = new BigInteger("2");
        BigInteger total = new BigInteger("0");
        BigInteger totalFactors =new BigInteger("1");
        while (x.multiply(x).compareTo(solve) <= 0) {
            int power = 0;
            while (solve.mod(x).equals(BigInteger.ZERO)) {
                power++;
                solve = solve.divide(x);
            }
            total = new BigInteger(""+(power + 1));
            totalFactors=totalFactors.multiply(total);
            x = x.add(BigInteger.ONE);
        }
        if (!(solve.equals(BigInteger.ONE))) {
            totalFactors =totalFactors.multiply(two);
        }
        totalFactors=totalFactors.mod(modulo);
        System.out.println(totalFactors);
    }

}

此代码通常将数字数组作为输入,因此相乘会产生大数字。 并且,计算除数的主要代码(包括这里的除数)并给出输出。

我希望这是一种有效的方法,如果需要,我会提出任何错误或补充。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-11
    • 1970-01-01
    • 2013-09-30
    • 1970-01-01
    • 2013-08-22
    • 1970-01-01
    相关资源
    最近更新 更多