针对大数的高效Prime分解

时间:2014-10-13 15:54:29

标签: c++ multithreading algorithm primes prime-factoring

我一直在研究一个小问题,我需要将18位数字计算到各自的素数分解中。考虑到它确实有效,所有的东西都会编译并运行得很好,但我希望减少素数分解的运行时间。我已经实现了递归和线程,但我想我可能需要一些帮助来理解大数计算的可能算法。

每次我在预制的4个数字上运行时,大约需要10秒钟。如果有任何想法,我想将其减少到0.06秒。

我注意到了一些像Sieve of Eratosthenes这样的算法,并在计算之前生成了所有素数的列表。我只是想知道是否有人可以详细说明。例如,我在理解如何将Eratosthenes的Sieve实现到我的程序中或者它是否是一个好主意时遇到了问题。关于如何更好地处理这个问题的任何和所有指示都会非常有用!

这是我的代码:

#include <iostream>
#include <thread>
#include <vector>
#include <chrono>

using namespace std;
using namespace std::chrono;

vector<thread> threads;
vector<long long> inputVector;
bool developer = false; 
vector<unsigned long long> factor_base;
vector<long long> primeVector;

class PrimeNumber
{
    long long initValue;        // the number being prime factored
    vector<long long> factors;  // all of the factor values
public:
    void setInitValue(long long n)
    {
        initValue = n;
    }
    void addToVector(long long m)
    {
        factors.push_back(m);
    }
    void setVector(vector<long long> m)
    {
        factors = m;
    }
    long long getInitValue()
    {
        return initValue;
    }
    vector<long long> getVector()
    {
        return factors;
    }
};

vector<PrimeNumber> primes;

// find primes recursively and have them returned in vectors
vector<long long> getPrimes(long long n, vector<long long> vec)
{
    double sqrt_of_n = sqrt(n);

    for (int i = 2; i <= sqrt_of_n; i++)
    {
        if (n % i == 0) 
        {
            return vec.push_back(i), getPrimes(n / i, vec); //cause recursion
        }
    }

    // pick up the last prime factorization number
    vec.push_back(n);

    //return the finished vector
    return vec;
}

void getUserInput()
{
    long long input = -1;
    cout << "Enter all of the numbers to find their prime factors. Enter 0 to compute" << endl;
    do
    {
        cin >> input;
        if (input == 0)
        {
            break;
        }
        inputVector.push_back(input);
    } while (input != 0);
}

int main() 
{

    vector<long long> temp1;   // empty vector
    vector<long long> result1; // temp vector

    if (developer == false)
    {
        getUserInput();
    }
    else
    {
        cout << "developer mode active" << endl;
        long long a1 = 771895004973090566;
        long long b1 = 788380500764597944;
        long long a2 = 100020000004324000;
        long long b2 = 200023423420000000;
        inputVector.push_back(a1);
        inputVector.push_back(b2);
        inputVector.push_back(b1);
        inputVector.push_back(a2);
    }

    high_resolution_clock::time_point time1 = high_resolution_clock::now();

    // give each thread a number to comput within the recursive function
    for (int i = 0; i < inputVector.size(); i++)
    {   
        PrimeNumber prime;
        prime.setInitValue(inputVector.at(i));
        threads.push_back(thread([&]{
            prime.setVector(result1 = getPrimes(inputVector.at(i), temp1));
            primes.push_back(prime);
        }));
    }

    // allow all of the threads to join back together.
    for (auto& th : threads)
    {
        cout << th.get_id() << endl;
        th.join();
    }

    high_resolution_clock::time_point time2 = high_resolution_clock::now();

    // print all of the information
    for (int i = 0; i < primes.size(); i++)
    {
        vector<long long> temp = primes.at(i).getVector();

        for (int m = 0; m < temp.size(); m++)
        {
            cout << temp.at(m) << " ";
        }
        cout << endl;
    }

    cout << endl;

    // so the running time
    auto duration = duration_cast<microseconds>(time2 - time1).count();

    cout << "Duration: " << (duration / 1000000.0) << endl;

    return 0;
}

6 个答案:

答案 0 :(得分:10)

试验除法仅适用于小数字因子。对于 n 最多2 ^ 64,你需要一个更好的算法:我建议从车轮分解开始得到小因子,然后用Pollard的rho算法来得到其余的。如果试验除法为O(sqrt(n)),则rho为O(sqrt(sqrt(n))),因此它的速度要快得多。对于2 ^ 64,sqrt(n)= 2 ^ 32,但sqrt(sqrt(n))= 2 ^ 16,这是一个巨大的改进。你应该期望在最多几毫秒内计算你的数字。

我没有用于分解的C ++代码,但我确实有可读的Python代码。如果您要我发布,请告诉我。如果你想了解更多关于车轮分解和rho算法的知识,我在my blog有很多素数的东西。

答案 1 :(得分:2)

for(int i  = 2; i * i <= n; ++i) //no sqrt, please
{
    while(n%i == 0) //while, not if
    {
         factors.push_back(i);
         n/=i;
    }
}
if(n != 1)
{
    factors.push_back(n);
}

