Программа для поиска простых чисел - PullRequest
29 голосов
/ 02 октября 2009

Я хочу найти простое число между 0 и длинной переменной, но я не могу получить никакого вывода.

Программа

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication16
{
    class Program
    {
        void prime_num(long num)
        {
            bool isPrime = true;
            for (int i = 0; i <= num; i++)
            {
                for (int j = 2; j <= num; j++)
                {
                    if (i != j && i % j == 0)
                    {
                        isPrime = false;
                        break;
                    }
                }
                if (isPrime)
                {
                    Console.WriteLine ( "Prime:" + i );
                }
                isPrime = true;
            }
        }

        static void Main(string[] args)
        {
            Program p = new Program();
            p.prime_num (999999999999999L);
            Console.ReadLine();
        }
    }
}

Может ли кто-нибудь помочь мне и найти возможную ошибку в программе?

Ответы [ 24 ]

75 голосов
/ 02 октября 2009

Вы можете сделать это быстрее, используя почти оптимальное пробное сито с разделением в одну (длинную) строку, например:

Enumerable.Range(0, Math.Floor(2.52*Math.Sqrt(num)/Math.Log(num))).Aggregate(
    Enumerable.Range(2, num-1).ToList(), 
    (result, index) => { 
        var bp = result[index]; var sqr = bp * bp;
        result.RemoveAll(i => i >= sqr && i % bp == 0); 
        return result; 
    }
);

Формула аппроксимации для числа используемых здесь простых чисел: π(x) < 1.26 x / ln(x). Нам нужно проверять только простые числа, не превышающие x = sqrt(num).

Обратите внимание, что сито Эратосфена имеет гораздо лучшую сложность во время выполнения, чем пробное деление (при правильном применении должно работать намного быстрее при больших значениях num.

25 голосов
/ 02 октября 2009

Попробуйте это:

void prime_num(long num)
{

    // bool isPrime = true;
    for (long i = 0; i <= num; i++)
    {
        bool isPrime = true; // Move initialization to here
        for (long j = 2; j < i; j++) // you actually only need to check up to sqrt(i)
        {
            if (i % j == 0) // you don't need the first condition
            {
                isPrime = false;
                break;
            }
        }
        if (isPrime)
        {
            Console.WriteLine ( "Prime:" + i );
        }
        // isPrime = true;
    }
}
9 голосов
/ 02 октября 2009

Люди упомянули пару строительных блоков для того, чтобы сделать это эффективно, но никто на самом деле не собрал все воедино. Сито из Эратосфена является хорошим началом, но с ним у вас не хватит памяти длиной , прежде чем вы достигнете установленного предела. Это не значит, что это бесполезно - когда вы делаете свой цикл, вы действительно заботитесь о главных делителях. Таким образом, вы можете начать с использования сита для создания базы простых делителей, а затем использовать их в цикле для проверки чисел на первичность.

Однако, когда вы пишете цикл, вы действительно НЕ хотите, чтобы мы использовали sqrt (i) в условии цикла, как предложили несколько ответов. Мы с вами знаем, что sqrt - это «чистая» функция, которая всегда дает один и тот же ответ, если задан один и тот же входной параметр. К сожалению, компилятор НЕ знает этого, поэтому, если использовать что-то вроде '<= Math.sqrt (x)' в условии цикла, он будет пересчитывать sqrt числа на каждой итерации цикла. </p>

Вы можете избежать этого несколькими разными способами. Вы можете либо предварительно вычислить sqrt перед циклом, и использовать предварительно вычисленное значение в условии цикла, либо вы можете работать в другом направлении и изменить i<Math.sqrt(x) на i*i<x. Лично я бы предварительно вычислил квадратный корень - хотя я думаю, что он более понятен и, возможно, немного быстрее - но это зависит от количества итераций цикла (i*i означает, что он все еще выполняет умножение в цикле ). Всего за несколько итераций i*i обычно будет быстрее. При достаточном количестве итераций потери от i*i каждой итерации перевешивают время выполнения sqrt после выхода из цикла.

Вероятно, этого достаточно для размера чисел, с которыми вы имеете дело - ограничение в 15 цифр означает, что квадратный корень составляет 7 или 8 цифр, что вписывается в довольно разумный объем памяти. С другой стороны, если вы хотите много работать с числами в этом диапазоне, вам может понадобиться взглянуть на некоторые из более сложных алгоритмов первичной проверки, такие как алгоритмы Полларда или Брента . Они более сложные (мягко говоря), но для больших чисел лот быстрее.

Существуют другие алгоритмы для еще больших чисел ( квадратичное сито , общее числовое сито ), но мы пока не будем в них разбираться - их много более сложный и действительно полезный только для работы с действительно большими числами (GNFS начинает использоваться в диапазоне от 100 цифр).

9 голосов
/ 02 октября 2009

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

for (int j = 3; j <= Math.Sqrt(i); j+=2) { ... }

Вы также можете выйти из функции, как только вы обнаружите, что число не простое, вам больше не нужно проверять делители (я вижу, вы уже это делаете!).

Это будет работать, только если num больше двух.

Нет Sqrt

Вы можете избежать Sqrt в целом, сохраняя текущую сумму. Например:

int square_sum=1;
for (int j=3; square_sum<i; square_sum+=4*(j++-1)) {...}

Это потому, что сумма чисел 1+ (3 + 5) + (7 + 9) даст вам последовательность нечетных квадратов (1,9,25 и т. Д.). И, следовательно, j представляет квадратный корень из square_sum. Если square_sum меньше i, то j меньше квадратного корня.

7 голосов
/ 17 апреля 2012

Первый шаг: написать метод расширения, чтобы выяснить, является ли ввод простым

public static bool isPrime(this int number ) {

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

    return true;   
}

2 шаг: напишите метод, который будет печатать все простые числа от 0 до ввода числа

public static void getAllPrimes(int number)
{
    for (int i = 0; i < number; i++)
    {
        if (i.isPrime()) Console.WriteLine(i);
    }
}
6 голосов
/ 02 октября 2009

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

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

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

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

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

Так что моя лучшая рекомендация для вас - сделать что-то вроде этого:

public void main(string args[])
{
    //Get the number you want to use as input
    long x = number;//'number' can be hard coded or retrieved from ReadLine() or from the given arguments

    IList<long> primes = FindSmallerPrimes(number);

    DisplayPrimes(primes);
}

public IList<long> FindSmallerPrimes(long largestNumber)
{
    List<long> returnList = new List<long>();
    //Find the primes, using a method as described by another answer, add them to returnList
    return returnList;
}

public void DisplayPrimes(IList<long> primes)
{
    foreach(long l in primes)
    {
        Console.WriteLine ( "Prime:" + l.ToString() );
    }
}

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

6 голосов
/ 06 декабря 2013

EDIT_ADD: Если Уилл Несс прав, что целью вопроса является просто вывод непрерывного потока простых чисел в течение всего времени работы программы (нажатие кнопки «Пауза / Разрыв» для паузы и любая клавиша для повторного запуска) ) без какой-либо серьезной надежды на каждое достижение этого верхнего предела, тогда код должен быть написан без аргумента верхнего предела и проверки диапазона «true» для первого цикла «i» for. С другой стороны, если вопрос хотел на самом деле вывести простые числа до предела, то следующий код сделает работу намного эффективнее, используя пробное деление только для нечетных чисел, с тем преимуществом, что он вообще не использует память (это также может быть преобразовано в непрерывный цикл согласно приведенному выше):

