首页
社区
课程
招聘
[讨论]压缩筛选法求1~N内的所有质数
发表于: 2009-5-21 22:49 13288
申请推荐此帖 编辑 删除

[讨论]压缩筛选法求1~N内的所有质数

2009-5-21 22:49
13288

用压缩筛选法求 1 ~ MAX_NUMBER 内的所有质数
  基于 John Moyer (jrm@rsok.com) 的 sieve2310 (http://www.rsok.com/~jrm/printprimes.html) 算法改写
原理: 考虑 30 = 2*3*5, 对任意非负整数N(即N>=0), 显然
  1. N*30 + 2*x (x = [0, 15]) 都能被2整除
  2. N*30 + 3*x (x = [0, 10]) 都能被3整除
  3. N*30 + 5*x (x = [0, 6]) 都能被5整除
因此, 除去2, 3, 5的倍数后, N*30+0 ~ N*30+29 这30个数中, 只有
  N*30+1, N*30+7, N*30+11, N*30+13, N*30+17, N*30+19, N*30+23, N*30+29
这8个数可能是质数. 也就是说, 30个整数只需要8个Bit就可以表示所有可能的质数
依此类推, 当 2310 = 2*3*5*7*11 时, 所有可能的质数为
  N*2310+1, N*2310+13, N*2310+17, ..., N*2310+13*13, N*2310+13*17, ..., N*2310+2309
共480个. 即只需要480 Bits就可以表示2310个整数中的所有可能的质数.


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 0
支持
分享
最新回复 (14)
雪    币:
能力值: (RANK: )
在线值:
发帖
回帖
粉丝
2
三种算法占用的空间和时间对比(同一台机, 编译时无优化选项, 同样计算50000000的质数, 计算完后马上停止计时, 不包括输出结果的时间, 单位分别为字节和秒):
jrm版: sieve_size = 1298760, time = 2 (BIGINT = 32)
jrm版: sieve_size = 1298760, time = 5 (BIGINT = 64)
Loka版: sieve_size = 6250001, time = 1
梵听版: sieve_size = 50000000, time = 3
显然, jrm版因为有压缩表示, 占用的空间最小, 但索引转换产生了额外的计算量, 特别是BIGINT = 64时, 对性能的影响特别明显.
不过我不明白为什么Loka版一样有定位到某个Bit的额外计算, 但却比梵听版用时还少.

附其它两个版本的代码如下:
梵听版: http://bbs.pediy.com/showthread.php?t=89405
Loka版: http://bbs.pediy.com/showthread.php?t=89406
2009-5-21 23:03
0
雪    币: 993
活跃值: (442)
能力值: ( LV12,RANK:403 )
在线值:
发帖
回帖
粉丝
3
不错,一个简单的技巧让空间利用率翻了3.75倍。
2009-5-21 23:04
0
雪    币: 993
活跃值: (442)
能力值: ( LV12,RANK:403 )
在线值:
发帖
回帖
粉丝
4
当时看到梵听的版本时,我只是觉得他的方法很直观,但是程序的书写上不够优化,所以在他的基础上简单的进行了改进优化,其它的都没改变。
1)即然是指定大小的空间,能不用new就不要用new了,预先分配的空间不仅节省时间也让编译时可以有目的的优化。
2)用位操作相对来说空间更少,数据不必经常换进换出,cache命中率会高一点。
3)他在扫描K以后的质数时用了乘法,我改成加法,并且从K*K开始,以前的不必再判了。
2009-5-21 23:10
0
雪    币:
能力值: (RANK: )
在线值:
发帖
回帖
粉丝
5
我按你说的做了如下修改:
if (isprime(i))  // 如果是质数
{
	for (j = i * i; j < MAX_NUMBER; j += i)
		nonprime(j);  // 将该质数的所有倍数设为非质数
}

结果还是一样, 32/64位分别是2/5秒, 没有差异(或许有0.x秒, time表示不了).
还有, 我是从NEXT_PRIME起到maxfactor, 步进为2, 至少这里循环次数就比你们的算法少了一半, 但时间上....
2009-5-21 23:18
0
雪    币: 993
活跃值: (442)
能力值: ( LV12,RANK:403 )
在线值:
发帖
回帖
粉丝
6
你在这里的改进对你的程序整体影响已经不大了,我说的方法是对梵听版所做的优化,因为你的程序比我们的更复杂,好比我的程序走的是一条筋的直路,你的程序就好比是崎岖小路,虽然这个比喻不大贴切,就象下面两个程序:
for(i=0; i<100; i++) j += i;

