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

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

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

  • Это должно быть сделано с помощью BigInteger
  • Сохранение предыдущих сгенерированных номеров недопустимо, поэтому больше обработки и меньше памяти. (Вы не можете использовать "Сито Аткина", как в this)
  • Отрицательные числа можно игнорировать.

Это то, что у меня есть до сих пор, но оно очень медленное.

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;
}

Ответ 1

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

Я буду использовать тот факт, что если n = x i1 p1 * x i2 p2 * x i3 p3 *... x ik 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, для повторения.

Я полагаю, что этот код будет работать достаточно быстро для чисел не более 2 64.

РЕДАКТИРОВАТЬ Я добавлю меры разумно быстро к ответу для полноты. Как видно из комментариев ниже, я сделал несколько измерений эффективности предложенного алгоритма для тестового примера 100000007 2 который был предложен Betlista:

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

Ответ 2

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

  • Вам нужно только проверить sqrt (n), а не n/2. Это делает ваш алгоритм O (sqrt (n)) вместо O (n).
  • Вам нужно только проверить нечетные числа после проверки 2, что должно удвоить скорость.
  • Хотя вы не можете использовать предыдущие номера, вы можете построить сито с известными простыми числами и небольшим хранилищем: 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: добавление периода к последнему значению, а не period-lastdelta. Спасибо @Betlista]

Ответ 3

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

}

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

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