static void primesttt(ulong top_number) {
  Console.WriteLine("Prime:  2");
  for (var i = 3UL; i <= top_number; i += 2) {
    var isPrime = true;
    for (uint j = 3u, lim = (uint)Math.Sqrt((double)i); j <= lim; j += 2) {
      if (i % j == 0)  {
        isPrime = false;
        break;
      }
    }
    if (isPrime) Console.WriteLine("Prime:  {0} ", i);
  }
}

Во-первых, код вопроса не производит вывод, потому что его переменные цикла являются целыми числами, а проверяемый лимит является огромным длинным целым числом, что означает, что цикл не может достичь предела, создавая внутренний цикл EDITED: в результате чего переменная 'j' возвращается к отрицательным числам; когда переменная 'j' возвращается к -1, проверяемое число не проходит простое тестирование, поскольку все числа делятся на -1 END_EDIT . Даже если бы это было исправлено, код вопроса производит очень медленный вывод, потому что он связан с выполнением 64-битного деления очень больших количеств составных чисел (все четные числа плюс нечетные составные) на весь диапазон чисел до этой вершины число десять возведено в шестнадцатую степень для каждого простого числа, которое оно может произвести. Приведенный выше код работает, потому что он ограничивает вычисление только нечетными числами и выполняет только деление по модулю до квадратного корня текущего проверяемого числа .

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

Несмотря на то, что ответ одного типа (вроде) @SLaks с использованием Linq работает, на самом деле это не «Сито Эратосфена», а просто неоптимизированная версия Trial Division , неоптимизированная в этом смысле. не удаляет нечетные простые числа, не начинается с квадрата найденного базового простого числа и не прекращает отбраковку базовых простых чисел, больших чем квадратный корень из верхнего числа для просеивания. Это также довольно медленно из-за множественных вложенных операций перечисления.

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

static IEnumerable<int> primes(uint top_number) {
  var cullbf = Enumerable.Range(2, (int)top_number).ToList();
  for (int i = 0; i < cullbf.Count; i++) {
    var bp = cullbf[i]; var sqr = bp * bp; if (sqr > top_number) break;
    cullbf.RemoveAll(c => c >= sqr && c % bp == 0);
  } return cullbf; }

