共有回帖数  0  个 
	 
	
	
	
     
          
          
               
                  - 关於素数的一点 (about the prime numbers)
- 
													  只看楼主					  
                     
                       收藏
                      
                          
                           回复
                      
					  
					                     
					 
 
            
				   - 
						
						
							 
									素数,亦称质数,指在一个大於1的自然数中,除了1和此整数自身外,无法被其他自然数整除的 数。换句话说,只有两个正因数(1和 自己)的自然数即为素数。
 素数这个概念 , 这个估计大家都不陌生 , 因为小学就已经接触了.
 此贴将会讨论一个很经典的素数问题.在 0~N 的自然数范围中 , 数出在此范围之内的素数个数.为了方便 , 也同是也是数学定义 , 我们称之为 π(n)
 π(10) = 4因为 10 之内的素数只有4个 -- 2 , 3 ,5 ,7
 关於π(n) 问题 , 最主流的方法也就是2种 , 也是小学的时候都学会的方法
 
 1 ) 从 2`n 逐个验证 , 若是检验到某数是素数的时候 , 则+1
 
 2 ) 打表法 , 也是所谓的埃拉托斯特尼筛法
 
 1 , 2 法随便找本c语言的书籍就有写了
 
 以耗损时间的角度来说(不是时间复杂度,此类问题的时间复杂度不可以用多项式表达)
 方法1最耗时
 
 以空间复杂度来说 , 第1种方法的空间复杂度是O(1), 只需消耗一个空间即可
 第2种方法的空间复杂度是O(n) ,
 
 -------------------
 
 接下来还有一个很不常用的方法
 
 消耗时间比1) 2) 方法来的更少
 且 空间复杂度只有 O(sqrt(n))
 
 那就是勒让德法则 , 当然还有更先进的Ernst Meissel法则, 还有黎曼ζ函数
 但是勒让德法则算是以上法则当中最容易了解
 
 所需的数学知识有 - 离散数学 , set论 , 斥容定理等
 
 本贴不详细的证明此法则.
 在成功写出以下程序之前 , 我大概失败了4次 , 共花费48小时来写 .第1次压堆 ,发现根本没那麼多记忆体可压 , 於是最后的那几次则是用到递归概念 , 然后优化 , 节枝 等等. 每次的失败给之后的优化带来不少灵感 .
 大家可以看出那个Recusion中有不少retrun , 那是为了节枝而定的
 
 #include ctime
 #include cstdio
 #include cstdlib
 bool primeTable[32000];int prime[3410];
 int p ,q , r = 1000000000 , s ;
 int stack[9];int ctrl = 0;
 long long result = r-1;
 
 void findPrime()
 {
 for(int i=2;i32000;primeTable[i++]=true);
 for(int i=2;i=177;i++)
 for(int j=i+i;j32000;j+=i)primeTable[j] = false;
 for (int i=2;i32000;i++)
 if ( primeTable && i  31622 )
 prime[p++] = i;
 }
 
 void Recusion(int start,int size,int N,long long pre)
 {
 if ( size == q )
 {
 result += N*r/pre;
 return;
 }
 if ( (bool)(ctrl) )
 {
 --ctrl ; return;
 }
 for (int i = start ;i  p ; i++)
 {
 long long temp = 1;
 temp = pre * prime;
 if ( temp  r )
 {
 for(int i= size ; i;i--)
 if ( stack - stack[i-1] == 1 )
 ctrl++;
 else break;
 return ;
 }
 stack[size] = i;
 Recusion(i+1,size+1,N,temp);
 }
 }
 
 int main()
 {
 clock_t start = clock();
 findPrime();
 for (q = 1,s = -1;q=9;q++,s*=(-1))
 Recusion(0,0,s,1);
 result += p;
 clock_t end = clock()-start;
 printf("0~1,000,000,000 , having %d prime numbersn",result);
 printf("It used %d msn",(int)end);
 system("pause");
 }
 
 以上是 π(1000000000) 的程序
 
 在我的 2G物理内存 , Inter Pantinum Dual E2160 1.80GHz 1.79GHz 处理器
 作业平台 xp professional service pack 2 繁体中文版上运行
 花费时间 24000 毫秒 (24s左右) , 记忆体  1M
 结果 π(1000000000) = 50,847,534
 
 通过此又让我想起另外一个问题 , 那就是排序
 
 若是只要我排序2个数字 , 那麼 , 我值要找出哪个大哪个小就可以了
 若是只要我排序10个数字 , 仍然可以找出那10个数中最大的数字 , 然后抽出 , 往下解决剩余9个数中最大的数字是那个 (这也是插入排序法的原理)
 若是只要我排序100个 , 我会用冒泡排序法
 若是排10000个 , 那麼会用比冒泡 插入 排序法更复杂的 快速排序法 , 或是合并排序法
 但是 当要我排1百万个的时候 , 原先的快速排序法变会失效 , 因为可能内存不足 , 这时候便需要借住外部文件帮助 -- 外部排序法
 
 显然对於一个排序问题 , 虽然排100万个跟排1万个的原理是一样
 但是在实现的过程中变很显然不同
 前者的复杂度要比后者的要高出指数倍 而不是100倍
 
 说回那个素数问题
 若是 π(n) , n = 1,000 的话 , 那麼方法 1 , 2 都能走的通
 若是 1,000  n = 1,000,000 , 方法1显然就不行了 , 不过可以通过打表解决
 若是 1,000,000  n = 10,000,000 , 仍然可以打表 , 不过不能打全表 ,途中需要优化
 若是 n  100,000,000 的话 打表法已经无用了 , 那只能另循新路了
 
 关於此问题 , 其实还有一个 O(1)空间复杂度 , O(1)时间复杂的算法
 可以求出 π(n)的大概值
 那就是素数定理
 π(n) =n/In(n)
 只能求出大致的值 ,让求精确值之前让心理有个度, 此定理在18xx年由某法国数学家证明出来
 其中 ln 是自然对数
 
 今天又成功优化
 
 使得[0,1000万] 只需要 62毫秒
 [0,1亿] 只需要 640毫秒
 [0,10亿] 只需要 6300毫秒
 
 比起之前
 [0,1亿] 只需要 2000毫秒
 [0,10亿] 只需要 24000毫秒 快了很多
 
 记忆体消耗也比之前少了
  
 楼主 2015-11-05 13:10 回复 
 
 
   
             
                  
                  
 
 
 
     
	 
  
	Copyright © 2010~2015 直线网 版权所有,All Rights Reserved.沪ICP备10039589号
	
	意见反馈 | 
	关于直线 | 
	版权声明 | 
	会员须知