2013-05-17 2 views
2

Я написал код, который возвращает сумму всех простых чисел, значения которых составляют менее 2 миллионов. Но на это уходит огромное время (дождался 30 минут ответа).Как улучшить алгоритм простой суммы?

Может ли кто-нибудь предложить, как сделать алгоритм более эффективным?

public static void main(String[] args){ 

    int i,primeNum=1,sumPrime=0,c=0; 
    while (primeNum<2000000){ 
      int factors=0; 
      for(i=1;i<=primeNum;i++){ 
       if((primeNum%i)==0) { 
        factors++; // total number of factors     
       } 
      }    
      if(factors==2){ 
       if(primeNum<2000000){ 
        sumPrime=primeNum+c; 
        c=sumPrime; 
       } 
       System.out.println(primeNum); 
      } 

      primeNum++; 

     } 
     System.out.println(sumPrime); 
    } 
+0

Ваш алгоритм неэффективен. – Rupak

+0

Потому что вы используете цикл в 2 миллиона раз. – Makky

+0

По-прежнему не должно занимать 30 минут. Я подозреваю бесконечный цикл – sanbhat

ответ

0

На самом деле код недостаточно оптимизирован, потому что в худшем случае время, затраченное на выполнение цикла, будет больше. Вы можете даже оптимизировать код для нахождения простого числа, как well.Try ниже код и он работает отлично

public static void main(String[] args) { 
    //you know 2 is the only even prime. 
    int sum = 2; 
    for (int i = 3; i <= 2000000; i++) { 
     boolean prime = isPrime(i); 
     if(prime){ 
      sum+=i; 
     } 
    } 
    System.out.println("Sum = " + sum); 
} 

/* 
* we know 2 is the “oddest” prime – it happens to be the only even prime 
* number. Because of this, we need only check 2 separately, then traverse 
* odd numbers up to the square root of n 
*/ 
public static boolean isPrime(int n) { 
    // check if n is a multiple of 2 
    if (n % 2 == 0) 
     return false; 
    // if not, then just check the odds 
    for (int i = 3; i * i <= n; i += 2) { 
     if (n % i == 0) 
      return false; 
    } 
    return true; 
} 
+0

, используя какое-то хранилище для простых чисел (например, List), сильно улучшит все это, потому что он перестает проверять те же номера, которые мы уже знаем как unprime снова и снова – LionC

+0

Но это снова вызовет или сделает сложным вещь пространство-накрест. IMO, для этих типов кодов лучше не хранить их в каких-либо структурах данных в памяти и просто развиваться наивным образом с небольшим количеством twiddling в случае простых чисел, проверяя до квадратного корня. Что сказать ? –

+1

Требуемое пространство памяти не должно быть сделкой на любом устройстве, в конце вы сохранили все простые числа <2.000.000, как целые, которые должны быть чем-то размером 1-2 МБ, что на самом деле не так много, но он значительно повышает эффективность. – LionC

1

Для начала:

  • ли петля for(i=1;i<=primeNum;i++){ должны бежать primeNum или, может быть, просто квадратный корень из primeNum?
  • i должен быть увеличен на 1 или более эффективен?
  • ...
+1

Кроме того, «вам нужно продолжить цикл после того, как вы обнаружите, что число будет делимым чем-то?» – Dukeling

+0

Плюс: вам нужно изобретать колесо ... ;-) –

0

Ваш алгоритм действительно неэффективна, для алгоритмов вычисления простых чисел выглядят here. Подведение итогов, если у вас их есть, не должно быть проблемой.

-2

Уменьшить использование условий if. Это замедляет ваш код. Попробуйте использовать тройные операторы, если это возможно, если это влияет на вашу скорость, даже если это не рекомендуется в java 1.5.

Попробуйте это решение:

if((factors==2)&&(primeNum<2000000)) 

вместо повторной, если условия и размещать какие-либо различия.

+0

Это совершенно не причина для кода, требующего так много времени, на самом деле в зависимости от компилятора обе версии будут генерировать одинаковый байт-код в любом случае – LionC

+0

Повторяется, если проверка состояния влияет на представление. В этом случае, когда цикл проходит около 2 миллионов раз, это определенно стоит попробовать. – Arun

+1

Это изменение влияет на поток управления программой. Но это не будет иметь большого значения - 2000000 итераций теста займет гораздо меньше одной секунды, что мало чем 30 минут. Кроме того, оптимизирующий компилятор может исключить тест primeNum, поскольку он всегда верен. – Skizz

1

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

Во всяком случае, я оставлю некоторые шаги, которые помогут вам в правильном направлении:

  • Разбейте свой код на два метода:
    • Эратосфена решето (http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes), который является довольно тривиально, и будет вернет вам список всех простых чисел под 2M.
    • Метод, который просто суммирует все значения, возвращаемые ситом.

Это будет очень эффективным, если вы реализуете сито правильно, он должен принести вниз время выполнения на пару секунд.