, который работает во много раз быстрее, чем ответ SLaks. Тем не менее, он все еще медленный и занимает много памяти из-за генерации списка и нескольких перечислений, а также операций множественного деления (подразумеваемых по модулю).

Следующая истинная реализация Sieve of Eratosthenes работает примерно в 30 раз быстрее и занимает гораздо меньше памяти, так как использует только одноразрядное представление для каждого просеянного числа и ограничивает его перечисление до конечной выходной последовательности итератора, а также оптимизирует только обработку нечетные композиты и отбраковка только от квадратов базовых простых чисел для базовых простых чисел до квадратного корня из максимального числа, как показано ниже:

static IEnumerable<uint> primes(uint top_number) {
  if (top_number < 2u) yield break;
  yield return 2u; if (top_number < 3u) yield break;
  var BFLMT = (top_number - 3u) / 2u;
  var SQRTLMT = ((uint)(Math.Sqrt((double)top_number)) - 3u) / 2u;
  var buf = new BitArray((int)BFLMT + 1,true);
  for (var i = 0u; i <= BFLMT; ++i) if (buf[(int)i]) {
      var p = 3u + i + i; if (i <= SQRTLMT) {
        for (var j = (p * p - 3u) / 2u; j <= BFLMT; j += p)
          buf[(int)j] = false; } yield return p; } }

Приведенный выше код вычисляет все простые числа до десяти миллионов в диапазоне около 77 миллисекунд на Intel i7-2700K (3,5 ГГц).

Любой из двух статических методов может быть вызван и протестирован с помощью операторов using и статического метода Main следующим образом:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

static void Main(string[] args) {
  Console.WriteLine("This program generates prime sequences.\r\n");

  var n = 10000000u;

  var elpsd = -DateTime.Now.Ticks;

  var count = 0; var lastp = 0u;
  foreach (var p in primes(n)) { if (p > n) break; ++count; lastp = (uint)p; }

  elpsd += DateTime.Now.Ticks;
  Console.WriteLine(
    "{0} primes found <= {1}; the last one is {2} in {3} milliseconds.",
    count, n, lastp,elpsd / 10000);

  Console.Write("\r\nPress any key to exit:");
  Console.ReadKey(true);
  Console.WriteLine();
}

, который покажет число простых чисел в последовательности до предела, последнее найденное простое число и время, затраченное на перечисление этого количества.

EDIT_ADD: Однако, чтобы получить перечисление числа простых чисел менее десяти тысяч триллионов (от десяти до шестнадцатой степени), как задает вопрос, сегментированный постраничный подход с использованием многоядерной обработки требуется, но даже с C ++ и очень высокооптимизированным PrimeSieve , для получения числа найденных простых чисел потребуется более 400 часов, а для перечисления всех их в десятки раз больше, чем за год делать то, что задает вопрос. Чтобы сделать это с использованием неоптимизированного алгоритма Trial Division, понадобятся супер-эоны и очень-очень долгое время даже при использовании оптимизированного алгоритма Trial Division, как, например, от десяти до двух миллионных энергетических лет (это два миллиона нулей! !).

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

Решения, которые я выкладываю здесь, тоже не сработают, так как даже последнему Сите Эратосфена потребуется около 640 Терабайт памяти для этого диапазона.

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

5 голосов
/ 02 октября 2009

Пахнет как домашнее задание. У моего очень очень старого графического калькулятора была простая программа, подобная этой. Технически, внутренний цикл проверки на отклонения должен работать только до i ^ (1/2). Вам нужно найти «все» простые числа от 0 до L? Другая важная проблема заключается в том, что переменные вашего цикла имеют значение «int», а ваши входные данные «длинные», это приведет к переполнению, из-за которого ваши циклы не будут выполняться ни разу. Исправьте переменные цикла.

2 голосов
/ 17 ноября 2012

Однострочный код в C #: -

Console.WriteLine(String.Join(Environment.NewLine, 
    Enumerable.Range(2, 300)
        .Where(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1)
        .All(nn => n % nn != 0)).ToArray()));                                    
2 голосов
/ 20 декабря 2012

Ответ Сито Эратосфена выше не совсем корректен. Как написано, он найдет все простые числа от 1 до 1000000. Чтобы найти все простые числа от 1 до num, используйте:

private static IEnumerable Primes01(int num)
{
    return Enumerable.Range(1, Convert.ToInt32(Math.Floor(Math.Sqrt(num))))
        .Aggregate(Enumerable.Range(1, num).ToList(),
        (result, index) =>
            {
                result.RemoveAll(i => i > result[index] && i%result[index] == 0);
                return result;
            }
        );
}

Семя Агрегата должно быть в диапазоне от 1 до num, так как этот список будет содержать окончательный список простых чисел. Enumerable.Range(1, Convert.ToInt32(Math.Floor(Math.Sqrt(num)))) - это количество раз, когда семя очищается.

...