Prime

  • 素数的实现有很多种,性质也很多
  • 用于直接判断,查找,构成其他数字等都可能出现,主要在于抽象或联想到素数问题

实现

试除法

  • 时间复杂度是
  bool is_prime(int n)
  {
		if(n < 2)  return false;
		else
	  {
			for(int i = 2; i  <= n / i; i ++) // n / i 是i对应的另一个约数,此为最简最快的判断条件
			if(n / i == 0)  return false;
	  }
	
	return true;
  }

质因数分解

性质

  • 任何合数(非质数的数)都可以拆成多个质因数的乘积
  • 如 30 = 2 * 3 * 5
  • 求解质因数
  • 从最小的质因数开始分解,做短除,一直除到没得除了再找更大的质因数
  void devide(int n)
  {
		for(int i = 2; i <= n; i ++)
	  {
			if(n % i == 0)  //找到质数
		  {
				while(n % i == 0) //短除
			  {
					n /= i;
					s ++;  // 记录短除次数,即该因数的指数
			  }
		  }
			cout << i << s << endl; // 打印出质因数与其次数
	}
  }

注意 为什么可以从2开始直接找质因数,不是要找到质因数,才可以进一步做短除吗?(i为什么是质因数)

  • 首先 i 从2开始找,第一个i为质数
  • 当找过了i个数,即n已经短除了所有2 ~ i - 1的可能质因数 ,此时当 i 的质因数还有 2 — i - 1 中的数的时候,i 不能整除 n
  • 因为n已经除去了所有数,比如 2 当 i 还有 2 这个质因数的时候, i = 2 * m1 * m2 … 等,而 n 不再是2的某个倍数,n % i != 0 ,即无法整除
  • 而此时 i 中由于没有 2 - i - 1 的所有数,所以 i 不满足 任何合数都可以化成质因数相乘结果 的定理,所以 i 不是一个合数,为质数

那么有性质二

  • n当中最多只有一个大于 的质因数
  • 如果有两个,两个相乘就大于n
  • 所以代码可以做更改
  void devide(int n)
  {
		for(int i = 2; i <= n / i; i ++)  //改为 sqrt(n)
	  {
			if(n % i == 0)  //找到质数
		  {
				while(n % i == 0) //短除
			  {
					n /= i;  // 这步最后一次整除后,n会等于1,所以最后一次特判要大于1
					s ++;  // 记录短除次数,即该因数的指数
			  }
		  }
			cout << i << s << endl; // 打印出质因数与其次数
	}
	
		if(n > 1)  cout << n << '1' << endl;
		// 如果n还没有被除尽,那么此时的n就是他的大于 sqrt(n) 的质因子
  }
  • 时间复杂度 最坏 最好

质数筛法

  • 从2开始,将n中2的倍数删掉,依次遍历删除,剩下的数就是质数
  • 因为在删除 i - 1 之前的数的倍数后,如果第 i 个数没有被删,证明它不是 2 - i - 1的倍数,即是质数
void get_prime(int n)
{
	for(int i = 2; i <= n; i ++)
  {
		if(!st[i])  // 该数没有被删
			primes[cnt ++] = n;  // 记下个数
		for(int j = i + i; j <= n; j += i)  st[j] = true;  //删掉该数
  }
}

性质 埃氏筛法

  • 有 1~n 中有 个质数(质数定理)
  • 发现只删去质数的倍数就够了(合数都是质数积)
  • 此时时间复杂度变为
for(int i = 2; i <= n; i ++)
{
	if(!st[i])
	{
		primes[cnt ++] = n;
		for(int j = i + i;  j <= n; j += i)  st[j] = true;
	}
}

性质 线性筛法

void get_prime(int n)
{
   for(int i = 2; i <= n; i ++)
 {
   	if(!st[i])  primes[cnt ++] = i;
   	for(int j = 0; primes[j] <= n / i; j ++)
     {
   		st[primes[j] * i] = true;  // 删到这个遍历到的质数的倍数
   		if(i % primes[j] == 0)  break;  // 如果i可以被这个质数整除,就退出
     }
}
}
  • 首先找质数,在目前找到的质数范围内,遍历质数集合,用一个合数的最小质数来删除这个合数(即 用 最先遍历到的 p[j] 来 删除 p[j] * i 这个合数)
  • 当 i % p[j] = 0 退出,是因为当 i 是 p[j] 的倍数时,i中就有一个小于后面所有素数的最小质因数,用 i * p[j] 来删除就会导致不是用最小的质数来删除合数,所以到此就退出
  • 同时,找最小质因子来删除合数保证了每一个合数都会被删掉(定理),并且每一个数都只被删除一次,因此是线性的