Project Euler # 3 в Java занимает вечность

Проблема № 3 в Project Euler:

Простые множители 13195: 5, 7, 13 и 29.

Какой самый большой простой делитель числа 600851475143?

Мое решение займет вечность. Я думаю, что получил правильную реализацию; однако при тестировании с большим числом я не смог увидеть результаты. Это бежит вечно. Интересно, что-то не так с моим алгоритмом:

public class LargestPrimeFactor3 {

    public static void main(String[] args) {
        long start, end, totalTime;
        long num = 600851475143L;
        long pFactor = 0;

        start = System.currentTimeMillis();

        for(int i = 2; i < num; i++) {
            if(isPrime(i)) {                
                if(num % i == 0) {
                    pFactor = i;                        
                }
            }
        }

        end = System.currentTimeMillis();
        totalTime = end - start;
        System.out.println(pFactor + " Time: "+totalTime);
    }

    static boolean isPrime(long n) {

        for(int i = 2; i < n; i++) {
            if(n % i == 0) {
                return false;
            }
        }        
        return true;
    }     
}

person miatech    schedule 19.08.2012    source источник
comment
Я бы начал с небольшой оптимизации вашего isPrime цикла. Просто повторяйте до i > sqrt(n).   -  person Blender    schedule 19.08.2012
comment
Чтобы добавить в Blender, вы также можете просто проверить, действительно ли n% 2 == true перед циклом for .. и запустить цикл for с i = 3 и выполнить итерацию по 2 (i + = 2). Сито было бы еще быстрее.   -  person Inisheer    schedule 19.08.2012
comment
А если вы используете Java, вы также можете использовать _ 1_ из класса BigInteger вместо for(int i = 2; i < num; i++) в вашем main методе.   -  person fardjad    schedule 19.08.2012
comment
Если вы действительно хотите, чтобы он работал быстро, попробуйте C / C ++. Для меня он работал менее чем за секунду и без оптимизаций, предложенных ниже.   -  person MartyE    schedule 19.08.2012
comment
Если вы планируете продолжить работу с проектом euler, возможно, вы захотите один раз вычислить большой список простых чисел (например, все ‹10 ^ 9) и повторно использовать его в последующих задачах.   -  person BeniBela    schedule 19.08.2012
comment
Кстати, проект euler - это оптимизация, а не просто грубая сила. есть много вопросов, которые невозможно решить в любое время с помощью простого решения (на любом языке). вам нужно выяснить, какие логические пути можно сократить. Кроме того, BigInteger / BigDecimal медленные. Я использовал библиотеку Apfloat в качестве замены. (я решил около 50 проблем, прежде чем двинулся дальше).   -  person jtahlborn    schedule 21.08.2012
comment
принятый код ответа неверен и по счастливой случайности дает правильный результат для данного ввода.   -  person Will Ness    schedule 21.08.2012


Ответы (9)


Хотя не на Java, я думаю, вы, вероятно, сможете разобрать следующее. По сути, необходимо сократить количество итераций, проверяя только нечетные делители и извлечение квадратного корня из числа. Вот метод грубой силы, который дает мгновенный результат на C #.

static bool OddIsPrime (long oddvalue)  // test an odd >= 3 
{
    // Only test odd divisors.
    for (long i = 3; i <= Math.Sqrt(oddvalue); i += 2)
    {
        if (value % i == 0)
            return false;
    }
    return true;
}