1

Есть несколько вещей:

  1. Если вы хотите, чтобы определить более одного простого алгоритма решета гораздо лучше. (Google для сита Эратосфена, а затем подведите итог).

  2. Даже при использовании наивного алгоритма, как вы это делаете, есть несколько улучшений можно:

    1. Думает об этом: кроме первого, даже премьера, все остальные нечетные, так что вы не должны делать/primeNumber++ в вашей петле, но еще primeNumber+=2 (и на самом деле начать цикл не с 1). [Runtime halfed]

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

    3. В вашей внутренней петле вы можете сэкономить еще больше. Вам не нужно проверять весь номер < prime, но только до < sqrt (prime). Потому что, когда число делит простое, всегда должны быть два фактора, и один из них должен быть меньше (или равен) квадратному корню. [Runtime "roototed"]

    4. Вы не хотите, чтобы факторы простого числа, вы только хотите знать, является ли число простым или нет. Поэтому, когда вы знаете, что это не просто, НЕ продолжайте его тестировать (т.е. выходите из цикла, когда у вас есть первый фактор (чтобы сделать это проще, вы НЕ должны тестировать 1 и сам номер) - это спасет вас огромное количество времени. [время выполнение еще более снижаются.]

Так с этим советами даже ваш наивный подход без решета приведет к выполнению менее 2 протоколируется (SQRT (15/4)) .

1

Эта часть очень innefficent: -

for(i=1;i<=primeNum;i++){ 
    if((primeNum%i)==0) { 
    factors++; // total number of factors     
    } 
}  

Подсчитывает общее количество факторов. Поскольку все, что вас интересует, - это число, простое или нет, количество факторов не требуется. Тест должен быть, если есть фактор, который не является 1 или проверенным числом. Таким образом, вы можете это сделать: -

boolean is_prime = true; 
// start at 3 as 1 is always a factor and even numbers above 2 are definately not prime, terminate at n-1 as n is also a factor 
for(i=3;i<primeNum;i+=2){ 
    if((primeNum%i)==0) { 
    is_prime = false; 
    break; 
    } 
} 

Это теперь более эффективно для не простых чисел. Для простых чисел это слишком много, факторы попадают в пары: если a.b == c, то a < = sqrt (c) и b> = sqrt (c), поэтому цикл может безопасно завершаться в sqrt (primeNum). Вы можете вычислить sqrt (primeNum) перед циклом, но для этого обычно требуется использование функций с плавающей запятой. Вместо этого или заканчивая при i> sqrt (primeNum), завершите цикл, когда i.i> primeNum. Вы также можете удалить умножение i.i и заменить его дополнительной переменной и несколькими добавками (слева в качестве упражнения для читателя).

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

0

Эта функция суммирует штрихами менее п с помощью решета Эратосфена:

function sumPrimes(n) 
    sum := 0 
    sieve := makeArray(2..n, True) 
    for p from 2 to n step 1 
     if sieve[p] 
      sum := sum + p 
      for i from p * p to n step p 
       sieve[i] := False 
    return sum 

Я оставлю его вам перевести Java. Для n = 2000000, это должно выполняться через одну или две секунды.

1

Это петля в цикле, которая заставляет код работать медленно. Вот код, я обнаружил, что работает с теми же критериями, всего за несколько секунд:

void setup() { 
    for (int i = 1; i<2000000; i++) { 
    if (isPrime(i)) { 
     System.out.println(i); 
    } 
    } 
} 

boolean isPrime(int number) { 
    if (number < 2) return false; 
    if (number == 2) return true; 
    if (number % 2 == 0) return false; 
    for (int i = 3; (i*i)<=number; i+=2) { 
    if (number % i == 0) return false; 
    } 
    return true; 
} 
+0

i i i в 'for'-condition неэффективно, лучше сделать его' i <= sqrt (number) '. Там компилятор может переместить вычисление корня перед циклом, и он выполняется только один раз, а тест - это просто сравнение. (А во внешнем цикле можно также сделать «i + = 2' вместо« i ++ ») – flolo

+0

Внешний цикл должен только продемонстрировать, что он возвращает хороший результат или плохой результат. Ваша формула с sqrt во всех моих тестовых камерах медленнее. Если вы конвертируете код в C, то ваша формула будет быстрее. Я попытался побить формулу i * i, но результат снова медленнее. – Dippo

0

В Python, вы можете сделать это таким образом. По сравнению с некоторыми предыдущими решениями я стараюсь не менять вектор isPrime, как только я пересекаю $ \ sqrt {n} $

import math 
def sum_primes(nmax): 
    maxDivisor=int(math.floor(math.sqrt(nmax))) 
    isPrime=[False,False]+range(2,nmax+1) 

    for i in range(2,maxDivisor+1): 
     if isPrime[i] is not False: 
      for dummy in range(i*2,nmax+1,i): 
       isPrime[dummy]=False 

    print (sum(isPrime)) 
Смежные вопросы