找到素数的位置

时间:2013-01-02 18:07:43

标签: c algorithm math primes sieve

我需要反过来找到第N个素数,即给定素数,我需要找到它的位置

2, 3, 5, 7...

素数可以很大,大约为10^7。此外,还有很多。

我有一个可以二进制搜索的预先计算的素数索引,但我也有50k的空间限制!可以筛选吗?或者任何其他快速方式?

修改: 非常感谢所有精彩的答案,我没想到他们!我希望他们对寻找同样的人有用。

7 个答案:

答案 0 :(得分:8)

你的范围只有一千万,这对于这种事情来说很小。我有两个建议:

1)以方便的间隔创建pi(n)表,然后使用分段的Eratosthenes筛来计算包含所需值的两个表条目之间的素数。间隔的大小决定了所需表的大小和计算结果的速度。

2)使用Legendre的phi(x,a)函数和Lehmer的计数公式直接计算结果。 phi功能需要一些存储,我不确定多少。

在这两个中,我可能会根据您的问题大小选择第一个替代方案。我的博客上提供了segmented Sieve of EratosthenesLehmer's计数功能的实现。

编辑1:

经过反思,我有第三种选择:

3)使用对数积分估计pi(n)。它是单调增加的,并且在你需要的时间间隔内总是大于pi(n)。但差异很小,从不超过200左右。因此,您可以预先计算所有小于千万的值的差异,制作200个变化点的表格,然后在请求时计算对数积分并查找校正因子表。或者你可以用Riemann的R函数做类似的事情。

第三种替代方案占用的空间最少,但我怀疑第一种替代方案所需的空间不会太大,筛选可能比计算对数积分更快。所以我会坚持原来的建议。在my blog处实现了对数积分和Riemann R函数。

编辑2:

正如评论所指出的那样,这并没有很好地发挥作用。请忽略我的第三个建议。

作为我在提出一个不起作用的解决方案时出错的忏悔,我写了一个程序,它使用pi(n)的值表和一个分段的Eratosthenes筛来计算pi(n)的值。 n< 10000000.我将使用Python,而不是原始海报所要求的C,因为Python更容易阅读以便用于教学目的。

我们首先计算筛分质量小于千万的平方根;这些素数将用于构建pi(n)的值表和执行计算最终答案的筛子。一千万的平方根是3162.3。我们不想使用2作为筛选素数 - 我们只筛选奇数,并将2视为特殊情况 - 但我们确实希望下一个素数大于平方根,所以筛选质数列表永远不会用尽(这会导致错误)。所以我们使用这个非常简单的Eratosthenes筛子来计算筛分质数:

def primes(n):
    b, p, ps = [True] * (n+1), 2, []
    for p in xrange(2, n+1):
        if b[p]:
            ps.append(p)
            for i in xrange(p, n+1, p):
                b[i] = False
    return ps

Eratosthenes的筛子分为两部分。首先,从2开始,列出小于目标数的数字。然后,从第一个未交叉的数字开始重复运行列表,并从列表中删除所有数字的倍数。最初,2是第一个未交叉的数字,因此交叉4,6,8,10等等。然后3是下一个未交叉的数字,因此交叉6,9,12,15,依此类推。然后将4作为2的倍数划掉,下一个未交叉的数字是5,所以划掉10,15,20,25等等。继续,直到考虑所有未交叉的数字;保持未交叉的数字是素数。 p上的循环依次考虑每个数字,如果它没有交叉,则i上的循环越过多个数。

primes函数返回447个素数的列表:2,3,5,7,11,13,...,3121,3137,3163。我们从列表中击出2并存储446个筛选素数在全局ps变量中:

ps = primes(3163)[1:]

我们需要的主要功能是计算范围上的素数。它使用一个筛子,我们将存储在一个全局数组中,以便它可以重复使用,而不是在每次调用count函数时重新分配:

sieve = [True] * 500

count函数使用分段的Eratosthenes筛来计算从lo到hi的范围内的素数(lo和hi都包含在范围内)。该函数有四个for循环:第一个清除筛子,最后一个计算质数,另外两个执行筛分,方式类似于上面显示的简单筛子:

def count(lo, hi):
    for i in xrange(500):
        sieve[i] = True
    for p in ps:
        if p*p > hi: break
        q = (lo + p + 1) / -2 % p
        if lo+q+q+1 < p*p: q += p
        for j in xrange(q, 500, p):
            sieve[j] = False
    k = 0
    for i in xrange((hi - lo) // 2):
        if sieve[i]: k += 1
    return k

该函数的核心是执行筛分的循环for p in ps,依次取每个筛分素数。当筛分素数的平方大于范围的极限时,循环终止,因为所有质数将在该点被识别(我们需要比平方根更大的下一个素数的原因是为了有筛选素数停止循环)。神秘变量q是在lo到hi范围内p的最小倍数的筛子的偏移(注意它不是范围中p的最小倍数,而是p的最小倍数的偏移的索引)范围,这可能令人困惑)。当if语句引用一个完美正方形的数字时,count语句会增加q。然后j上的循环从筛子上击中p的倍数。

我们以两种方式使用piTable = [0] * 10000 函数。第一次使用建立一个pi(n)值的表,其值为1000的倍数;第二次使用在表格内插入。我们将表存储在全局变量piTable中:

piTable

我们根据原始请求选择参数1000和10000,以将内存使用量保持在50千字节以内。 (是的,我知道原始的海报放宽了这个要求。但我们无论如何都可以尊重它。)一万个32位整数将占用40,000字节的存储空间,并且从lo到hi的1000范围内进行筛选只需要500字节存储将非常快。您可能希望尝试其他参数以查看它们如何影响程序的空间和时间使用情况。通过调用count函数一万次来构建for i in xrange(1, 10000): piTable[i] = piTable[i-1] + \ count(1000 * (i-1), 1000 * i)

