大数的有效素数分解

Efficient Prime Factorization for large numbers

本文关键字:分解 有效      更新时间:2023-10-16

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

每次我在我预先制作的4个数字上运行这个程序,大约需要10秒。如果有任何想法的话,我想把这个时间减少到0.06秒。

我注意到一些算法,比如埃拉托斯梯尼筛,并在计算之前生成所有素数的列表。我只是想知道是否有人能详细说明一下。例如,我在理解如何在我的程序中实现埃拉托斯尼筛方面存在问题,或者这是否是一个好主意。任何关于如何更好地解决这一问题的建议都将非常有用!

这是我的代码:

#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;
}

试探除法只适用于分解小数字。对于2^64以下的n,您需要一个更好的算法:我建议从轮分解开始获得小因子,然后是Pollard的rho算法来获得其余因子。其中试除法是O(sqrt(n)),rho是O(squrt(sqrt)),所以它要快得多。对于2^64,sqrt(n)=2^32,但sqrt(sqrt(n))=2^16,这是一个巨大的改进。您最多应该在几毫秒内对您的数字进行因子运算。

我没有用于分解的C++代码,但我有可读的Python代码。如果你想让我发布,请告诉我。如果你想了解更多关于轮分解和rho算法的信息,我的博客上有很多素数的东西。

我发现现代处理器上的Eratosthenes筛会抖动缓存,因此主内存带宽是限制因素。我在尝试运行多个线程时发现了这一点,但未能像我希望的那样加快速度。

因此,我建议将筛选分成适合三级缓存的段。此外,如果从位向量中排除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)的素数——这需要大约1055个字节,您可以从中生成高达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

我所说的"段"是指筛子的一部分。在这种情况下,筛选大小约为32MB,因此在有多个段的情况下,它们在任何时候都会使用约4MB的三级缓存。

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

FWIW,我得到了一个小的改进——在情况(4)中为0.240秒——通过使用预先计算的模式初始化每个片段,该模式删除了7、11、13和17的倍数。该模式为17017字节。

显然,要在0.06秒内完成一次因子分解。。。你需要预先计算筛子!

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的平方。即使对于18位数的数字,它也会很快工作,但前提是素数都很小。如果它是两个大素数的乘积,或者更糟的是,它本身就是素数,这将持续大约10秒。

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

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
    }
}

你应该先用2检验这个数字。然后,从3开始,再次测试,每次将循环增加2。你已经知道4,6,8。。。是偶数,并且以2为因子。通过对偶数进行测试,您可以将复杂性降低一半。

要对数字N进行因子运算,只需要素数<=sqrt(N)。对于一个18位数的数字,你只需要对所有小于1e9的素数进行测试,而且由于有98个小于2e9的millon素数,你可以很容易地在今天的计算机上存储100 millon数字,并并行运行因子分解。如果每个数字占用8字节的RAM(int64_t),则100兆素数将占用800 MB的内存。该算法是SPOJ问题#2 Prime Generator的经典解决方案。

列出所有适合32位int的小素数的最好方法是构建一个Eratostenes筛。我告诉过你,我们需要小于sqrt(N)的素数来对任何N进行因子分解,所以要对64位整数进行因子分解需要所有适合32位数字的素数。

算法:

  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

整数分解算法,非常简单,甚至可以在算盘上实现。

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