Какой самый быстрый способ в 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);
}
}
Этот код, как правило, принимает массив числа в качестве входных данных и тем самым умножает, что приведет к большим числам.
И после этого основной код для подсчета числа делителей (включая число принимается как дивизор здесь) выполняется и задается как результат.
Я надеюсь, что это эффективный способ и предложить любые ошибки или добавление, если это необходимо.