static void Main(string[] args)
{
    long max = 600851475143;   // an odd value
    long maxFactor = 0;

    // Only test odd divisors of MAX. Limit search to Square Root of MAX.
    for (long i = 3; i <= Math.Sqrt(max); i += 2)
    {
        if (max % i == 0)
        {
            if (OddIsPrime(i))  // i is odd
            {
                maxFactor = i;
            }
        }
    }
    Console.WriteLine(maxFactor.ToString());
    Console.ReadLine();
}
person Inisheer    schedule 19.08.2012
comment
Кроме того, я думаю, что компилятор может каждый раз пересчитывать Math.Sqrt(value). Возможно, лучше всего будет использовать это как переменную класса, которая будет устанавливаться, как только станет известно число. и цикл в isPrime может перейти к for (long i=3; i<limit; i+=2) - person MartyE; 19.08.2012
comment
вау ... math.sqrt () действительно имеет значение, но я не понимаю почему. Кто-нибудь хочет аргументировать, почему? Спасибо - person miatech; 19.08.2012
comment
@miatech Переход только к sqrt числа значительно уменьшает количество тестируемых делителей. т.е. если бы вы проверяли 1000001, вместо проверки 1000001 делителя, вы проверяете только 1000. И если вы проверяете только нечетные до 1000, то это сокращает его еще на половину. Итак, вы перейдете от тестирования 1000001 значения к тестированию 500. - person Inisheer; 19.08.2012
comment
Ваша IsPrime функция неверно сообщает, что 2 не является простым числом. - person Blastfurnace; 20.08.2012
comment
@Blastfurnace Да, я оставил это, потому что знал, что не буду проверять, является ли 2 простым, при решении проблемы. Микрооптимизация. - person Inisheer; 20.08.2012
comment
я отредактировал, чтобы исправить ошибку: должно быть <= sqrt, а не <; и упрощено только для разногласий. Но: ваш код неверен: для 3000009 он будет указывать 3 как самый большой фактор. Посмотрите мой ответ здесь. :) - person Will Ness; 21.08.2012

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

class LargestPrimeFactor4 {

    public static void main(String[] args) {
        long start, end, totalTime;
        long num = 600851475143L;   // odd value is not divided by any even
        long pFactor = 1L;

        start = System.currentTimeMillis();

        for(long i = 3L; i <= num / i; ) 
        {
            if( num % i == 0 ) {
                pFactor = i;
                num = num / i;
            }
            else {
                i += 2;
            }
        }
        if( pFactor < num ) { pFactor = num; }

        end = System.currentTimeMillis();
        totalTime = end - start;
        System.out.println( pFactor + " Time: " + totalTime);
    }
}
person Will Ness    schedule 20.08.2012
comment
извините, допустил ошибку при анализе вашего кода, все хорошо. Слишком много вина ;-) - person stefan; 21.08.2012
comment
@stefan хорошие времена! :) :) Я не согласен с удалением. Это такая простая вещь ... Это не домашнее задание ... - person Will Ness; 21.08.2012
comment
один пункт удаленного комментария все еще применяется: проверка if (pFactor < i) не требуется, так как это может быть только ранее проверенное i или 1. Так что удалите эту ветку, и вам станет еще лучше ;-) - person stefan; 21.08.2012
comment
Теперь мы почти друзья, за исключением вашего странного сочетания египетских скобок и правильного пути ;-) - person stefan; 21.08.2012
comment
Повторение записи переменной не так плохо, как ветвление. Лично меня не волнует, что это пишут снова и снова. - person stefan; 21.08.2012
comment
@stefan сохранил как можно больше оригинального кода ... :) ветвление, шманчинг. :) Меня больше беспокоят стены сложности. :) надо выпить сейчас! - person Will Ness; 21.08.2012
comment
@stefan (но серьезно, полезно знать, спасибо. У меня нет хороших привычек насчет производительности в маленьких ...) - person Will Ness; 21.08.2012

public HashSet<Integer> distinctPrimeFactors(int n) //insane fast prime factor generator
{
    HashSet<Integer> factors = new HashSet<Integer>();
    int lastres = n;
    if (n==1)
    {
        factors.add(1);
        return factors;
    }
    while (true)
    {
        if (lastres==1)
            break;
        int c = 2;
        while (true)
        {
            if (lastres%c==0)
                break;
            c++;
        }
        factors.add(c);
        lastres/=c;
    }
    return factors;
}

Если вы хотите быстро сгенерировать различные простые множители для числа, используйте этот метод, который уменьшает число на каждой итерации. Вы можете изменить int на long, и это должно сработать для вас.

person Jesus Ramos    schedule 19.08.2012

Вот псевдокод для целочисленной факторизации пробным делением:

define factors(n)

    z = 2

    while (z * z <= n)

        if (n % z == 0)
            output z
            n /= z

        else
            z++

    output n

