Какой самый быстрый способ в Java, чтобы получить сумму делителей числа

Я пытаюсь написать функцию на Java, которая вернет количество факторов конкретного числа.

необходимо учитывать следующие ограничения.

  1. это должно быть сделано с BigInteger
  2. хранить предыдущие произведенные номера не позволены, таким образом больше обрабатывать и меньше памяти.(Вы не можете использовать "сито Аткина", как в этой)
  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;
}

3 ответов


я могу предложить более быстрое решение, хотя у меня есть чувство, что оно еще не будет достаточно быстрым. Ваше решение выполняется в O(n) и мой будет работать в O(sqrt(n)).

Я собираюсь использовать тот факт, что если n = xi1p1 * xИ2p2 * xi3p3 * ... xik pk премьер-факторизации n (т. е. xяj все различные простые числа), то n имеет (p1 + 1) * (p2 + 1)*... * (pk + 1) факторы в общей сложности.

теперь вот решение:

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.

редактировать я добавлю меры разумно быстро к ответу для полноты. Как видно из приведенных ниже комментариев, я сделал несколько замеров по производительности предложенного алгоритма для тестового случая 1000000072, который был предложен Betlista:

  • если алгоритм используется как время на моей машине-57 секунд.
  • если я считаю только нечетные числа, время сокращается до 28 секунд
  • если я изменю чек для конечного состояния while для сравнения с квадратным корнем number что я найти бинпоиском время снижает до 22 секунды.
  • наконец, когда я попытался переключить все BigIntegerС long время было сокращено до 2 секунд. Так как предложенный алгоритм не будет работать достаточно быстро number больше, чем диапазон long возможно, имеет смысл переключиться на реализацию long

некоторые улучшения:

  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/3rds с использованием показанной дельты 2-значения), это stil O(sqrt (n)).

объединяя сито с привязкой sqrt, вы должны получить ускорение 4 / (3*sqrt (n)).

[Edit: добавлялся период к последнему значению, а не период-lastdelta. Спасибо @Betlista]


самое быстрое решение, предложенное Борисом Странджевым, имеет некоторую проблему, генерируя вывод больших чисел в 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);
    }

}

этот код обычно принимает массив чисел в качестве входных данных и тем самым умножает, что приведет к большим числам. И, после этого берется основной код для подсчета числа делителей (включая число как делитель здесь) делается и дается вывод.

Я надеюсь, что это эффективный способ и предложить любые ошибки или добавление, если это необходимо.