def pi(n):
    if type(n) != int and type(n) != long:
        raise TypeError('must be integer')
    if n < 2: return 0
    if n == 2: return 1
    i = n // 1000
    return piTable[i] + count(1000 * i, n+1)

到目前为止的所有计算都可以在编译时而不是运行时完成。当我在ideone.com进行这些计算时,它们花了大约五秒钟,但是那个时间不算数,因为当程序员首次编写代码时,它可以一直进行一次。作为一般规则,您应该寻找机会将代码从运行时间移动到编译时,以使程序运行得非常快。

唯一剩下的就是编写实际计算小于或等于n的素数的函数:

if

第一个if语句进行类型检查。第二个if语句返回对荒谬输入的正确响应。第三个print pi(6543223) 语句专门处理2个;我们的筛分使1成为素数和2个复合物,两者都是不正确的,所以我们在这里进行修复。然后i被计算为piTable的最大索引小于请求的n,并且return语句将piTable中的值添加到表值和请求值之间的素数计数中; hi限制是n + 1,因为否则在n是素数的情况下,它将不被计数。举个例子,说:

pi

将导致号码447519显示在终端上。

{{1}}功能非常快。在ideone.com,在大约半秒内计算出对pi(n)的一千个随机调用,因此每个大约半毫秒;其中包括生成素数的时间和结果的总和,因此实际计算pi函数的时间甚至不到半毫秒。这对我们建立赌桌的投资来说是一个相当不错的回报。

如果您对使用素数进行编程感兴趣,我已经在我的blog上完成了相当多的工作。请来参观。

答案 1 :(得分:4)

如果你知道输入是素数的先验,你可以使用近似pi(n)≈n/ log n和一个小的固定点表来获得素数,其中舍入结果不足以获得正确的值n。我认为除了缓慢的蛮力方法之外,这是你在尺寸限制范围内最好的选择。

答案 2 :(得分:3)

我建议启发式混合模型可以在这里工作。存储每个第n个素数,然后通过素性测试进行线性搜索。为了加快速度,您可以使用快速简单的素性测试(如使用a==2的Fermat测试)并预先计算误报。根据输入的最大大小和存储限制进行一些微调应该很容易解决。

答案 3 :(得分:2)

这里有一些有用的代码。您应该使用适用于您的输入范围的确定性Miller-Rabin测试替换基于试验除法的素性测试。筛选找到适当小范围的质数将比试验分割更好,但这是朝着错误方向迈出的一步。

#include <stdio.h>
#include <bitset>
using namespace std;

short smallprimes[549]; // about 1100 bytes
char in[19531]; // almost 20k

// Replace me with Miller-Rabin using 2, 7, and 61.
int isprime(int j) {
 if (j<3) return j==2;
 for (int i = 0; i < 549; i++) {
  int p = smallprimes[i];
  if (p*p > j) break;
  if (!(j%p)) return 0;
 }
 return 1;
}

void init() {
 bitset<4000> siv;
 for (int i = 2; i < 64; i++) if (!siv[i])
  for (int j = i+i; j < 4000; j+=i) siv[j] = 1;
 int k = 0;
 for (int i = 3; i < 4000; i+=2) if (!siv[i]) {
  smallprimes[k++] = i;
 }

 for (int a0 = 0; a0 < 10000000; a0 += 512) {
  in[a0/512] = !a0;
  for (int j = a0+1; j < a0+512; j+=2)
   in[a0/512] += isprime(j);
 }
}

int whichprime(int k) {
 if (k==2) return 1;
 int a = k/512;
 int ans = 1 + !a;
 for (int i = 0; i < a; i++) ans += in[i];
 for (int i = a*512+1; i<k; i+=2) ans += isprime(i);
 return ans;
}

int main() {
 int k;
 init();
 while (1 == scanf("%i", &k)) printf("%i\n", whichprime(k));
}

答案 4 :(得分:1)

以下听起来像您正在寻找的。 http://www.geekviewpoint.com/java/numbers/index_of_prime。在那里你会找到代码和单元测试。由于您的列表相对较小(即10^7),因此应该处理它。

基本上,您会找到2n之间的所有素数,然后计算小于n的所有素数以查找索引。此外,如果n不是素数,则函数返回-1

答案 5 :(得分:0)

你的建议是最好的。预计算(或download)小于10 ^ 7的素数列表,然后二进制搜索它们。

只有664579个素数少于10 ^ 7,因此该列表将占用~2.7 MB的空间。解决每个实例的二进制搜索将是超级快速的 - 只有~20次操作。

答案 6 :(得分:0)

我做过一次。写了一个代码,给定n,可以快速找到第n个素数,最多n = 203542528,所以约为2e8。或者,它可以向后,对于任何数字n,可以告诉多少素数小于n。

采用数据库。我将所有素数存储到某一点(我的上限的sqrt。)在你的情况下,这意味着你将所有素数存储到sqrt(1e7)。其中有446个,您可以以压缩形式存储该列表,因为到该点的最大差异仅为34.超过该点,存储每个第k个素数(对于某个k值)。然后快速筛子就足够了在短时间内生成所有素数。

所以在MATLAB中,找到1e7'th prime:

nthprime(1e7)
ans =
   179424673

或者,它可以找到小于1e7的素数:

nthprime(1e7,1)
ans =
      664579

关键是,这样的数据库易于构建和搜索。如果你的数据库不超过50k,那应该没问题。