Проще всего понять это на примере. Рассмотрим факторизацию n = 13195. Первоначально z = 2, но при делении 13195 на 2 остаток остается 1, поэтому предложение else устанавливает z = 3, и мы выполняем цикл. Теперь n не делится на 3 или на 4, но когда z = 5, остаток при делении 13195 на 5 равен нулю, поэтому выведите 5 и разделите 13195 на 5, так что n = 2639 и z = 5 не изменится. Теперь новое n = 2639 не делится на 5 или 6, но делится на 7, поэтому выведите 7 и установите n = 2639/7 = 377. Теперь мы продолжаем с z = 7, и это оставляет остаток, как и деление. на 8, 9, 10, 11 и 12, но 377/13 = 29 без остатка, поэтому выведите 13 и установите n = 29. В этой точке z = 13 и z * z = 169, что больше 29, поэтому 29 является простым и является последним множителем 13195, поэтому выведите 29. Полная факторизация составляет 5 * 7 * 13 * 29 = 13195.

Существуют лучшие алгоритмы факторизации целых чисел с использованием пробного деления и даже более мощные алгоритмы факторизации целых чисел, которые используют методы, отличные от пробного деления, но алгоритм, показанный выше, поможет вам начать работу, и его достаточно для Project Euler # 3. Когда будете готовы к большему, загляните сюда.

person user448810    schedule 19.08.2012

Две вещи для повышения производительности:

static boolean isPrime(long n)
{
    for(int i = 2; i <= sqrt(n); i++)  // if n = a * b, then either a or b must be <= sqrt(n).
    {
        if(n % i == 0)
        {
            return false;
        }
    }        
    return true;
}  

теперь для основного цикла

for(int i = num; i > 1; i--) // your interested in the biggest, so search from high to low until you have a match
{
    if(num % i == 0 && isPrime(i)) // check for num % i == 0 is faster, so do this first
    {
        pFactor = i;
        break; // break if you have a factor, since you've searched from the top
    }
}

Здесь еще есть кое-что, что можно улучшить, но это вам предстоит выяснить. Подумайте об изменении num. Удачи с проектом Эйлер :)

person stefan    schedule 19.08.2012
comment
поиск с высоты здесь не улучшается: наибольший простой фактор 600851475143 равен 6857. Гораздо ближе к 1. :) Кроме того, здесь нам не нужны никакие проверки на простоту. Ознакомьтесь с моим ответом. - person Will Ness; 21.08.2012
comment
идти сверху лучше, чем алгоритм OP, так как числа 6856 не нужно проверять. Очевидно, есть и лучшие способы, поэтому я предложил изменить num. Я пытался намекнуть на разложение на простые множители, что намного лучше (тогда, очевидно, от низкого к высокому). ProjectEuler.net - это самостоятельное исследование новых идей, поэтому подсказки лучше - person stefan; 21.08.2012
comment
6856 / 600851475143 = 1.1410473775350726e-8. Это одна миллионная процента улучшение. Ну давай же! :) Ах, я понимаю вашу точку зрения насчет подсказок и ЧП .... - person Will Ness; 21.08.2012
comment
PE научил меня оптимизировать каждую мелочь. Конечно, для решения этой проблемы лучше развить идею простой факторизации, но также полезно делать небольшие шаги и узнавать, как далеко вы можете зайти с текущим подходом. И вы правы: придерживаться того же подхода - плохая идея, даже обратный поиск ничего не дает. - person stefan; 21.08.2012

Вы можете просто разложить число на множители, и тогда ответом будет самый большой простой множитель:

import java.util.ArrayList;
import java.util.Collections;

public class PrimeFactorization {

    /* returns true if parameter n is a prime number, 
         false if composite or neither */
    public static boolean isPrime(long n) {
        if (n < 2) return false;
        else if (n == 2) return true;
        for (int i = 2; i < Math.pow(n, 0.5) + 1; i++)
            if (n % i == 0)
                return false;
        return true;
    }

    /* returns smallest factor of parameter n */
    public static long findSmallestFactor(long n) {
        int factor = 2; // start at lowest possible factor
        while (n % factor != 0) { // go until factor is a factor
            factor++; // test the next factor
        }
        return factor;
    }

