一位同事刚刚告诉我,C#字典集合根据与哈希相关的神秘原因按素数调整大小。而我当前的问题是,“它是如何知道下一个素数是什么?他们是故事还是一个巨大的表格或动态计算?这是一个可怕的非确定性运行时插入导致调整大小”
所以我的问题是,给定N,这是一个素数,计算下一个素数的最有效方法是什么?
答案 0 :(得分:72)
大约一年前,我正在为libc++工作这个区域 C ++ 11的无序(哈希)容器。我想我会分享 我在这里的经历。此体验支持marcog's accepted answer 合理定义“暴力”。
这意味着即使是简单的蛮力也会在大多数时候足够快 情况,平均取O(ln(p)* sqrt(p))。
我开发了几个size_t next_prime(size_t n)
的实现
这个函数的规范是:
返回:大于或等于
n
的最小素数。
next_prime
的每个实现都附带有辅助函数is_prime
。 is_prime
应被视为私人实施细节;并不意味着客户直接打电话。当然,这些实现中的每一个都经过了正确性测试
通过以下性能测试进行测试:
int main()
{
typedef std::chrono::high_resolution_clock Clock;
typedef std::chrono::duration<double, std::milli> ms;
Clock::time_point t0 = Clock::now();
std::size_t n = 100000000;
std::size_t e = 100000;
for (std::size_t i = 0; i < e; ++i)
n = next_prime(n+1);
Clock::time_point t1 = Clock::now();
std::cout << e/ms(t1-t0).count() << " primes/millisecond\n";
return n;
}
我应该强调这是一次性能测试,并不反映典型情况 用法,看起来更像是:
// Overflow checking not shown for clarity purposes
n = next_prime(2*n + 1);
所有性能测试均编译为:
clang++ -stdlib=libc++ -O3 main.cpp
实施1
有七个实现。显示第一个的目的
实施是为了证明如果你未能停止测试候选人
对于x
的因素,sqrt(x)
为bool
is_prime(std::size_t x)
{
if (x < 2)
return false;
for (std::size_t i = 2; i < x; ++i)
{
if (x % i == 0)
return false;
}
return true;
}
std::size_t
next_prime(std::size_t x)
{
for (; !is_prime(x); ++x)
;
return x;
}
,那么您甚至无法达到
实施可归类为蛮力。这个实现是
残酷地慢。
e
对于这个实现,我只需将0.0015282 primes/millisecond
设置为100而不是100000,只需要
获得合理的运行时间:
sqrt(x)
实施2
此实现是强力实施中最慢的
与实现1的唯一区别是它停止测试初始性
当因子超过bool
is_prime(std::size_t x)
{
if (x < 2)
return false;
for (std::size_t i = 2; true; ++i)
{
std::size_t q = x / i;
if (q < i)
return true;
if (x % i == 0)
return false;
}
return true;
}
std::size_t
next_prime(std::size_t x)
{
for (; !is_prime(x); ++x)
;
return x;
}
时。
sqrt(x)
请注意,q < i
不是直接计算的,而是由5.98576 primes/millisecond
推断的。这个
将事情加快了几千倍:
%
并验证了marcog的预测:
......这完全在约束之内 大多数问题依赖于 大多数现代硬件上的毫秒级。
实施3
速度几乎可以加倍(至少在我使用的硬件上)
避免使用bool
is_prime(std::size_t x)
{
if (x < 2)
return false;
for (std::size_t i = 2; true; ++i)
{
std::size_t q = x / i;
if (q < i)
return true;
if (x == q * i)
return false;
}
return true;
}
std::size_t
next_prime(std::size_t x)
{
for (; !is_prime(x); ++x)
;
return x;
}
11.0512 primes/millisecond
运算符:
bool
is_prime(std::size_t x)
{
for (std::size_t i = 3; true; i += 2)
{
std::size_t q = x / i;
if (q < i)
return true;
if (x == q * i)
return false;
}
return true;
}
std::size_t
next_prime(std::size_t x)
{
if (x <= 2)
return 2;
if (!(x & 1))
++x;
for (; !is_prime(x); x += 2)
;
return x;
}
21.9846 primes/millisecond
实施4
到目前为止,我甚至没有使用2是唯一的素数的常识。 这种实现融合了这些知识,速度几乎翻了一番 再次:
6 * k + {1, 5}
实施4可能是大多数人在思考时所想到的 “蛮力”。
实施5
使用以下公式,您可以轻松选择所有数字 既不被2也不能被3整除:
bool
is_prime(std::size_t x)
{
std::size_t o = 4;
for (std::size_t i = 5; true; i += o)
{
std::size_t q = x / i;
if (q < i)
return true;
if (x == q * i)
return false;
o ^= 6;
}
return true;
}
std::size_t
next_prime(std::size_t x)
{
switch (x)
{
case 0:
case 1:
case 2:
return 2;
case 3:
return 3;
case 4:
case 5:
return 5;
}
std::size_t k = x / 6;
std::size_t i = x - 6 * k;
std::size_t o = i < 2 ? 1 : 5;
x = 6 * k + o;
for (i = (3 + o) / 2; !is_prime(x); x += i)
i ^= 6;
return x;
}
其中k&gt; = 1.以下实现使用此公式,但已实现 用可爱的xor技巧:
32.6167 primes/millisecond
这实际上意味着算法必须只检查1/3 对于素数而不是1/2的数字和性能测试的整数 显示预期加速率接近50%:
30 * k + {1, 7, 11, 13, 17, 19, 23, 29}
实施6
此实现是实现5的逻辑扩展:它使用 以下公式计算所有不能被2,3和5整除的数字:
static const std::size_t small_primes[] =
{
2,
3,
5,
7,
11,
13,
17,
19,
23,
29
};
static const std::size_t indices[] =
{
1,
7,
11,
13,
17,
19,
23,
29
};
bool
is_prime(std::size_t x)
{
const size_t N = sizeof(small_primes) / sizeof(small_primes[0]);
for (std::size_t i = 3; i < N; ++i)
{
const std::size_t p = small_primes[i];
const std::size_t q = x / p;
if (q < p)
return true;
if (x == q * p)
return false;
}
for (std::size_t i = 31; true;)
{
std::size_t q = x / i;
if (q < i)
return true;
if (x == q * i)
return false;
i += 6;
q = x / i;
if (q < i)
return true;
if (x == q * i)
return false;
i += 4;
q = x / i;
if (q < i)
return true;
if (x == q * i)
return false;
i += 2;
q = x / i;
if (q < i)
return true;
if (x == q * i)
return false;
i += 4;
q = x / i;
if (q < i)
return true;
if (x == q * i)
return false;
i += 2;
q = x / i;
if (q < i)
return true;
if (x == q * i)
return false;
i += 4;
q = x / i;
if (q < i)
return true;
if (x == q * i)
return false;
i += 6;
q = x / i;
if (q < i)
return true;
if (x == q * i)
return false;
i += 2;
}
return true;
}
std::size_t
next_prime(std::size_t n)
{
const size_t L = 30;
const size_t N = sizeof(small_primes) / sizeof(small_primes[0]);
// If n is small enough, search in small_primes
if (n <= small_primes[N-1])
return *std::lower_bound(small_primes, small_primes + N, n);
// Else n > largest small_primes
// Start searching list of potential primes: L * k0 + indices[in]
const size_t M = sizeof(indices) / sizeof(indices[0]);
// Select first potential prime >= n
// Known a-priori n >= L
size_t k0 = n / L;
size_t in = std::lower_bound(indices, indices + M, n - k0 * L) - indices;
n = L * k0 + indices[in];
while (!is_prime(n))
{
if (++in == M)
{
++k0;
in = 0;
}
n = L * k0 + indices[in];
}
return n;
}
它还在is_prime中展开内部循环,并创建一个“小”的列表 素数“对于处理小于30的数字很有用。
41.6026 primes/millisecond
这可以说超越了“蛮力”,有利于提升 再加速27.5%:
210 * k + {1, 11, ...},
实施7
实现上述游戏再进行一次迭代,开发一个 不能被2,3,5和7整除的数字的公式:
47.685 primes/millisecond
此处未显示源代码,但与实现6非常相似。 这是我选择实际用于无序容器的实现 libc++的源代码,源代码是开源的(在链接中找到)。
这最后一次迭代有利于另外14.6%的速度提升:
{{1}}
使用此算法可确保libc++的哈希表的客户端可以选择 他们决定的任何素数都对他们的情况和表现最有利 对于这个应用程序是完全可以接受的。
答案 1 :(得分:43)
以防万一有人好奇:
使用反射器我确定.Net使用一个静态类,其中包含〜72个素数的硬编码列表,范围高达7199369,扫描的最小素数至少是当前大小的两倍,对于大于此值的大小它通过对所有奇数的试验除法计算下一个素数,直到潜在数的sqrt。这个类是不可变的和线程安全的(即不存储更大的素数以供将来使用)。
答案 2 :(得分:34)
已知gaps between consecutive prime numbers非常小,第一个缺口超过100,发生在素数370261.这意味着即使是一个简单的蛮力在大多数情况下也会足够快,取O(ln( p)* sqrt(p))平均。
对于p = 10,000,这是O(921)操作。请记住,每次插入O(ln(p))时我们都会执行此操作(粗略地说),这大多数都是在大多数现代硬件上大多数问题的约束下。
答案 3 :(得分:12)
一个很好的技巧是使用部分筛子。例如,在数字N = 2534536543556之后的下一个素数是什么?
检查N的模数与小素数列表的关系。因此...
mod(2534536543556,[3 5 7 11 13 17 19 23 29 31 37])
ans =
2 1 3 6 4 1 3 4 22 16 25
我们知道N之后的下一个素数必须是奇数,我们可以立即丢弃这个小素数列表的所有奇数倍。这些模数允许我们筛选出那些小素数的倍数。如果我们使用高达200的小素数,我们可以使用这个方案立即丢弃大于N的大多数潜在素数,除了一个小列表。
更明确地说,如果我们正在寻找超过2534536543556的素数,它不能被2整除,所以我们只需要考虑超出该值的奇数。上面的模数显示2534536543556与2 mod 3一致,因此2534536543556 + 1与0 mod 3一致,因为必须是2534536543556 + 7,2534536543556 + 13等。实际上,我们可以筛选出许多数字而无需任何需要测试它们的原始性,没有任何试验分裂。
同样,事实是
mod(2534536543556,7) = 3
告诉我们2534536543556 + 4与0 mod 7是一致的。当然,这个数字是偶数,所以我们可以忽略它。但是2534536543556 + 11是一个可以被7整除的奇数,2534536543556 + 25等等。同样,我们可以将这些数字排除为明显复合(因为它们可以被7整除),因此不是素数。
仅使用最多37个素数的小列表,我们可以排除紧跟我们起点2534536543556的大部分数字,只有少数几个:
{2534536543573 , 2534536543579 , 2534536543597}
在这些数字中,他们是否是素数?
2534536543573 = 1430239 * 1772107
2534536543579 = 99833 * 25387763
我努力提供列表中前两个数字的主要因素分解。看到它们是复合的,但主要因素很大。当然,这是有道理的,因为我们已经确保剩下的数字不会有小的素因子。我们的短名单中的第三个(2534536543597)实际上是超过N的第一个素数。我所描述的筛选方案将倾向于产生素数,或者由通常较大的素因子组成。因此,在找到下一个素数之前,我们需要实际将一个显式的primality测试应用于少数几个。
类似的方案很快就会产生超过N = 1000000000000000000000000000的下一个素数,即1000000000000000000000000103。
答案 4 :(得分:12)
这是对其他答案进行可视化的补充。
我从100.000th(= 1,299,709)到200.000th(= 2,750,159)获得了素数
一些数据:
Maximum interprime distance = 148
Mean interprime distance = 15
Interprime距离频率图:
Interprime Distance vs Prime Number
只是看它是“随机的”。 However ...
答案 5 :(得分:5)
没有函数f(n)来计算下一个素数。相反,必须测试一个数字的素数。
在找到第n个素数时,从第1个到第(n-1)个已经知道所有素数也是非常有用的,因为这些是唯一需要作为因子进行测试的数字。
由于这些原因,如果有一组预先计算的大素数,我不会感到惊讶。如果某些素数需要反复重新计算,对我来说真的没有意义。
答案 6 :(得分:3)
正如其他人已经指出的那样,尚未找到一种在给定当前素数的情况下找到下一个素数的方法。因此,大多数算法更多地关注使用快速检查primality的方法,因为您必须检查已知素数和下一个素数之间的数字的n / 2。
如Paul Wheeler所示,根据应用程序的不同,您还可以轻松编写查找表的硬编码。
答案 7 :(得分:3)
纯粹的新颖性,总有这种方法:
#!/usr/bin/perl
for $p ( 2 .. 200 ) {
next if (1x$p) =~ /^(11+)\1+$/;
for ($n=1x(1+$p); $n =~ /^(11+)\1+$/; $n.=1) { }
printf "next prime after %d is %d\n", $p, length($n);
}
当然会产生
next prime after 2 is 3
next prime after 3 is 5
next prime after 5 is 7
next prime after 7 is 11
next prime after 11 is 13
next prime after 13 is 17
next prime after 17 is 19
next prime after 19 is 23
next prime after 23 is 29
next prime after 29 is 31
next prime after 31 is 37
next prime after 37 is 41
next prime after 41 is 43
next prime after 43 is 47
next prime after 47 is 53
next prime after 53 is 59
next prime after 59 is 61
next prime after 61 is 67
next prime after 67 is 71
next prime after 71 is 73
next prime after 73 is 79
next prime after 79 is 83
next prime after 83 is 89
next prime after 89 is 97
next prime after 97 is 101
next prime after 101 is 103
next prime after 103 is 107
next prime after 107 is 109
next prime after 109 is 113
next prime after 113 is 127
next prime after 127 is 131
next prime after 131 is 137
next prime after 137 is 139
next prime after 139 is 149
next prime after 149 is 151
next prime after 151 is 157
next prime after 157 is 163
next prime after 163 is 167
next prime after 167 is 173
next prime after 173 is 179
next prime after 179 is 181
next prime after 181 is 191
next prime after 191 is 193
next prime after 193 is 197
next prime after 197 is 199
next prime after 199 is 211
除了所有有趣和游戏之外,众所周知,最佳哈希表大小严格证明形式为4N−1
的素数。所以只是找到下一个素数是不够的。你也必须做另一张检查。
答案 8 :(得分:0)
据我记忆,它使用当前大小的两倍旁边的素数。它没有计算出素数 - 那里有预加载数字的表达到一个大的值(不完全是,大约10,000,000左右)。当达到该数字时,它使用一些天真的算法来获得下一个数字(如curNum = curNum + 1)并使用以下方法验证它:http://en.wikipedia.org/wiki/Prime_number#Verifying_primality