算法系列--打印素数(详解)

继续算法之旅。关于素数判定这个问题,也是一个很经典的程序设计题目。

概念:质数(prime number)又称素数,有无限个。一个大于1的自然数,除了1和它本身外,不能被其他自然数整除,换句话说就是该数除了1和它本身以外不再有其他的因数,否则称为合数

朴素法

分析:如果要判定一个数自然数n(n>1)是不是素数,从素数定义出发,首先想到的肯定是采用循环,遍历 2至n-1所有值,逐个与n除,判定是否可以整除,如果存在能够整除的情况,则n不为素数。

改进方法:脱离单纯程序实现的角度,你会发现可以改进的地方在于遍历的范围,如果略作思考,你会发现n/2到n-1的值是不用遍历的,因为最小的试探除数是2,大于n/2的数肯定不能被整除。进一步思考,遍历的范围还可以继续缩小,其实只需将遍历的上限设置成n的平方根即可。简单分析,假设 n可以被p整除,结果为q ,即q=n/p,如果p大于n的平方根,q必定小于n的平方根,所以只要测试n的平方根以下的数字即可,这样可以进一步提高运算效率。


以下是代码:

public class Prime {
	/**
	 * 打印1-100的素数
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		for (int i = 1; i <= 100; i++)
			if (isPrime(i))
				System.out.println(i);

	}

	public static boolean isPrime(int num) {
		if (num == 1)
			return false;
		int max = (int) Math.sqrt(num);
		for (int i = 2; i <= max; i++)
			if (num % i == 0)
				return false;
		return true;
	}
}

筛数法

  筛数法求素数的基本思想是:把从1开始的、某一范围内的正整数从小到大顺序排列, 1不是素数,首先把它筛掉。剩下的数中选择最小的数是素数,然后去掉它的倍数。依次类推,直到筛子为空时结束。

具体做法如下:
<1> 先将1挖掉(因为1不是素数)。
<2> 用2去除它后面的各个数,把能被2整除的数挖掉,即把2的倍数挖掉。
<3> 用3去除它后面的各数,把3的倍数挖掉。
<4> 分别用4、5…各数作为除数去除这些数以后的各数。(事实上,可以简化,如果需要找1~n范围内素数表,只需进行到除数为n^2(根号n),取其整数即可。例如对1~50,只需进行到将50^2作为除数即可。)

如上算法可表示为:
<1> 挖去1;
<2> 用刚才被挖去的数的下一个数p去除p后面各数,把p的倍数挖掉;
<3> 检查p是否小于n^2的整数部分(如果n=1000, 则检查p<31?),如果是,则返回(2)继续执行,否则就结束;
<4> 纸上剩下的数就是素数。


特别说明:为什么除数的筛选范围只到问题规模N的平方根即可?因为通过分析可以知道,当除数的范围大于这个上限(N的平方根取整)时,所有的要筛选的值都和之前筛选过的重复,继续筛选无效。

代码如下:

/**
	 * 删选法求素数
	 * 
	 * @param N
	 */
	public static void isPrime2(int N) {
		int[] flags = new int[N + 1];
		for (int i = 0; i < N + 1; i++)
			flags[i] = i;
		flags[1] = 0;// 排除1,1不为素数
		int max = (int) Math.sqrt(N);// 设定N的除数的范围,小于N的平方根
		for (int i = 2; i <=max; i++) {
			for (int j = i + 1; j <= N; j++) {
				//筛选,将N的除数的倍数去掉
				if (flags[j] != 0 && flags[j] % i == 0) {
					flags[j] = 0;
				}
			}
		}
		for (int i = 1; i <= N; i++) {
			if (flags[i] != 0) {
				System.out.println(flags[i]);
			}
		}

	}

6N±1法求素数

  任何一个自然数,总可以表示成为如下的形式之一:

  6N,6N+1,6N+2,6N+3,6N+4,6N+5 (N=0,1,2,…)

  显然,当N≥1时,6N,6N+2,6N+3,6N+4都不是素数,只有形如6N+1和6N+5的自然数有可能是素数。所以,除了2和3之外,所有的素数都可以表示成6N±1的形式(N为自然数)。根据上述分析,我们只对形如6 N±1的自然数进行筛选,这样就可以大大减少筛选的次数,从而进一步提高程序的运行效率和速度。

实现思路:可以和朴素法判定单个自然数是否为素数结合起来,利用循环递增获取指定范围具备6 N±1特征的数字,利用朴素法来判定数字是否为素数,打印出结果。

以下是代码

	/*
	 * 6N+1法求素数
	 */

	public static void isPrime3(int N) {
		if (N <= 1)
			System.out.println("此范围无素数");
		else if (N > 1 && N <= 2)
			System.out.println(2);
		else if (N > 2 && N <= 4) {
			System.out.println(2);
			System.out.println(3);
		} else {
			System.out.println(2);
			System.out.println(3);
			int k = 1, pre = 0, next = 0;
			while (true) {
				pre = 6 * k - 1;
				next = 6 * k + 1;
				if (isPrime1(pre) && pre <= N)
					System.out.println(pre);
				else if (pre > N)
					break;
				if (isPrime1(next) && next <= N)
					System.out.println(next);
				else if (next > N)
					break;
				k++;
			}
		}
	}

特别说明:以上只给出代码样例,并不能保证代码的权威性,加之作者水平有限,难免有不足之处,还望大家多多包涵。


你可能感兴趣的:(算法,算法系列)