    /* reduces the parameter n into a product of only prime numbers
       and returns a list of those prime number factors */
    public static ArrayList<Long> primeFactorization(long n) {

        ArrayList<Long> primes = new ArrayList<Long>();
          // list of prime factors in the prime factorization
        long largestFactor = n / findSmallestFactor(n);    

        long i = 2;
        while (i <= largestFactor) { 
          // for all possible prime factors 
          // (2 - largest factor of the number being reduced)

            if (isPrime(i) && n % i == 0) { 
                // if this value is prime and the number is divisible by it

                primes.add(i); // add that prime factor to the list
                n /= i; // divide out that prime factor from the number 
                        // to start reducing the new number
                largestFactor /= i; // divide out that prime factor 
                       // from the largest factor to get the largest 
                       // factor of the new number
                i = 2; // reset the prime factor test
            } else {
                i++; // increment the factor test
            }
        }

        primes.add(n); // add the last prime number that could not be factored
        Collections.sort(primes);
        return primes;
    }
}

А потом назовите это так:

ArrayList<Long> primes = PrimeFactorization.primeFactorization(600851475143L);
System.out.println(primes.get(primes.size() - 1));

Все это занимает всего несколько миллисекунд.

person Michael Yaworski    schedule 11.05.2014
comment
вы можете попробовать опубликовать этот код на CodeReview и посмотреть, какие предложения по улучшению вы можете там получить ... :) - person Will Ness; 11.05.2014
comment
@WillNess Это заняло 55 секунд из-за findSmallestFactor итерации 600851475147 раз lol. Если этот метод будет улучшен, то это будет действительно хороший алгоритм для простой факторизации. Спасибо, и я отправлю туда, чтобы получить отзывы. - person Michael Yaworski; 11.05.2014
comment
вы также можете сравнить свой код с моим ответом здесь и сравнить время их выполнения. --- и нет, с вашим кодом есть еще несколько проблем. :) - person Will Ness; 11.05.2014
comment
@WillNess Да. Мне нужно посмотреть, почему деление на i работает. - person Michael Yaworski; 11.05.2014
comment
разделение i и повторный запуск с i вместо 2, увеличение на 2 (можете ли вы улучшить это? :)) и остановку после sqrt. - person Will Ness; 11.05.2014
comment
@WillNess Да вау. Мне любопытно, каким образом я мог это придумать. - person Michael Yaworski; 11.05.2014

Это не идеальное решение, но оно подойдет для 600851475143.

public static void main(String[] args) {
    long number= 600851475143L;
    int rootOfNumber = (int)Math.sqrt(number)+10;
    for(int i = rootOfNumber; i > 2; i--) {
        if(number % i == 0) {
            if(psudoprime(i)) {
                System.out.println(i);
                break;
            }
        }
    }

}

public static boolean psudoprime(int num) {
    for(int i = 2; i < 100; i++) {
        if(num % i == 0) {
            return false;
        }
    }
    return true;
}
person Saurabh Prajapati    schedule 26.12.2017

public class LargestPrimeFactor {
    static boolean isPrime(long n){
        for(long i=2;i<=n/2;i++){
            if(n%i==0){
                return false;                                               
            }
        }
        return true;    
    }

    static long LargestPrimeFact(long n){
        long largestPrime=0;
        for(long i=2;i<Math.sqrt(n)/2;i++){
            if(n%i==0){
                if(isPrime(i)){
                    largestPrime=i;
                }
                }                                       
            }
        return largestPrime;
    }
    public static void main(String args[]) {
        System.out.println (LargestPrimeFact(600851475143L));
    }
}

Источник: http://crispylogs.com/project-euler-problem-3-solution/

person user3624855    schedule 11.05.2014
comment
к сожалению, это неверный ответ. Для 6, 23, 25, 600851475149 он вернет 0. - person Will Ness; 11.05.2014
comment
@mikeyaworski нет, не единственная причина. /2 совершенно неверно, и < тоже. После этого это просто очень неэффективный алгоритм. - person Will Ness; 11.05.2014

Этот отлично работает !!

public class Puzzle3 {
public static void main(String ar[])
{
    Long i=new Long("1");
    Long p=new Long("600851475143");
    Long f=new Long("1");
    while(p>=i)
    {
        if(p%i==0)
        {
            f=i;
            p=p/i;
            int x=1;
            i=(long)x;
        }
        i=i+2;
    }
    System.out.println(f);
}

}

person mtk.94    schedule 11.08.2014
comment
Привет, вы могли бы предоставить некоторый контекст или руководство, чтобы помочь автору понять ваше решение. - person Eel Lee; 11.08.2014
comment
что происходит здесь - person EpicPandaForce; 11.08.2014