Algorithm 高效地找到从1到10^6的所有数字的所有除数

Algorithm 高效地找到从1到10^6的所有数字的所有除数,algorithm,math,vector,Algorithm,Math,Vector,我需要找到1到n(包括1和n)之间所有数字的所有除数。其中n等于10^6,我想把它们存储在向量中 vector< vector<int> > divisors(1000000); void abc() { long int n=1,num; while(n<1000000) { num=n; int limit=sqrt(num); for(long int i=1;i<limit;i++

我需要找到1到n(包括1和n)之间所有数字的所有除数。其中n等于10^6,我想把它们存储在向量中

vector< vector<int> > divisors(1000000);
void abc()
{
    long int n=1,num;
    while(n<1000000)
    {
        num=n;
        int limit=sqrt(num);
        for(long int i=1;i<limit;i++)
        {
            if(num%i==0)
            {
                divisors[n].push_back(i);
                divisors[n].push_back(num/i);
            }
        }
        n++;
    }
}
vector除数(1000000);
无效abc()
{
长整数n=1,num;

虽然(n我认为这可能不是最好的解决方案,但它比目前提出的解决方案要好得多,因此我们开始:

检查从
1
n
的所有数字(
i
),对于每个数字:

  • 将数字添加到自身的列表中
  • 将乘数设置为2
  • i
    添加到
    i*乘数的列表中
  • 增加
    乘数
  • 重复步骤3和4,直到
    i*乘数
    大于
    n

  • [Edit3]完成重新编辑

    您当前的方法是
    O(n^1.5)
    而不是
    O(n^2)

    原来我建议去看看

    但正如奥利弗·查尔斯沃思(Oliver Charlesworth)建议我阅读的那样,这在这里应该不是什么大问题(测量也证实了这一点)

    因此,无需为列表预先分配memroy(这只会浪费内存,而且由于缓存障碍,甚至会降低总体性能,至少在我的设置中是这样)

    那么如何优化呢?

    • 要么降低常数时间,使运行时比迭代更好(即使复杂度更差)
    • 或者将复杂度降低到不需要更大的开销就可以实现一些加速
    我将从SoF(埃拉托斯坦筛)开始。

    但是,我会将当前迭代的筛子添加到数字除数列表中,而不是将数字设置为可除数。这应该是
    O(n^2)
    ,但如果编码正确,则开销会大大降低(无除数且完全可并行)

  • 开始计算所有数字的SoF
    i=2,3,4,5,…,n-1

  • 对于您点击的每个数字
    x
    不更新SoF表(您不需要它)。而是将迭代筛
    i
    添加到
    x
    的除数列表中。类似于:

  • C++源代码:

    const int n=1000000;
    List<int> divs[n];
    void divisors()
        {
        int i,x;
        for (i=1;i<n;i++)
         for (x=i;x<n;x+=i)
          divs[x].add(i);
        }
    
    下一件事是使用直接内存访问(不确定您是否可以使用
    vector
    )我的列表能够做到这一点不要将其与硬件DMA混淆这只是避免阵列范围检查。这加快了重复性检查的恒定开销,结果时间为
    [1.793s]
    比原始SoF
    O(n^2)
    版本慢一点。因此,如果你的
    n
    更大,这就是方法

    [Notes]

    如果您想进行素数分解,那么只通过素数迭代
    i
    (在这种情况下,您需要SoF表)

    如果你对SoF或PRIME有问题,可以在这方面寻找一些额外的想法

    const int N = 1000000;
    vector<vector<int>> divisors(N+1);
    
    for (int i = 2; i <= N; i++) {
      for (j = i; j <= N; j += i) {
        divisors[j].push_back(i);
      }
    }
    
    这就变成了
    N*sum(1/N表示N从1到N)
    ,这就是
    N*log(N)
    ,可以使用
    1/x对dx从1到N的积分来显示


    我们可以看到algorhitm是最优的,因为除数的数量和运算的数量一样多。结果的大小或除数的总数与algorhitm的复杂度相同。

    另一个优化不是使用-vector-或-list-,而是使用大量的除数,请参阅

    第一步:筛选除数

    第二步:用除数总数筛选除数

    注:10倍范围内的最大时间增加20倍。-->O(N*log(N))

    Dev-C++5.11,C语言

    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    
    int SieveNbOfDiv(int NumberOfDivisors[], int IndexCount[], int Limit) {
        for (int i = 1; i*i <= Limit; i++) {
            NumberOfDivisors[i*i] += 1;
            for (int j = i*(i+1); j <= Limit; j += i )
                NumberOfDivisors[j] += 2;
        }
        int Count = 0;
        for (int i = 1; i <= Limit; i++) {
            Count += NumberOfDivisors[i];
            NumberOfDivisors[i] = Count;
            IndexCount[i] = Count;
        }
        return Count;
    }
    
    void SieveDivisors(int IndexCount[], int NumberOfDivisors[], int Divisors[], int Limit) {
        for (int i = 1; i <= Limit; i++) {
            Divisors[IndexCount[i-1]++] = 1;
            Divisors[IndexCount[i]-1] = i;
        }
        for (int i = 2; i*i <= Limit; i++) {
            Divisors[IndexCount[i*i-1]++] = i;
            for (int j = i*(i+1); j <= Limit; j += i ) {
                Divisors[IndexCount[j-1]++] = i;
                Divisors[NumberOfDivisors[j-1] + NumberOfDivisors[j] - IndexCount[j-1]] = j/i;
            }
        }
    }
    
    int main(int argc, char *argv[]) {
        int N = 1000000;
        if (argc > 1) N = atoi(argv[1]);
        int ToPrint = 0;
        if (argc > 2) ToPrint = atoi(argv[2]); 
    
        clock_t Start = clock();
        printf("Using sieve of divisors from 1 to %d\n\n", N);
    
        printf("Evaluating sieve of number of divisors ...\n");
        int *NumberOfDivisors = (int*) calloc(N+1, sizeof(int));
        int *IndexCount = (int*) calloc(N+1, sizeof(int));
        int size = SieveNbOfDiv(NumberOfDivisors, IndexCount, N);
    
        printf("Total number of divisors = %d\n", size);
        printf("%0.3f second(s)\n\n", (clock() - Start)/1000.0);
    
        printf("Evaluating sieve of divisors ...\n");
        int *Divisors = (int*) calloc(size+1, sizeof(int));
        SieveDivisors(IndexCount, NumberOfDivisors, Divisors, N);
    
        printf("%0.3f second(s)\n", (clock() - Start)/1000.0); 
    
        if (ToPrint == 1)
            for (int i = 1; i <= N; i++) {
                printf("%d(%d) = ", i, NumberOfDivisors[i] - NumberOfDivisors[i-1]);
                for (int j = NumberOfDivisors[i-1]; j < NumberOfDivisors[i]; j++)
                    printf("%d ", Divisors[j]);
                printf("\n");
            }
    
        return 0;
    }
    

    @Shapiroyacov为什么?我需要从1到1000000的数字的所有除数如果我理解正确,您需要范围
    [1,…,1000000]内每个数字的所有除数
    ?如果是这样,为什么要检查范围内每个数字的所有可能除数?无论
    10
    的除数列表是什么,都在
    20、30、…、90、100、…、1000等的除数列表中。@shapiroyacov那么我该如何改进我的代码?尝试实现一个基于shapiro提到的思想的算法。也就是说不过,不仅仅是调整代码,它实际上需要不同的逻辑。@viveksehgal什么是太多的时间,在什么平台上?你不认为这个解决方案是O(n^2)?肯定是
    O(n^2)
    。但是你想要更快的代码,在我看来这会更快。为什么?这不会浪费操作。每次#3完成后,你都会在某处添加一个数字(
    如果(num%i==0)
    在代码中失败的次数最多-每
    n
    大于2次)另外,我提到AFAIK,不是最佳解决方案,只是一个改进…我认为这是
    O(n log n)
    ,而不是
    O(n^2)对于每一个连续的外循环迭代,内环迭代次数较少,这里的复杂性似乎是“代码”>log n<代码>的总和。假设这是C++,则列表增长的事情是不正确的;<代码>向量:PoPuthBuff</代码>被O(1)摊销。.@OliverCharlesworth只有当
    vector
    是链表而不是线性数组时才是真的…因为我不使用它,我不知道…
    vector
    是标准线性数组,而不是链表。@OliverCharlesworth在这种情况下,没有预分配我的点代表…想象一下,如果数组在开始y上是16个项目,你会添加1000个数字你需要一次又一次地重新分配(多久一次取决于模板的增长策略,我通常会将模板的大小增加一倍)…事实上-假设一个指数增长策略,那么你仍然会得到O(1)摊销。我不认为这是O(N*log(N))你能解释一下吗?@hasan83-请看Anatolig对我答案的评论。@hasan83有点像和声系列,我已经更新了我的答案answer@LukaRahne这是SoF…上界应该是N/2,和我的方法一样,如果你也想要1作为除数,那么从1开始,为什么你要把一个减到N/2?在这种情况下,数字本身
    sum (N/n for n from 1 to N)
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    
    int SieveNbOfDiv(int NumberOfDivisors[], int IndexCount[], int Limit) {
        for (int i = 1; i*i <= Limit; i++) {
            NumberOfDivisors[i*i] += 1;
            for (int j = i*(i+1); j <= Limit; j += i )
                NumberOfDivisors[j] += 2;
        }
        int Count = 0;
        for (int i = 1; i <= Limit; i++) {
            Count += NumberOfDivisors[i];
            NumberOfDivisors[i] = Count;
            IndexCount[i] = Count;
        }
        return Count;
    }
    
    void SieveDivisors(int IndexCount[], int NumberOfDivisors[], int Divisors[], int Limit) {
        for (int i = 1; i <= Limit; i++) {
            Divisors[IndexCount[i-1]++] = 1;
            Divisors[IndexCount[i]-1] = i;
        }
        for (int i = 2; i*i <= Limit; i++) {
            Divisors[IndexCount[i*i-1]++] = i;
            for (int j = i*(i+1); j <= Limit; j += i ) {
                Divisors[IndexCount[j-1]++] = i;
                Divisors[NumberOfDivisors[j-1] + NumberOfDivisors[j] - IndexCount[j-1]] = j/i;
            }
        }
    }
    
    int main(int argc, char *argv[]) {
        int N = 1000000;
        if (argc > 1) N = atoi(argv[1]);
        int ToPrint = 0;
        if (argc > 2) ToPrint = atoi(argv[2]); 
    
        clock_t Start = clock();
        printf("Using sieve of divisors from 1 to %d\n\n", N);
    
        printf("Evaluating sieve of number of divisors ...\n");
        int *NumberOfDivisors = (int*) calloc(N+1, sizeof(int));
        int *IndexCount = (int*) calloc(N+1, sizeof(int));
        int size = SieveNbOfDiv(NumberOfDivisors, IndexCount, N);
    
        printf("Total number of divisors = %d\n", size);
        printf("%0.3f second(s)\n\n", (clock() - Start)/1000.0);
    
        printf("Evaluating sieve of divisors ...\n");
        int *Divisors = (int*) calloc(size+1, sizeof(int));
        SieveDivisors(IndexCount, NumberOfDivisors, Divisors, N);
    
        printf("%0.3f second(s)\n", (clock() - Start)/1000.0); 
    
        if (ToPrint == 1)
            for (int i = 1; i <= N; i++) {
                printf("%d(%d) = ", i, NumberOfDivisors[i] - NumberOfDivisors[i-1]);
                for (int j = NumberOfDivisors[i-1]; j < NumberOfDivisors[i]; j++)
                    printf("%d ", Divisors[j]);
                printf("\n");
            }
    
        return 0;
    }
    
    Copyright (c) 2009 Microsoft Corporation.  All rights reserved.
    
    c:\Users\Ab\Documents\gcc\sievedivisors>sievedivisors 100000
    Using sieve of divisors from 1 to 100000
    
    Evaluating sieve of number of divisors ...
    Total number of divisors = 1166750
    0.000 second(s)
    
    Evaluating sieve of divisors ...
    0.020 second(s)
    
    c:\Users\Ab\Documents\gcc\sievedivisors>sievedivisors 1000000
    Using sieve of divisors from 1 to 1000000
    
    Evaluating sieve of number of divisors ...
    Total number of divisors = 13970034
    0.060 second(s)
    
    Evaluating sieve of divisors ...
    0.610 second(s)
    
    c:\Users\Ab\Documents\gcc\sievedivisors>sievedivisors 10000000
    Using sieve of divisors from 1 to 10000000
    
    Evaluating sieve of number of divisors ...
    Total number of divisors = 162725364
    0.995 second(s)
    
    Evaluating sieve of divisors ...
    11.900 second(s)
    
    c:\Users\Ab\Documents\gcc\sievedivisors>