C ++优化此算法

时间:2015-11-03 10:20:57

标签: c++ algorithm math optimization

在观看了一些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。

希望有人对此有明确的答案 - 希望我可以使用在使用大量递归优化程序时收集到的任何知识。

3 个答案:

答案 0 :(得分:4)

问题是'era​​se'将向量中的每个元素向下移动一个,这意味着它是一个O(n)操作。

有三种替代选择:

1)只需将已删除的元素标记为“空”(例如,将它们设为0)。这意味着未来的通行证必须通过那些空头位置,但这并不昂贵。

2)制作一个新的向量,并在那里push_back个新值。

3)使用std :: remove_if:这会将元素向下移动,但只需一次通过即可,因此效率更高。如果你使用std :: remove_if,那么你必须记住它不会调整矢量本身的大小。

答案 1 :(得分:3)

大多数vector操作(包括erase())的线性时间复杂度为O(n)

由于您有两个大小为10^6vector大小为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()表示填充tosievestd::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;
});

现在我们不想标记元素,我们不想在之后复制,而只是直接复制我们要复制的元素。这不仅比上述建议更快,而且更好地说明了意图。