Algorithm 高效地找到从1到10^6的所有数字的所有除数
我需要找到1到n(包括1和n)之间所有数字的所有除数。其中n等于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++
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)
,但如果编码正确,则开销会大大降低(无除数且完全可并行)
开始计算所有数字的SoFi=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]
比原始SoFO(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>