Почему эта ситовая оптимизация нарушает мой код?

И как их исправить, чтобы они работали? Я пытаюсь оптимизировать свое сито в соответствии с предыдущими предложениями, но в обоих случаях код ломается:

Увеличение j = j + ( i * 2) приведет к нарушению кода.

Очевидно, мне не хватает некоторых концепций оптимизации, как и у других людей. Но в целом вы просто проходите и отмечаете все кратные простого числа как непростые. Оптимизация - следующий шаг.

// prime-3
// sieve implementation
function prime3(n) {
  const sieve = (new Array(n)).fill(true);
  // Only iterate up to the square root n for marking
  for (let i = 2; i * i <= n; i += 1) {
    if (sieve[i]) {
      // recommended optimization breaks the code
      // j = j + ( i * 2 )
      for (let j = i * i; j <= n; j = j + i) {
        sieve[j] = false;
      }
    }
  }
  return makePrimes(sieve, n);
};

function makePrimes(sieve, n) {
  let primes = [];
  for (let i = 2; i < n; i++) {
    if (sieve[i]) {
      primes.push(i);
    }
  }
  return primes;
}
console.log(prime3(100));


person jennifer    schedule 11.03.2020    source источник
comment
рекомендация была для особого случая 2, начиная с 3 и увеличьте количество кандидатов на 2 и используйте приращение внутреннего цикла 2*i вместо простого i - при условии, что вы начинаете с 3 и увеличиваете кандидатов на 2, то есть. :) т.е. оптимизации идут в определенном порядке, вы не можете пропустить одну и использовать другую, которая действительно зависит от первой!   -  person Will Ness    schedule 12.03.2020
comment
спасибо, отметил. также мне нужно было пройтись по коду, чтобы увидеть, что он делает, и чем это более очевидно. Создание сита с выделенными эвенами - это O(n) сложность по времени, можете ли вы объяснить общую проблему?   -  person jennifer    schedule 15.03.2020
comment
Что касается большой временной сложности, см. последнее предложение в моем ответе на ваш другой вопрос. Дает ссылки. π(n)* π(√n) там означает: каждое простое число под n проверяется на делимость на все простые числа под sqrt(n). π(n) = ~ n/log n (подробности см. По ссылке). насчет композитов смотрите другую ссылку. общая сложность - максимальная из двух. Если после работы по этим ссылкам у вас возникнут какие-то конкретные вопросы, задавайте их, пожалуйста. :)   -  person Will Ness    schedule 15.03.2020
comment
или вы имели в виду сложность правильного сита Эратосфена? для этого см., например, stackoverflow.com/a/2582776/849891. (комментарий выше относится к оптимальному сите пробного деления).   -  person Will Ness    schedule 15.03.2020


Ответы (3)


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

function makeSieve(n){
  const sieve = new Array(n).fill(true);
  for(let i = 2; i < sieve.length; i += 2){
    sieve[i] = false;
  }
}

Теперь, когда вы переходите к отметке непростых чисел, вы можете увеличивать i * 2

Например

3, 6, 9, 12, 15, 18, 21

станет

3, 9, 15, 21
person anon    schedule 15.03.2020

Ваша оптимизация должна применяться после того, как сначала нужно избавиться от четного числа (кроме 2). потому что, когда i==2, вы фактически пропускали все четные числа, увеличивая на i*2.

Вот рабочий код:

// prime-3
// sieve implementation
function prime3(n) {
  let sieve = (new Array(n)).fill(true);
  for (let i = 4; i < n; i+=2) {
    sieve[i] = false;
  }
  
  // Only iterate up to the square root n for marking
  for (let i = 2; i * i <= n; i += 1) {
    if (sieve[i]) {
      // now it works
      // j = j + ( i * 2 )
      for (let j = i * i; j <= n; j = j + i*2) {
        sieve[j] = false;
      }
    }
  }
  return makePrimes(sieve, n);
};

function makePrimes(sieve, n) {
  let primes = [];
  for (let i = 2; i < n; i++) {
    if (sieve[i]) {
      primes.push(i);
    }
  }
  return primes;
}
console.log(prime3(100));

Редактировать

Поцарапайте это. Дальнейшее тестирование действительно показывает, что prime3 в 3 раза быстрее, чем простое сито.

Код хоть и работает, но, кажется, вложил в него слишком много уловок и внес как дополнительные вычисления, так и путаницу. Простой код сита, подобный приведенному ниже в моем сравнении, выполняет код в ответе. Опять же, KISS - это принцип.

Сценарию в исходном ответе потребовалось 317 мсек, чтобы просеять 1 млн чисел, в то время как простое сито заняло всего 241 мс.

function simpleSieve(n) {
  let a = new Array(n)
  let answer = []
  let p
  for (let i = 2; i < n; i ++) {
    a[i] = i
  }
  for (let i = 2; i < n; i++) {
    if (a[i]) {
      answer.push(a[i])
      p = a[i]
      for(let j = p; j < n;  j += p) {
        a[j] = 0
      }
    }
  }
  return answer
}