这基本上是您算法的更简洁的实现。它的复杂度是N的sqrt。即使对于一个18位数字,它也能很快地工作,但前提是素数因素都很小。如果它是两个大素数的乘积,或者更糟糕的是,它本身就是素数,这将持续大约10秒钟。

答案 2 :(得分:2)

我发现现代处理器上的Eratosthenes Sieve会使缓存崩溃,因此主内存带宽是限制因素。我在尝试运行多个线程时发现了这一点,并且没有像我希望的那样加快速度。

所以,我建议将筛子分成适合L3缓存的段。此外,如果从位向量中排除2,3和5的倍数,则8位字节可以表示数字行上的30个数字,每个数字为1位,即1,7,11,13,17,19 ,23或29模30 - 使得素数高达10 ^ 9的位图需要~32MB - 10 ^ 9 /(30 * 1024 * 1024)。这几乎是位图大小的一半,它只排除了2的倍数,即~60MB - 10 ^ 9 /(2 * 8 * 1024 * 1024)。

显然,要将筛子运行到10 ^ 9,你需要最多为sqrt(10 ^ 9)的素数 - 这需要大约1,055个字节,从中你可以生成全筛子的任何部分,最多10 ^ 9

FWIW,我得到的适度AMD Phenom II x6 1090T(8MB L3缓存)的结果,适用于高达10 ^ 9的素数:

  1. 1 core,   1 segment    3.260 seconds elapsed
  2. 5 cores,  1 segment    1.830 seconds elapsed
  3. 1 core,   8 segments   1.800 seconds elapsed
  4. 5 cores, 40 segments   0.370 seconds elapsed

其中&#34;段&#34;我的意思是筛子的一部分。在这种情况下,筛子大约是32MB,因此在有多个段的情况下,他们一次使用大约4MB的L3缓存。

这些时间包括扫描完成的筛子并生成所有素数作为整数数组所需的时间。这需要大约0.5秒的CPU!因此,要在不实际从中提取素数的情况下运行筛子,在上述情况(4)中需要0.270秒。

FWIW,我得到一个小的改进 - 在案例(4)中为0.240秒 - 通过使用预先计算的模式初始化每个段,删除7,11,13和17的倍数。该模式是17,017字节。

显然,要在0.06秒内进行单一因子分解......你需要预先计算出筛子!

答案 3 :(得分:1)

通过更改循环可以轻松实现两个简单的加速:

if (n % 2) {
    return vec.push_back(i), getPrimes(n / i, vec);
}

for (int i = 3; i <= sqrt_of_n; i += 2)
{
    if (n % i == 0) 
    {
        return vec.push_back(i), getPrimes(n / i, vec); //cause recursion
    }
}

首先应该测试两个数字。然后,从3开始,您再次测试将循环递增两次。你已经知道了4,6,8,......是偶数,有2个因子。对偶数进行测试,可以将复杂性降低一半。

要计算数字N,您只需要素数&lt; = sqrt(N)。对于18位数字,您只需要测试小于1e9的所有素数,并且由于there are 98 millon primes小于2e9,您可以在今天的计算机上轻松存储100万个数字并运行因子分析平行。如果每个数字占用8个字节的RAM(int64_t),则100毫秒的素数将需要800 MB的内存。该算法是SPOJ problem #2, Prime Generator的经典解决方案。

列出所有适合32位int的小素数的最佳方法是构建一个Eratostenes筛选器。我告诉过你我们需要小于sqrt(N)的素数来计算任何N,所以要求64位整数,你需要所有适合32位数的素数。

答案 4 :(得分:0)

算法:

  1. 将数字除以2,直到不能将其整除,然后存储结果并 显示它。
  2. 将数字除以3,直到不能被3整除并显示结果,
  3. 对5,7 ...等重复相同的过程,直到n的平方根。
  4. 如果最后的结果数是质数,则将其计数显示为1。

    int main() {
    long long n;
    cin >> n;
    int count =0 ;
    while(!(n%2)){
        n = n / 2;
        count++;
    }
    if(count > 0) {
        cout<<"2^"<<count<<" ";
    }
    for(long long i=3;i<=sqrt(n) ; i+=2){
        count=0;
        while(n%i == 0){
            count++;
            n = n/i;
        }
        if(count){
            cout << i <<"^" <<count<<" ";
        }
    
    }
    if(n>2){
        cout<<n <<"^1";
    }
    
    
    
    return 0;
    }
    

输入:100000000 输出2 ^ 8 5 ^ 8

答案 5 :(得分:-2)

将整数分解的算法非常简单,即使在算盘上也可以实现。

void start()
{ 
   int a=4252361;    //  integer to factorize
   int b=1,  c,  d=a-1;  

   while ((a > b) && (d != b))
  {
    if (d > b)
     {
       c=c+b;
       a=a-1;
       d=a-c-1;
     }
    if (d < b)
     {
       c=b-d;
       b=b+1;
       a=a-1;
       d=a-c-1;
     }         
  }
    if ((d == b)&&(a > b)) 
  Alert ("a = ",  a-1,  " * ", b+1); 

    if ((d < b)&&(a <= b)) 
  Alert ("a  is a prime");

  return;
}

算法由我组成,Miliaev Viktor Konstantinovich,1950年7月26日出生。 它是用MQL4 15.12编写的。 2017.电子邮件:tenfacet27@gmail.com