【问题标题】:Solving the seventh with O(n)用 O(n) 解决第七个问题
【发布时间】:2018-07-31 13:11:48
【问题描述】:

我已经解决了seventh problem of Euler,上面写着:

通过列出前六个素数:2、3、5、7、11 和 13,我们可以 看到第 6 个素数是 13。

第 10001 个素数是多少?

我使用它解决了它,并且在我保存表兄弟的数组中,当它达到 10001 的长度时,我返回那个数字。该算法需要 1300 毫秒,我认为这非常低效,我在实现中具体做了什么?

var start = performance.now();

function eratosthenes(n) {
  var arr = [2], acc = 0; 
// matrix to save the found prime numbers and mark their multiples
  for(var i = 3; true; i += 2) { // loop
    if(arr.length === n) return arr[arr.length - 1]; // if the array length is equal to n return the last number
    if(!resolve(arr, i)) { // check if is multiple of the prime numbers, already found.
      arr.push(i); // if isnt multiple, save it
    }
  }
}

function resolve(array, n) {
  return array.some(cur => !(n%cur));
}
console.log(eratosthenes(10001)); // Tooks 1300 ms

var end = performance.now();
var time = end - start;

console.log(time);

【问题讨论】:

  • 你的算法不是Sieve of eratosthenes,如果实施得当,应该会有帮助。
  • 不,实现不正确。也许this 可以提供帮助
  • 你为什么不自己做实验呢?二进制搜索不是一个坏主意。10^6 应该是一个很好的猜测。
  • @JaromandaX Prime number theorem 我怀疑上述算法需要多长时间才能找到第 100,001 个素数
  • @J.Uchu 这个问题很简单,应该可以自己解决。提示:保持计数?

标签: javascript algorithm


【解决方案1】:

如果是代码编写练习,最好探索早期的答案。

但是,如果您想要一个简单而快速的解决方案,那么您可以使用我创建的 prime-lib 来解决它:

import {generatePrimes} from 'prime-lib';

const seekIndex = 10_001; // index of the prime being sought
const start = Date.now();
let a, c = 0;
const i = generatePrimes({boost: seekIndex + 1});
while ((a = i.next()) && !a.done && c++ < seekIndex) ;

console.log(`Prime: ${a.value}, took ${Date.now() - start}ms`);

在我的电脑上它会吐出来:

Prime: 104759, took 5ms

而使用现代 RXJS,这变得更加简单:

import {generatePrimes} from 'prime-lib';
import {from, last} from 'rxjs';

const seekIndex = 10_001; // index of the prime being sought
const i = generatePrimes({boost: seekIndex + 1});
from(i).pipe(last()).subscribe(console.log);
//=> 104759

【讨论】:

  • 在这里打开一个旧问题来发布一个新答案是没有问题的。但是,如果您打算将人们指向您维护的库,那么在您的帖子中提及一次是个好主意。例如,当我提到 Ramda 时,我总是这样做:“使用 Ramda 时(免责声明:我是它的主要作者之一)......”
  • @ScottSauyet 已修改 ;)
  • 这很好……比我那需要大约 90 毫秒的天真无轮筛要快得多。总有一天,我发誓我会深入研究 Will Ness 推迟的筛子优化。除了我们可以从当前素数的平方开始的明显优化之外,我真的不知道他在推迟什么。它不仅仅是 2-3-5-7(或其他)轮子,对吧?
  • Will Ness 只触及了皮毛,哈哈,但GordonBGood did a lot more digging into it,其中大部分也出现在我的图书馆中 :) 总的来说,它可能成为一个过度消耗的主题。
  • 我看到了你代码中提到的那篇文章,可能有一天会深入研究它。但这并不是让我过度消耗的东西。很久以前,当我在做欧拉问题时,我想要一个相当有效的素数生成器,写了一个筛子并完成了它。然而,我仍然很好奇!
【解决方案2】:

这与 גלעד ברקן 的答案具有相似的运行时间(实际上在我的机器上快了大约 10%),但不依赖于在开始之前知道近似的 max。它执行一系列 Eratosthenes 直到最大值(从 2 开始),然后加倍 max,根据先前找到的素数初始化数组中的新元素并重复。

