找出N和M之间的每个数字可以表示为一对素数之和

时间:2015-08-30 03:37:41

标签: java algorithm math optimization time-complexity

考虑这种方法:

public static int[] countPairs(int min, int max) {
    int lastIndex = primes.size() - 1;
    int i = 0;
    int howManyPairs[] = new int[(max-min)+1];

    for(int outer : primes) {
        for(int inner : primes.subList(i, lastIndex)) {
            int sum = outer + inner;

            if(sum > max)
                break;

            if(sum >= min && sum <= max)
                howManyPairs[sum - min]++;
        }

        i++;
    }

    return howManyPairs;
}

如您所见,我必须计算最小值和最大值之间的每个数字可以表示为两个素数之和的次数。

primes是一个ArrayList,所有素数在2到2000000之间。在这种情况下,min是1000000而max是2000000,这就是素数到2000000的原因。

我的方法很好,但这里的目标是更快地做点什么。

我的方法需要两个循环,一个在另一个内部,它使我的算法成为O(n²)。它很像Bubbleort。

如何重写我的代码以更好的复杂性来完成相同的结果,比如O(nlogn)?

最后一件事:我用Java编写代码,但您的回复也可以是Python,VB.Net,C#,Ruby,C,甚至只是英文解释。

4 个答案:

答案 0 :(得分:3)

对于xmin之间的每个数字max,我们希望计算x可以写为两个素数之和的方式的数量。这个数字也可以表示为

sum(prime(n)*prime(x-n) for n in xrange(x+1))

如果x为素数,则prime(x)为1,否则为0。我们不考虑两个素数加起来x的方式的数量,而是考虑两个非负整数加起来x的所有方式,如果两个整数是素数,则将总和加1。

这不是一种更有效的计算方法。但是,将它置于这种形式有助于我们认识到我们想要的输出是两个序列的discrete convolution。具体来说,如果pp[x] == prime(x)的无限序列,则p与其自身的卷积就是这样的序列

convolve(p, p)[x] == sum(p[n]*p[x-n] for n in xrange(x+1))

或代替p的定义,

convolve(p, p)[x] == sum(prime(n)*prime(x-n) for n in xrange(x+1))

换句话说,将p与自身进行卷积会产生我们想要计算的数字序列。

计算卷积的简单方法几乎就是你正在做的事情,但有更快的方法。对于n - 元素序列,基于fast Fourier transform的算法可以在O(n*log(n))时间而不是O(n**2)时间内计算卷积。不幸的是,这是我的解释结束的地方。即使你有适当的数学符号,快速傅立叶变换也很难解释,而且我对Cooley-Tukey算法的记忆并不像我想的那样精确,我可以&#39真的是公正的。

如果您想详细了解convolutionFourier transforms,尤其是Cooley-Tukey FFT algorithm,我刚刚关联的维基百科文章将是一个不错的开端。如果您只是想使用更快的算法,那么最好的办法就是获得一个可以执行此操作的库。在Python中,我知道scipy.signal.fftconvolve会完成这项工作;在其他语言中,您可以通过您选择的搜索引擎很快找到一个库。

答案 1 :(得分:2)

您搜索的是每个号码Goldbach partitions的计数 在你的范围内,imho没有有效的算法。

不均匀数字为0,低于4*10^18的偶数数字保证超过0,
但除此之外......首先,如果存在0分区的偶数(大于4*10^18) 自1700年以来,这是一个未解决的问题,而确切数字这样的事情则更为复杂。

有一些渐近和启发式解决方案,但如果你想要确切的数字,
除了获得更多的CPU和RAM之外,你无能为力。

答案 2 :(得分:2)

其他答案有一个从N到M的外部循环。然而,外部循环(或循环)是素数对的效率更高,用于构建N和M之间相等的数字列表他们的总和。

由于我不了解Java,我将在Ruby中为特定示例提供解决方案。这应该允许任何有兴趣在Java中实现该算法的人,无论他们是否了解Ruby。

我最初假设产品等于M和N之间的数字的两个素数必须是唯一的。换句话说,4不能表达为4 = 2+2

