素数(质数):一个正整数n,除了1和其本身外不能被其他任何数整除,否则成为合数。
1 既不是素数也不是合数。
素数的判断:
对于一个整数n,判断素数要判断其是否能被2,3,…, n-1范围内的数整除,时间复杂度是O(n),但是这样会多出很多的运算。如果一个整数k可以被n整除,由于k*(n/k)==n,所以n/k也能被n整除,且两个数一定满足其中一个小于等于sqrt(n),所以在遍历判断时只需要遍历到sqrt(n)就可以了。
bool isPrime(int n){ if(n <= 1) return false; //特判 int sqr = (int)sqrt(1.0*n); //对n取根号 for(int i=2; i<=sqr; i++){ if(n % i == 0) return false; } return true; //n 是素数 }
由上面的思路,我们可以使用打表的技巧,将从1~n之间的每个数进行判断,如果是素数就加入素数表。方便之后的查找使用。
//求素数表 const int maxn = 101; //表长 int prime[maxn], pNum = 0; //prime数组存放所有素数,pNum为素数个数 bool p[maxn] = {0} ; // p[i] == true 表示i是素数 void Find_Prime(){ for(int i= 1; i< maxn; i++){ if(isPrime(i) == true){ prime[pNum++] = i; //是素数则放入Prime数组 p[i] = true; } } }
下面是求0-100以内所有素数的完整代码:100以内素数的代码
#include <cstdio> #include <cmath> using namespace std; //求0-100以内的所有素数 bool isPrime(int n){ if(n <= 1) return false; //特判 int sqr = (int)sqrt(1.0*n); //对n取根号 for(int i=2; i<=sqr; i++){ if(n % i == 0) return false; } return true; //n 是素数 } //求素数表 const int maxn = 101; //表长 int prime[maxn], pNum = 0; //prime数组存放所有素数,pNum为素数个数 bool p[maxn] = {0} ; // p[i] == true 表示i是素数 void Find_Prime(){ for(int i= 1; i< maxn; i++){ if(isPrime(i) == true){ prime[pNum++] = i; //是素数则放入Prime数组 p[i] = true; } } } int main(){ Find_Prime(); for(int i=0;i<pNum; i++){ printf("%d ", prime[i]); } }
上面的方法只适用于n在〖10〗^5 以内的数,再大的数字就不能承受了,所以可以使用下面的方法,素数筛选法,“埃氏筛法”是比较容易理解的一种,时间复杂度为O(nloglogn)。
算法思路是:从小到大枚举所有数,对每一个素数,筛去他的所有倍数,剩下的就都是素数了。因为当遍历到a,如果a不是素数,那么a一定有小于a的素数因子,这样之前的步骤中a一定会被筛掉,所以如果没有被筛掉,则一定是素数。初始时,先确定一个素数,可以从2开始。
下面的代码时输出任意第m-n个素数的代码,如下
#include <cstdio> #include <iostream> #include <cmath> using namespace std; const int maxn = 1000001; //表长 int prime[maxn], pNum = 0; bool p[maxn] = {0}; //标记是否为素数,如果被筛掉则设为true void Find_Prime(int n){ for(int i = 2; i < maxn; i++){ if(p[i] == false){ prime[pNum++] = i; if(pNum >= n) break; //只需要n个素数 for(int j = i+i; j < maxn; j += i){ //筛去i的所有倍数,即让j每次加上i p[j] = true; } } } } int main(){ int m, n, count = 0; scanf("%d %d", &m, &n); Find_Prime(n); for(int i=m; i<=n; i++){ printf("%d", prime[i-1]); count++; if(count % 10 != 0 && i < n) printf(" "); else printf("\n"); } return 0; }
质因子分解:
即把一个正整数写成一个或多个质数相乘的形式。所以可以使用上面打印出来的素数表,并且由于每个质因子都可以不出现一次,所以可以定义一个结构体factor,如下所示:
struct factor{ int x, cnt; //x为质因子, cnt为其个数 }fac[10];
下面的代码时求解PAT A1059题,给定一个正整数N,然后对其进行质因数分解。
#include <cstdio> #include <iostream> #include <cmath> using namespace std; const int maxn = 100010; bool is_prime(int n){ if(n == 1) return false; int sqr = (int)sqrt(n*1.0); for(int i=2; i <= sqr; i++){ if(n % i == 0) return false; } return true; } int prime[maxn], pNum=0; void Find_Prime(){ for(int i = 1;i < maxn; i++){ if(is_prime(i) == true){ prime[pNum++] = i; } } } struct factor{ int x, cnt; //x为质因子, cnt为其个数 }fac[10]; int main(){ Find_Prime(); int n, num = 0; scanf("%d", &n); if(n == 1) printf("1=1"); //特判1的情况 else{ printf("%d=" , n); int sqr = (int)sqrt(n*1.0); for(int i=0;i<pNum && prime[i] <= sqr; i++){ //枚举根号n以内的素数 if(n % prime[i] == 0) { //如果prime[i]是n的因子 fac[num].x = prime[i]; //先放入质因子数组 fac[num].cnt = 0; //计算出质因子prime[i]的个数 while(n % prime[i] == 0){ fac[num].cnt++; n /= prime[i]; } num++; //不同质因子个数加1 } if(n == 1) break; //及时跳出循环 } //如果不能被根号n以内的质因子除尽 if(n != 1) { fac[num].x = n; //一定有一个大于根号n的因子即其本身 fac[num++].cnt = 1; } //按格式输出 for(int i = 0;i < num; i++){ if(i > 0) printf("*"); printf("%d", fac[i].x); if(fac[i].cnt > 1){ printf("^%d",fac[i].cnt); } } } return 0; }
最后记录一个结论:如果要求一个正整数N的因子个数,那么只需要得到各质因子pi的个数e1,e2,…,ek,于是N的因子个数就是(e1+1) * (e2+1)* … *(ek+1)。原因是对每个质因子pi都可以选择其出现0次,1次,…,ei次,共(ei+1)种可能,所以可以有很多种组合,组合起来就是就是答案;
参考资料: 《算法笔记》 胡凡/曾磊
来源:https://www.cnblogs.com/hhhuang/p/12034693.html