for(i=0; i<100; i++) if (i%5) j += i; else j *= i;
一样,第一个CPU的流程比较通顺,第二个就会磕磕碰碰。
2009-5-21 23:27
0
雪    币: 993
活跃值: (442)
能力值: ( LV12,RANK:403 )
在线值:
发帖
回帖
粉丝
7
按你的方法写了个程序,不过我没用到11,只用到5。
#include <stdio.h>
#include <math.h>

#define NUM 50000000
typedef unsigned char BYTE;
BYTE table[NUM/30+1];

void suShu()
{
	int i, j, k, u, x, y, z;
	int predict[10] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
	int xbit[8] = {1, 7, 11, 13, 17, 19, 23, 29};
	int flag[30];
	int halfNum;

	for(i=0; i<30; i++) flag[i] = 8;
	for(i=0; i<8; i++) flag[xbit[i]] = i;
	table[0] = 1;
	halfNum = (int)sqrt(1.0*NUM)-29;

	for( j=0,k=0; k<halfNum; j++,k+=30 )
	{
		for(i=0; i<8; i++)
		{
			if (((table[j]>>i)&1) == 0)
			{
				z = k+xbit[i];
				u = z%30;
				y = z*z;
				x = y%30;
				for( ; y<=NUM; y+=z)
				{
					if (flag[x] < 8) table[y/30] |= (1<<flag[x]);
					x += u;
					if (x >= 30) x -= 30;
				}
			}
		}
	}
	halfNum += 30;
	for(i=0; i<8; i++)
	{
		if (((table[j]>>i)&1) == 0)
		{
			z = k+xbit[i];
			if (z < halfNum)
			{
				u = z%30;
				y = z*z;
				x = y%30;
				for( ; y<=NUM; y+=z)
				{
					if (flag[x] < 8) table[y/30] |= (1<<flag[x]);
					x += u;
					if (x > 30) x -= 30;
				}
			}
		}
	}

	for( k=0; k<10; k++) printf("%10d", predict[k]);

	for( j=1,k=30; k<=NUM-30; j++,k+=30 )
	{
		for(i=0; i<8; i++)
		{
			if (((table[j]>>i)&1) == 0)
			{
				printf("%10d", k+xbit[i]);
			}
		}
	}
	for(i=0; i<8; i++)
	{
		if (((table[j]>>i)&1) == 0)
		{
			if ((k+xbit[i]) <= NUM) printf("%10d", k+xbit[i]);
		}
	}
	printf("\n");
}

int main()
{
	suShu();

	return 0;
} 
2009-5-22 01:37
0
雪    币: 28
活跃值: (12)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
8
谢谢你的优化,看了之后觉得自己写的东西实在太差了,呵呵

试着算5亿,才用了20秒,比我以前的40秒根本上一个天一个地!!
2009-5-29 21:49
0
雪    币: 1022
活跃值: (31)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
9
建立素数表时,存储相邻两个素数的差值也是一种不错的方法。
小于等于999999937的素数有50847534个,
其中相邻两个素数的差值最大为282。
若只存储相邻两个素数的差值的一半即(p[k]-p[k-1])/2,
用一个字节一直表示小于304599508537的素数。
2009-5-30 14:18
0
雪    币: 993
活跃值: (442)
能力值: ( LV12,RANK:403 )
在线值:
发帖
回帖
粉丝
10
[QUOTE=lingyu;633215]若只存储相邻两个素数的差值的一半即(p[k]-p[k-1])/2,
用一个字节一直表示小于304599508537的素数。[/QUOTE]
好主意,在看雪的收获真是不小,“听君一席话,胜读十年书。”
惟一的问题就是用起来有点小小的不爽,每次要经过一定的计算才能得到具体的素数,
瑕不掩瑜,一点小小的损失对于N倍的存储扩展来说还是值得的。
2009-5-30 18:55
0
雪    币:
能力值: (RANK: )
在线值:
发帖
回帖
粉丝
11
[QUOTE=lingyu;633215]建立素数表时,存储相邻两个素数的差值也是一种不错的方法。
小于等于999999937的素数有50847534个,
其中相邻两个素数的差值最大为282。
若只存储相邻两个素数的差值的一半即(p[k]-p[k-1])/2,
用一个字节一直表示小于304599508537的素数。[/QUOTE]
这种办法依赖于你要先知道"相邻两个素数的差值最大为282". 当数字上限去到 304599508537 以上时你确定还能满足这个条件吗?
我的意思是, 筛选法的前提是不知道素数的分布规律的.
不过这个.... 好象我这个压缩版本需要知道一点分布规则......
好在我依赖的规律是可以证明的, 你依赖的规律是事实......
晕.
2009-5-31 21:28
0
雪    币: 1022
活跃值: (31)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
12
来源:http://www.trnicely.net/gaps/gaplist.html
=========================================================================================
   Gap  Cls Discvrer Year  Merit Digts  Following the prime
