• 求解素数


    编程珠玑2-性能监视工具 这个章节主要介绍利用性能监测工具来加速计算素数的程序,因为平时用java,因此调研了下java的性能监测工具,了解了下JFR,jmc自带JFR,安装jmc6.0试用了下,jmc6.0和之前的版本有些区别,可以参考https://stackoverflow.com/questions/50800070/java-mission-control-jmc-6-0-does-not-show-hot-methods-when-examining-a-jfr-fl ,不过JFR只能监测到方法级别,本篇暂且先关注求素数这个算法。
    程序p1-p5的优化过程书中已经介绍的很清楚了,这里直接写出java版本的p6的程序如下:

        /**
         * 功能:普通方法找出小于n的素数
         * */
        private static int[] findPrimeNumbersBruteForce(int n) {
            int count = 0;
            //已经找到的素数集合
            int[] primeNumbers = new int[n];
            int pos = 0;
            for (int i = 2; i <= n; i++) {
                boolean isPrime = true;
                for (int j=0; j < pos && (long)(primeNumbers[j] * primeNumbers[j]) <= i; j++) {
                    //count ++;
                    //System.out.println("i  : " + i + ", primeNumbers[j] : " + primeNumbers[j]);
                    if (i % primeNumbers[j] == 0) {
                        isPrime = false;
                        break;
                    }
                }
                if (isPrime) {
                    primeNumbers[pos++] = i;
                }
            }
            //System.out.println("findPrimeNumbersBruteForce count : " + count);
            return primeNumbers;
        }
    

    习题2中提出问题:你能进一步提高写出比性能吗?根据提示,了解埃氏筛法(Sieve of Eratosthenes)。
    埃氏筛法是一种由古希腊数学家埃拉托斯特尼(公元前250年)提出的一种简单检定素数的算法。算法思想是从1~n中依次删除2的倍数,3的倍数,.......,从而得到所有的素数。java代码实现如下所示:

        /**
         * 功能:一般埃氏筛法,找出小于等于n的素数
         * */
        private static int[] findPrimeNumbersSieve(int n) {
    
            boolean[] mark = new boolean[n+1];
            for(int i = 2; i <= n; i++){
                mark[i] = true;
            }
    
            int i, count = 0;
            int[] primeNumbers = new int[n];
            for (i = 2; i * i <= n; i++) {    //这层可以看作是素数的迭代,因此 i * i <= n                 
                if (mark[i]) {
                    primeNumbers[count++] = i;
                    for (int j = i * i; j <= n; j += i) {
                        mark[j] = false;
                    }
                }
            }
    
            for (; i <= n; i++) {
                if (mark[i]) {
                    primeNumbers[count++] = i;
                }
            }
            return primeNumbers;
        }
    

    埃氏筛法复杂度为O(nlnlnn)(证明过程可从文末参考资料中了解),因此埃氏筛法并不是线性筛法。一个合数会被标记多次,比如30,会被素数2,3,5各标记一次,如果一个合数质因子很多,那么会被标记很多次。对埃氏筛法进行改进,称为线性筛法(也有叫欧拉筛法),java代码实现如下:

    /**
         * 功能:线性筛法,找出小于等于n的素数
         * */
        private static int[] findPrimeNumbersLinearSieve( int n ) {
            int count = 0;
            boolean[] mark = new boolean[n+1];
            for (int i = 2; i <= n; i++) {
                mark[i] = true;
            }
    
            int[] primeNumbers = new int[n];
            int pos = 0;
            for (int i = 2 ; i <= n ; i++) { //这层是1...n的迭代
                if (mark[ i ] )
                    primeNumbers[pos++] = i;
                for(int j = 0 ; j < pos && i * primeNumbers[j] <= n; j++){
                    mark[ i * primeNumbers[j]] = false;
                    //count++;
                    //System.out.println("i  : " + i + ", primeNumbers[j] : " + primeNumbers[j]);
                    if( i % primeNumbers[j] == 0 )          //保证了一个合数只被最小的素因子标记
                        break;
                }
            }
            //System.out.println("findPrimeNumbersLinearSieve count : " + count);
            return primeNumbers;
        }
    
    

    线性筛法的复杂度为O(n),线性筛法解决了一个合数被标记多次的问题,思路是一个合数只由最小素因子标记,重点在if( i % primeNumbers[j] == 0 ) break; 这行代码上:
    primeNumbers中的素数是自增的,
    如果i % primeNumbers[j] == 0,即i = k * primeNumbers[j],那么假设 x= i * primeNumbers[j+1] = k * primeNumbers[j] * primeNumbers[j+1] = k' * primeNumbers[j],因此 x的最小素因子是primeNumbers[j], 而不是primeNumbers[j+1],接下来的素数同理,所以x不用质数primeNumbers[j+1]标记。

    有没有觉得和普通筛法(findPrimeNumbersBruteForce(int n))很像???但性能却相差很大,本机测试当n=100000000时,findPrimeNumbersBruteForce(int n)耗时20s+,而线筛的只用1s左右。

    数学小知识复习
    调和级数:调和级数是各项倒数为等差数列的级数,通常指:
    1 + 1/2 + 1/3 + 1/4 + 1/5 + 1/6 + 1/7 + 1/8 +...
    而 1 + 1/2 + 1/3 + 1/4 + 1/5 + 1/6 + 1/7 + 1/8 +... = ln(n) + C, C为欧拉常数数值是0.5772……

    为什么1不是素数?
    根据算术基本定理,每一个比1大的整数,要么本身是一个素数,要么可以写成唯一一组素数的乘积,最小的素数是2。如果1是素数,那么这一系列素数就不唯一了,会造成很多麻烦。

    参考文章:
    http://www.cnblogs.com/dc93/p/3930362.html, https://blog.csdn.net/OIljt12138/article/details/53861367,
    http://www.cnblogs.com/zhuohan123/p/3233011.html
    Mertens' 2nd theorem

  • 相关阅读:
    C#: 抓取网页类(获取网页中所有信息)
    web application stress tool(WAS) 使用方法
    迁移数据到历史表SQL .
    jquery 获取 自定义属性(attr 和 prop)
    C#: json字符串中的特殊字符处理
    asp.net : 拒绝频繁的IP访问
    SQL : 自动生成订单编号
    WCF: 已超过传入消息(65536)的最大消息大小配额。若要增加配额,请使用相应绑定元素上的 MaxReceivedMessageSize 属性。
    SQL: 随机从数据库取值
    WCF:读取 XML 数据时,超出最大字符串内容长度配额 (8192)。通过更改在创建 XML 读取器时所使用的 XmlDictionaryReaderQuotas 对象的 MaxStringContentLength 属性,可增加此配额。
  • 原文地址:https://www.cnblogs.com/withwhom/p/11624166.html
Copyright © 2020-2023  润新知