在观看了一些Terence Tao视频后,我想尝试将算法应用到c ++代码中,以找到所有素数到n的数字。在我的第一个版本中,我只是测试了从2到n的每个整数,看看它们是否可被2到sqrt(n)之间的任何整数整除,我得到程序在~52秒内找到1-10,000,000之间的素数。 / p>
试图优化程序,并实现我现在知道的Eratosthenes筛选,我认为任务将比51秒快得多,但遗憾的是,事实并非如此。即使上升到1,000,000也需要花费相当长的时间(不过没时间)
#include <iostream>
#include <vector>
using namespace std;
void main()
{
vector<int> tosieve = {};
for (int i = 2; i < 1000001; i++)
{
tosieve.push_back(i);
}
for (int j = 0; j < tosieve.size(); j++)
{
for (int k = j + 1; k < tosieve.size(); k++)
{
if (tosieve[k] % tosieve[j] == 0)
{
tosieve.erase(tosieve.begin() + k);
}
}
}
//for (int f = 0; f < tosieve.size(); f++)
//{
// cout << (tosieve[f]) << endl;
//}
cout << (tosieve.size()) << endl;
system("pause");
}
是否重复引用向量或其他东西?为什么这么慢?即使我完全忽略了某些东西(可能是完全初学者:I)我会认为用这种可怕的低效方法找到2到1,000,000之间的素数比我原来从2到找到它们的方式要快10,000,000。
希望有人对此有明确的答案 - 希望我可以使用在使用大量递归优化程序时收集到的任何知识。
答案 0 :(得分:4)
问题是'erase'将向量中的每个元素向下移动一个,这意味着它是一个O(n)操作。
有三种替代选择:
1)只需将已删除的元素标记为“空”(例如,将它们设为0)。这意味着未来的通行证必须通过那些空头位置,但这并不昂贵。
2)制作一个新的向量,并在那里push_back
个新值。
3)使用std :: remove_if:这会将元素向下移动,但只需一次通过即可,因此效率更高。如果你使用std :: remove_if,那么你必须记住它不会调整矢量本身的大小。
答案 1 :(得分:3)
大多数vector
操作(包括erase()
)的线性时间复杂度为O(n)
。
由于您有两个大小为10^6
且vector
大小为10^6
的循环,因此您的算法最多可执行10^18
次操作。
如此庞大N
的Qubic算法需要花费大量时间
N
= 10^6
对于二次算法来说甚至足够大。
请仔细阅读 Sieve of Eratosthenes 。事实上,完全搜索和Eratosthenes算法的筛选花费了相同的时间,意味着你完成了第二个错误。
答案 2 :(得分:2)
我在这里看到两个性能问题:
首先,push_back()
必须偶尔重新分配动态内存块。使用reserve()
:
vector<int> tosieve = {};
tosieve.resreve(1000001);
for (int i = 2; i < 1000001; i++)
{
tosieve.push_back(i);
}
第二个erase()
必须将所有元素移到您尝试删除的元素后面。您将元素设置为0,然后在向量上运行向量(未经测试的代码):
for (auto& x : tosieve) {
for (auto y = tosieve.begin(); *y < x; ++y) // this check works only in
// the case of an ordered vector
if (y != 0 && x % y == 0) x = 0;
}
{ // this block will make sure, that sieved will be released afterwards
auto sieved = vector<int>{};
for(auto x : tosieve)
sieved.push_back(x);
swap(tosieve, sieved);
} // the large memory block is released now, just keep the sieved elements.
考虑使用标准算法而不是手写循环。它们可以帮助您说明您的意图。在这种情况下,我看到std::transform()
表示筛网的外圈,std::any_of()
表示内圈,std::generate_n()
表示填充tosieve
,std::copy_if()
表示填充sieved
(未经测试的代码):
vector<int> tosieve = {};
tosieve.resreve(1000001);
generate_n(back_inserter(tosieve), 1000001, []() -> int {
static int i = 2; return i++;
});
transform(begin(tosieve), end(tosieve), begin(tosieve), [](int i) -> int {
return any_of(begin(tosieve), begin(tosieve) + i - 2,
[&i](int j) -> bool {
return j != 0 && i % j == 0;
}) ? 0 : i;
});
swap(tosieve, [&tosieve]() -> vector<int> {
auto sieved = vector<int>{};
copy_if(begin(tosieve), end(tosieve), back_inserter(sieved),
[](int i) -> bool { return i != 0; });
return sieved;
});
修改强>
另一种完成任务的方法:
vector<int> tosieve = {};
tosieve.resreve(1000001);
generate_n(back_inserter(tosieve), 1000001, []() -> int {
static int i = 2; return i++;
});
swap(tosieve, [&tosieve]() -> vector<int> {
auto sieved = vector<int>{};
copy_if(begin(tosieve), end(tosieve), back_inserter(sieved),
[](int i) -> bool {
return !any_of(begin(tosieve), begin(tosieve) + i - 2,
[&i](int j) -> bool {
return i % j == 0;
});
});
return sieved;
});
现在我们不想标记元素,我们不想在之后复制,而只是直接复制我们要复制的元素。这不仅比上述建议更快,而且更好地说明了意图。