=========================================================================================
     1* CFC Euclid   -300   1.44     1  2
     2* CFC AEWestrn 1934   1.82     1  3
     4* CFC Glaisher 1877   2.06     1  7
     6* CFC Glaisher 1877   1.91     2  23
....
   508  CFC RP.Brent 1973  17.99    13  1841086484491
   510  CFC RP.Brent 1973  17.94    13  2209016910131
   512  CFC RP.Brent 1973  18.08    13  1999066711391
   514* CFC RP.Brent 1973  19.44    12  304599508537
   516* CFC RP.Brent 1973  19.29    12  416608695821
   518  CFC RP.Brent 1973  18.20    13  2296497058133
   520  CFC RP.Brent 1973  18.26    13  2336167262449
   522  CFC RP.Brent 1973  18.76    13  1214820695701
   524  CFC RP.Brent 1973  18.42    13  2256065636039
   526  CFC RP.Brent 1973  18.71    13  1620505682371
   528  CFC RP.Brent 1973  18.82    13  1529741785139
   530  CFC RP.Brent 1973  18.65    13  2205492372371
2009-5-31 23:58
0
雪    币: 388
活跃值: (707)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
13
#define MAX 1229
int a[MAX]={2,3,5};
char s[10240] = {0,0,1,1,0,1};

void prime()
{
    int i,j,k,d,stat;
    k = 3;
    d = 2;
    for (i = 7;k < MAX; i += d)
    {
        stat = 1;
        d = 6-d;
        for (j = 0; a[j]*a[j] <= i  && stat; ++j)
        {
            if (i%a[j] == 0)
                stat = 0;
        }
        if (stat)
        {
            a[k] = i;
            s[i] = 1;
            ++k;
        }
    }
}

int main()
{
    prime() ;
    return 0 ;
}

你们测试下这个的效率,如果要大一点,自己修改.MAX是质数的个数,

以前做http://acm.hdu.edu.cn/showproblem.php?pid=2098
这题写的代码了!
2009-7-1 10:31
0
雪    币: 0
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
我先描述下我的测试出来的问题:
我加以一个计数器,
对N=1000进行测试,发现有170个素数,而1000以内实际上只有168个,
我拿出来以百度里面的素数表进行对比发现多出的两是961和989
而961 可以分解为31*31,989可以分解为 23*43
两个显然不是素数。
我有对50000做出了测试,得到5141个
而实际50000里面应该只有,5133个,多出了8个。
具体另外6个就没去细细核对了。

我看了楼主的给的链接 “基于 John Moyer (jrm@rsok.com) 的 sieve2310 (http://www.rsok.com/~jrm/printprimes.html) 算法改写”,发现那边测试是没问题的。

我研究了楼主的程序,发现可能是
// 设置某数为非质数
void nonprime(BIGINT n)

这个函数出错了,但是还没找到解决办法。大家一起探讨吧!
2014-4-22 09:08
0
雪    币: 8043
活跃值: (3744)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
多谢指出, 问题不在nonprime, 而是在于循环的结束值判断.
需要修改两处地方:
	maxfactor = (BIGINT)sqrt(1.0 * MAX_NUMBER);  // 最大因数值
	for (i = NEXT_PRIME; i[COLOR="Red"] <= [/COLOR]maxfactor; i += 2)  // 从13开始的所有奇数, 2/3/5/7/11的倍数已经在压缩时过滤掉了
	{
		if (isprime(i))  // 如果是质数
		{
			count = MAX_NUMBER / i;  // 该质数的最大倍数
			for (j = 2; j [COLOR="Red"]<= [/COLOR]count; j ++)
				nonprime(j * i);  // 将该质数的所有倍数设为非质数
		}
	}
2014-5-14 17:04
0
游客
登录 | 注册 方可回帖
返回
//