Редактировать 2

Повторное тестирование с cpp и prime3 действительно улучшает примерно в 3 раза быстрее, чем простое сито:

p3:
n = 100000000, t = 866717 microseconds.
n = 200000000, t = 2354425 microseconds.
n = 300000000, t = 3689165 microseconds.
n = 400000000, t = 4950224 microseconds.
n = 500000000, t = 6119779 microseconds.
n = 600000000, t = 7375925 microseconds.
n = 700000000, t = 8647293 microseconds.
n = 800000000, t = 10477116 microseconds.
n = 900000000, t = 11589894 microseconds.
n = 1000000000, t = 12806997 microseconds.
simple:
n = 100000000, t = 2316019 microseconds.
n = 200000000, t = 6063749 microseconds.
n = 300000000, t = 9783295 microseconds.
n = 400000000, t = 13315450 microseconds.
n = 500000000, t = 16640474 microseconds.
n = 600000000, t = 20282461 microseconds.
n = 700000000, t = 24219469 microseconds.
n = 800000000, t = 29203786 microseconds.
n = 900000000, t = 32965856 microseconds.
n = 1000000000, t = 37694084 microseconds.

Коды здесь для полноты:

void simpleSieve(int n) {
  bool *a = (bool *)calloc(n, sizeof(bool));
  int p;
  memset(a, true, sizeof(bool) * n);
  for (int i = 2; i < n; i++) {
    if (a[i]) {
      p = i;
      for (int j = p; j < n; j += p) {
        a[j] = 0;
      }
    }
  }
  free(a);
}

void prime3(int n) {
  bool *sieve = (bool*)calloc(n, sizeof(bool));
  sieve[2] = true;
  for (int i = 3; i < n; i+=2) {
    sieve[i] = true;
  }
  int step;
  for (int i = 2; i * i <= n; i += 1) {
    if (sieve[i]) {
      step = i*2;
      for (int j = i * i; j <= n; j = j + step) {
        sieve[j] = false;
      }
    }
  }
  free(sieve);
}
person Jingshao Chen    schedule 11.03.2020
comment
Кажется неэффективным создавать массив со всеми истинными значениями, а затем отмечать все события. Хотелось бы, чтобы был способ инициализировать массив со всеми выделенными событиями. - person jennifer; 11.03.2020
comment
цикл просто O (n). величина меньше общей сложности, которая составляет O (nlog (log (N))) - person Jingshao Chen; 11.03.2020
comment
почему вы отмечаете кратные 4,6,8,10, ... как составные? разве они не помечены как составные, кратные 2? --- почему вы проверяете, являются ли четные числа больше 2 простыми? разве вы уже не знаете, что это не так? --- почему вы помечаете 2 как составное, затем как простое, а затем проверяете, простое оно или нет? разве вы уже не знаете, что 2 - простое число? - person Will Ness; 12.03.2020
comment
Они не были отмечены как составные в вопросе, потому что приращение на i*2, а наименьшее i равно 2. Последнее «почему» действительно. Я обновил свой ответ. - person Jingshao Chen; 12.03.2020
comment
Также отредактировал свой ответ, чтобы добавить стандартное простое сито - person Jingshao Chen; 12.03.2020
comment
все причины были действительны. - person Will Ness; 15.03.2020

Собственно оптимизация у меня работает:

 // prime-3
    // sieve implementation
    function prime3 (n) {
      const sieve = (new Array(n)).fill(true);
    
      // Only iterate up to the square root n for marking
      for (let i = 2; i * i <= n; i += 1) {
        if (sieve[i]) {
    
          // recomended optimizations break the code
          // j = i * i 
          // j = j + ( i * 2 )
          for (let j = i * i; j <= n; j = j+i) {
            sieve[j] = false;
          }
        }
      }
      return makePrimes(sieve, n);
    };
    
    function makePrimes(sieve, n){
      let primes = [];
      for (let i = 2; i < n; i++) {
        if(sieve[i]) {
          primes.push(i);
        }
      }
      return primes;
    }
    
    console.log(prime3(100));

Но ваша j = j + ( i * 2 ) «оптимизация» на самом деле нарушает алгоритм. Результаты будут содержать не простые числа. Вот фрагмент для проверки:

function prime3 (n) {
      const sieve = (new Array(n)).fill(true);
    
      // Only iterate up to the square root n for marking
      for (let i = 2; i * i <= n; i += 1) {
        if (sieve[i]) {
    
          // recomended optimizations break the code
          // j = i * i 
          // j = j + ( i * 2 )
          for (let j = i * i; j <= n; j = j+(i*2)) {
            sieve[j] = false;
          }
        }
      }
      return makePrimes(sieve, n);
    };
    
    function makePrimes(sieve, n){
      let primes = [];
      for (let i = 2; i < n; i++) {
        if(sieve[i]) {
          primes.push(i);
        }
      }
      return primes;
    }
    
    console.log(prime3(100));

person Max K    schedule 11.03.2020