function eratosthenes(n) {
    let prev_max = 1, max = 2, i, j;
    const primes = [], is_prime = new Array(max+1).fill(true);
    while( true ) {
      for ( i = prev_max + 1; i <= max; i++){
        if ( ! is_prime[i] ) continue;

        primes.push( i );

        if ( primes.length === n )
            return i;

        for ( j = i + i; j <= max; j += i )
            is_prime[j] = false;
      }

      const next_max = max*2;
      is_prime.length = next_max + 1;
      is_prime.fill( true, max + 1, next_max );

      for ( i = 0; i < primes.length; i++ ) {
        const prime = primes[i];
        for ( j = max + prime - max%prime; j <= next_max; j += prime )
          is_prime[j] = false;
      }
      prev_max = max;
      max = next_max;
   }
}

var start = performance.now(); 
console.log(eratosthenes(10001)); 
var end = performance.now();
var time = end - start;

console.log(time);

【讨论】:

  • 这是应用二分查找寻找边界对吗?看起来很有趣。
【解决方案3】:

应 JaromandaX 的要求,这是 Eratosthenes 筛的代码。在我的浏览器上是 51 毫秒(OP 解决方案是 750 毫秒)

var max = 1000000;

function eratosthenes(n) {
   var arr = [], count = 0;
   for (var i = 0; i < max; i++){
   	   arr.push(true);
   }
   for (var i = 2; i < max; i++){
   	   if(arr[i]){
 
   	   	   count++;
   	   	   if(count == n){
 
   	   	   	  return i;
   	   	   } 
   	   	   for (var j = i + i; j < max; j += i ){
   	   	   	    arr[j] = false;
   	   	   }
   	   }
   }
    
}
var start = performance.now(); 
console.log(eratosthenes(10001)); 
var end = performance.now();
var time = end - start;

console.log(time);

【讨论】:

  • 内循环可以优化for (var j = i * i; j &lt; max; j += i) {...}。有了更严格的界限(例如 115000 就像其他答案一样),它然后在我的计算机上运行
  • 我想你会对我的回答感兴趣,它的性能比 גלעד ברקן 的性能稍好,但不依赖于 max 的任何近似值,因此它不是专门为 10001st 工作的素数。
【解决方案4】:

欧拉筛,Pham 知道这个 :) 12ms

Uchu,我看不出你的代码在哪里标记倍数。这不就是埃拉托色尼筛应该做的吗?

JavaScript代码(这段代码其实是btilly对code的改编,优化了我的一个想法):

var start = performance.now();
n = 115000
a = new Array(n+1)
total = 0
s = []
p = 1
count = 0
while (p < n){
  p = p + 1

  if (!a[p]){
    count = count + 1
    if (count == 10001){
      console.log(p);
      end = performance.now();
      time = end - start;

      console.log(time);
      break;
    }
    a[p] = true
    s.push(p)

    limit = n / p
    new_s = []

    for (i of s){
      j = i
      while (j <= limit){
        new_s.push(j)
        a[j*p] = true;
        j = j * p
      }
    }
    s = new_s
  }
}

【讨论】:

  • 不错的答案 :) +1
  • 不 - 必须是特定于浏览器的 - 我猜你正在运行 google chrum
  • Chrum 运行这个答案 (17ms) 比 Pham 的 (107ms) 快 600% - Firefox 运行这个答案 (60ms) 比 Phams (40ms) 慢 50%...
  • 您设置的界限比我的要好(10^5 vs 10^6),这就是我的速度较慢的原因,您可以编辑我的答案并查看差异。但是,由于 10^6 是我最初的猜测,所以我故意将其设置为 :)
  • @PhamTrung 啊好点!这是一个很好的例子,说明 O(n) 时间没有那么快,因为每次迭代都有额外的开销。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-10
  • 2020-12-25
  • 2020-01-22
  • 2020-07-04
  • 1970-01-01
  • 2015-02-12
  • 1970-01-01
相关资源
最近更新 更多