使用Ruby的素数库。

require 'prime'

假设M和N分别为5和50。

lower =  5
upper = 50

计算最高为upper-2 #=> 48的素数,2为第一个素数。

primes = Prime.each.take_while { |p| p < upper-2 }
  #=> [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]

构造两个素数的所有组合的枚举器。

enum = primes.combination(2)
  => #<Enumerator: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]:combination(2)>

我们可以看到这个枚举器通过将它转换为数组而生成的元素。

enum.to_a
  #=> [[2, 3], [2, 5],..., [2, 47], [3, 5],..., [43, 47]] (105 elements)

只需将enum视为数组。

现在构造一个计数哈希,其键是lowerupper之间的数字,其中至少有一对素数与该数字相加,其值是素数对的数量加上相关键的值。

enum.each_with_object(Hash.new(0)) do |(x,y),h|
  sum = x+y
  h[sum] += 1 if (lower..upper).cover?(sum)
end
  #=> {5=>1, 7=>1, 9=>1, 13=>1, 15=>1, 19=>1, 21=>1, 25=>1, 31=>1, 33=>1,
  #    39=>1, 43=>1, 45=>1, 49=>1, 8=>1, 10=>1, 14=>1, 16=>2, 20=>2, 22=>2,
  #    26=>2, 32=>2, 34=>3, 40=>3, 44=>3, 46=>3, 50=>4, 12=>1, 18=>2, 24=>3,
  #    28=>2, 36=>4, 42=>4, 48=>5, 30=>3, 38=>1} 

这表明,有两种方法16可以表示为两个素数的总和(3+135+11),{{1}有三种方式(343+315+29),11+23无法使用。{/ p>

如果要求的两个素数不必是唯一的(例如,要包括6),则只需要稍作改动。

4=2+2

,其排序值为

arr = primes.combination(2).to_a.concat(primes.zip(primes))

然后

a = arr.sort
  #=> [[2, 2], [2, 3], [2, 5], [2, 7],..., [3, 3],..., [5, 5],.., [47, 47]] (120 elements) 

a.each_with_object(Hash.new(0)) do |(x,y),h| sum = x+y h[sum] += 1 if (lower..upper).cover?(sum) end #=> {5=>1, 7=>1, 9=>1, 13=>1, 15=>1, 19=>1, 21=>1, 25=>1, 31=>1, 33=>1, # 39=>1, 43=>1, 45=>1, 49=>1, 6=>1, 8=>1, 10=>2, 14=>2, 16=>2, 20=>2, # 22=>3, 26=>3, 32=>2, 34=>4, 40=>3, 44=>3, 46=>4, 50=>4, 12=>1, 18=>2, # 24=>3, 28=>2, 36=>4, 42=>4, 48=>5, 30=>3, 38=>2} 应替换为a。我在这里使用arr只是为了对结果哈希的元素进行排序,以便更容易阅读。

由于我只是想描述这种方法,我使用强力方法来枚举a的元素对,抛弃120对素数中的44对,因为它们的总和超出范围{{1 (primes)。显然,还有很大的改进空间。

答案 3 :(得分:1)

两个素数之和表示N = A + B,其中AB是素数,A < B表示A < N / 2B > N / 2 。请注意,它们不能等于N / 2

因此,您的外部循环应该只从1循环到floor((N - 1) / 2)。在整数数学中,floor是自动的。

如果素数存储在Set中,则可以消除内部循环。假设您的数组已排序(公平假设),请使用LinkedHashSet,以便在外部循环中迭代该集可以在(N - 1) / 2处停止。

我会留给你编码。

<强>更新

很抱歉,以上内容是针对特定A查找BN的问题的答案。您的问题是在Nmin(包括)之间找到所有max

如果您遵循上述逻辑,您应该能够将其应用于您的问题。

外圈应该从1max / 2 内循环应该从min - outermax - outer

要找到内部循环的起点,可以保留一些额外的索引变量,或者可以依赖于排序的主数组并使用Arrays.binarySearch(primes, min - outer)。第一种选择可能会快一点,但第二种选择肯定更简单。