这个随笔我是在写以下PAT上题目过来写的,以下是题目测试点
题目就是很简单就能AC,但是对于是判断素数这个问题似乎我一学C语言的时候就已经接触了,对于普通的先开根号在其范围内直接循环取余我就不重复了。
- 介绍第一种方法:
摘抄来自huang_mao_xin的帖子
证明:令x≥1,将大于等于5的自然数表示如下:
······ 6x-1,6x,6x+1,6x+2,6x+3,6x+4,6x+5,6(x+1),6(x+1)+1 ······
可以看到,不在6的倍数两侧,即6x两侧的数为6x+2,6x+3,6x+4,由于2(3x+1),3(2x+1),2(3x+2),所以它们一定不是素数,再除去6x本身,显然,素数要出现只可能出现在6x的相邻两侧。这里有个题外话,关于孪生素数,有兴趣的道友可以再另行了解一下,由于与我们主题无关,暂且跳过。这里要注意的一点是,在6的倍数相邻两侧并不是一定就是质数。
根据以上规律,判断质数可以6个为单元快进,即将方法(2)循环中i++步长加大为6,加快判断速度,代码如下:
bool Isprime(int num) { if (num < 2)return false; //两个较小数另外处理 if (num == 2 || num == 3) return true; //不在6的倍数两侧的一定不是质数 if (num % 6 != 1 && num % 6 != 5) return false; int tmp = sqrt(num); //在6的倍数两侧的也可能不是质数 for (int i = 5; i <= tmp; i += 6) if (num %i == 0 || num % (i + 2) == 0) return false; //排除所有,剩余的是质数 return true; }
第一眼看到的时候感觉这种方法似乎真的很省时,但是拿PAT的测试没有通过,所以问题待定,以后补充
- 第二个方法:叫Eratosthenes筛法,简称“筛法”
原理:如果一个数是素数,那么这个数的倍数一定不是素数,把所有的非素数都去掉,但是这个算法会有冗余,算法效率要比O(n)低,这个方法适合用于筛选出1到N的所有素数这类问题,但不适合用在数较大的情况。
bool flag[N]; void findPrime(int num){ for (int i = 0; i <= num; i++){ flag[i] = true; } flag[0] = flag[1] = false; for (int i = 2; i <= num; i++){ if (!flag[i]) continue; for (int j = i*i; j <= num; j += i){ flag[j] = false; } } }
该算法较优化后:
int a[1000005]; void IsPrime(int num) { memset(a, -1, sizeof(a)); a[0] = a[1] = 0; a[2] = -1; for (int i = 3; i <= num; i++) a[i] = i % 2 == 0 ? 0 : -1; int t = sqrt(num); for (int j = 3; j <= t; j++) if (a[j]) for (int i = j*j; i <= num; i += 2*j) a[i] = 0; }
下面这个是我认为改进很大的筛法:摘抄来着MoreWindows的原创文章
贴段代码,详情点开链接好了
bool flag[MAXN]; int primes[MAXN / 3], pi; void GetPrime_2() { int i, j; pi = 0; memset(flag, false, sizeof(flag)); for (i = 2; i < MAXN; i++) { if (!flag[i]) primes[pi++] = i; for (j = 0; (j < pi) && (i * primes[j] < MAXN); j++) flag[i * primes[j]] = true; } }