第四章 数学知识 质数相关

1、质数判定

1. 问题解释

所谓质数判定,就是给定一个数,判断一下该数是否为质数。

质数(Prime number),又称素数,指在大于1的自然数中,除了1和该数自身外,无法被其他自然数整除的数(也可定义为只有1与该数本身两个正因数的数)。大于1的自然数若不是素数,则称之为合数(也称为合成数)。例如,5是个素数,因为其正约数只有1与5。

2.模板代码及注释

bool prime(int n)
{
	//如果要判断的数字小于2,根据定义可以直接返回结果。
	if(n < 2) return false;
	//对于每个可能的约数,如果n能整除,则证明n有除了1和其自身之外的约数,返回false
	for(int i = 2; i <= n / i; i ++)
	{
		if(n % i == 0) return false;
	}
	return true;
}

对于i <= n / i 的判断。对于每个数n,其实并不需要从2判断到n-1,我们知道,一个数若可以进行因数分解,那么分解时得到的两个数一定是一个小于等于sqrt(n),一个大于等于sqrt(n),据此,上述代码中并不需要遍历到n-1,遍历到sqrt(n)即可,因为若sqrt(n)左侧找不到约数,那么右侧也一定找不到约数。而调用函数会让操作变慢,模板中的写法,不会溢出,且效率较高。

3.时间复杂度分析

O ( s q r t ( N ) ) O(sqrt(N)) O(sqrt(N))

2、分解质因数

1. 问题解释

素因数在数论里是指能整除给定正整数的素数。根据算术基本定理,不考虑排列顺序的情况下,每个正整数都能够以唯一的方式表示成它的素因数的乘积。将一个正整数表示成素因数乘积的过程和得到的表示结果叫做素因数分解。显示素因数分解结果时,如果其中某个素因数出现了不止一次,可以用幂次的形式表示。例如360的素因数分解是:
在这里插入图片描述
其中的素因数2、3、5在360的素因数分解中的幂次分别是3,2,1。

2.模板代码及注释

//将结果存储在ans中
unordered_map<int,int> ans;
void prime(int x)
{
   for(int i = 2; i <= n / i; i ++)
   {
   	  //如果x可以整除i
   	  if(x % i == 0)
   	  {
   	  	 //将x中的所有i元素删除
   	     while(x % i == 0)
   	     {
   	   	   x /= i;
   	   	   //记录下x中质因子i的数量
   	   	   ans[i] ++;
   	   	  }
   	   }
   }
   // 最后如果n还是>1,说明这就是大于sqrt(n)的唯一质因子,输出即可。
   if(x > 1) ans[x] ++;
} 

这里说明一下,为什么我们记录下来的i一定是质数。假如 i 是一个合数,那么它一定可以分解成多个质因子相乘的形式,这多个质因子同时也是 a 的质因子且比 i 要小,而比 i 小的数在之前的循环过程中一定是被条件除完了的,所以 i 不可能是合数,只可能是质数。所以可以直接记录。
资料来源

3.时间复杂度

O ( s q r t ( N ) ) > T > O ( l o g 2 N ) O(sqrt(N))>T>O(log_2N ) O(sqrt(N))>T>O(log2N)

3、筛质数

1.问题解释

所谓筛质数,是指快速的从1-n中,筛出来所有的质数。

2. 模板代码及注释

1.埃氏筛法

用质数将所有的合数都删掉

void eratosthenes(){
    for(int i=2;i<=n;i++){
        if(!st[i]){
            //这里所有比i小的数字均已经遍历过一遍,所有i不包含任何除了1和他自己以外的约数,就是质数
            primes[cnt++]=i;
            for(int j=i;j<=n;j+=i) st[j]=true;//可以用质数就把所有的合数都筛掉;
        }
    }
}

下面的动图很好的演示了这个过程。
第四章 数学知识 质数相关_第1张图片
资料来源
时间复杂度 O ( n l o g l o g n ) O(nloglogn) O(nloglogn)

2.线性筛法

在埃氏筛法中,有些合数可能不止被删除一次。例如6,被2删除过一次,又被3删除过一次。线性筛法的核心就是让每个合数只被其最小质因数删除一次。这样就可以提高时间效率。
如下面代码所示,对于一个合数x,假设p[j]是他的最小质因子,当i枚举到x / p[j]的时候,就会被筛掉,每个数字只会被筛一遍。

//用来存储所有质数
int p[N];
//用来存储这个数是不是被筛掉了
bool st[N];
void prime(){
    for(int i=2;i<=n;i++){
        if(!st[i]) p[cnt++]=i;
        //1
        for(int j=0;p[j]<=n/i;j++){
            st[p[j]*i]=true;
            //2 
            if(i%p[j]==0) break;
        }
    }
}
  1. 为什么不需要j < cnt
    因为里面的if判断使得不会超的,如果i是一个合数,那么之前的质数一定有他的质因子,会break;
    如果是质数,那么那他一定也在这个prime中,一定也会break
  2. 为什么要用这个if进行break
    因为线性筛法,其目的是用最小质因子去筛掉合数。如果不break,例如
    i = 4 如果在 p[j] = 2 的时候没有break,那么他会更新 12 但是这个不是线性筛法想要的,线性筛法
    应该在i = 6 时,用2这个质因子筛掉,然后break。这个4包含了2 同时还包含了一个其他的数,会造成影响
  3. 为什么p[j]一定是p[j] * i 的最小质因子
    首先明确,根据算法,p中存储的质数是从小到大排列的。
    1 如果i%p[j] == 0,那么p[j]一定是i的最小质因子,也一定是p[j]*i的最小质因子
    2 如果i%p[j]!= 0,那么p[j]一定小于i的所有质因子,所以也一定是最小质因子

时间复杂度 O ( n ) O(n) O(n)

参考资料

1.质数
2.质因数
3.Acwing
4.其他参考资料均在文中标注

你可能感兴趣的:(Acwing算法基础课笔记,c++,开发语言,后端)