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

时间:2015-10-11 05:52:43

标签: algorithm math vector

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

If (CInt(str) < 5) Then

这也是太多时间。我能以任何方式优化它吗?

4 个答案:

答案 0 :(得分:2)

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

这在O(N*log(N))

中运行

直觉是上N / 2个数只运行一次。然后从剩余的数字上半部再次运行...

其他方式。如果你将N从10 ^ 6增加到10 ^ 7,那么就像10 ^ 6乘10那样有多少操作。(这是线性的),但是额外的是从10 ^ 6到10 ^ 7的数字在最坏的情况下,每次运行不超过10次。

操作次数

sum (N/n for n from 1 to N)

然后变为N * sum(1/n for n from 1 to N),这是N*log(N),可以使用1/x over dx from 1 to N

的集成显示

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

答案 1 :(得分:1)

我认为这可能不是最好的解决方案,但它比提供的解决方案要好得多,所以我们走了:

i的所有数字(1)翻到n,并为每个数字:

  1. 将号码添加到自己的列表中。
  2. 将乘数设为2。
  3. i添加到i * multiplier
  4. 列表中
  5. 增加multiplier
  6. 重复步骤3&amp; 4直到i * multiplier大于n

答案 2 :(得分:0)

[Edit3]完整reedit

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

最初,我建议您查看Why are my nested for loops taking so long to compute?

但正如奥利弗查尔斯沃思建议我阅读About Vectors growth这不应该是一个问题(测量也证实了这一点)。

因此无需为列表预先分配memroy(它只会浪费内存,并且由于CACHE障碍甚至会降低整体性能,至少在我的设置上)。

那么如何优化?

  • 要么降低常量时间,要么运行时间优于迭代(即使复杂性更差)
  • 或者降低复杂程度以至于实际上有一些加速不会增加开销

我会从SoF(Eratosthenes筛选)开始

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

  1. 开始为所有数字计算SoF i=2,3,4,5,...,n-1

  2. 您点击的每个号码x都不会更新SoF表(您不需要它)。而是将迭代筛i添加到x的除数列表中。类似的东西:

  3. 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);
        }
    
    • 这花费了1.739次,发现13969984个除数总数,每个数字最多240个除数(包括1x)。正如您所看到的,它不会使用任何划分。并且除数按升序排序。

    • List<int>是整数模板的动态列表(类似于您的vector<>

    你可以根据你的迭代类型进行调整,这样你就可以检查nn=sqrt(n)并且每次迭代添加2个除数,O(n^1.5*log(n))不同的常数时间(开销)因单一划分而慢一点需要和重复性检查(log(n)具有较高的基数)所以你需要测量它是否加快了我的设置速度是这样慢(~2.383 s即使它具有更好的复杂性。)< / p>

    const int n=1000000;
    List<int> divs[n];
    int i,j,x,y,nn=sqrt(n);
    
    for (i=1;i<=nn;i++)
     for (x=i;x<n;x+=i)
        {
        for (y=divs[x].num-1;y>=0;y--)
         if (i==divs[x][y]) break;
        if (y<0) divs[x].add(i);
        j=x/i;
        for (y=divs[x].num-1;y>=0;y--)
         if (j==divs[x][y]) break;
        if (y<0) divs[x].add(j);
        }
    

    接下来就是使用直接内存访问(不确定你能用vector<>做到这一点)我的列表能够做到这一点,不要把它与硬件DMA混淆,这只是避免了数组范围检查。这加快了双重性检查的持续开销,结果时间为[1.793s],这比原始SoF O(n^2)版本慢一点。所以,如果你变大n,那就是这样。

    <强> [注释]

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

    如果您遇到SoF或素数问题,请查看Prime numbers by Eratosthenes quicker sequential than concurrently?以了解其他一些想法

答案 3 :(得分:0)

另一个优化不是使用-vector- nor -list-,而是使用大量的除数,请参阅http://oeis.org/A027750

第一步:除数除数

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

注意:10倍范围内最多可增加20倍的时间。 - &GT; O(N *日志(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;
}

有一些结果:

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>