(左程云)算法讲解097【必备】质数判断、质因子分解、质数筛

题目1:判断较小的数字是否是质数

一个数字n,从 2 开始到 根号n,看看这些数字能否被n整除即可,时间复杂度O(根号n)

public static boolean isPrime(long n) {
    if (n <= 1) {
        return false;
    }
    // 2 ... 根号n
    for (long i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            return false;
        }
    }
    return true;
}

题目2:判断较大的数字是否是质数(Miller-Rabin测试)

测试链接 : https://www.luogu.com.cn/problem/U148828

判断n是否是质数,Miller-Rabin测试大概过程:
1,每次选择1 ~ n-1范围上的随机数字,或者指定一个比n小的质数,进行测试
2,测试过程的数学原理不用纠结,不重要,因为该原理除了判断质数以外,不再用于别的方面
3,原理:费马小定理、Carmichael(卡米切尔数)、二次探测定理(算法导论31章)、乘法同余、快速幂
4,经过s次Miller-Rabin测试,s越大出错几率越低,但是速度也会越慢,一般测试20次以内即可

重点是用法
因为有乘法同余,所以想验证任意的long类型的数字,需要注意位数的事情
代码中都标记好了,我们说明一下:java模版、 C++模版
时间复杂度O( s * (logn)的三次方 ),速度很快

<=10^9

package class097;

// 判断较大的数字是否是质数(Miller-Rabin测试)
// 测试链接 : https://www.luogu.com.cn/problem/U148828
// 如下代码无法通过所有测试用例
// 本文件可以解决10^9范围内数字的质数检查
// 时间复杂度O(s * (logn)的三次方),很快
// 为什么不能搞定所有long类型的数字检查
// 原因在于long类型位数不够,乘法同余的时候会溢出,课上已经做了说明

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

public class Code02_LargeNumberIsPrime1 {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
		int t = Integer.valueOf(br.readLine());
		for (int i = 0; i < t; i++) {
			// 这里要注意
			// 如果读入long类型的数字,数字本身很大的话
			// 先读出字符串str,然后用Long.valueOf(str)
			long n = Long.valueOf(br.readLine());
			out.println(millerRabin(n) ? "Yes" : "No");
		}
		out.flush();
		out.close();
		br.close();
	}

	// 质数的个数代表测试次数
	// 如果想增加测试次数就继续增加更大的质数
	public static long[] p = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 };

	public static boolean millerRabin(long n) {
		if (n <= 2) {
			return n == 2;
		}
		if ((n & 1) == 0) {
			return false;
		}
		for (int i = 0; i < p.length && p[i] < n; i++) {
			if (witness(p[i], n)) {
				return false;
			}
		}
		return true;
	}

	// 单次测试的函数,不用纠结原理
	// 返回n是不是合数
	public static boolean witness(long a, long n) {
		long u = n - 1;
		int t = 0;
		while ((u & 1) == 0) {
			t++;
			u >>= 1;
		}
		long x1 = power(a, u, n), x2;
		for (int i = 1; i <= t; i++) {
			x2 = power(x1, 2, n);
			if (x2 == 1 && x1 != 1 && x1 != n - 1) {
				return true;
			}
			x1 = x2;
		}
		if (x1 != 1) {
			return true;
		}
		return false;
	}

	// 返回 : n的p次方 % mod
	// 快速幂,讲解098会重点讲述,此时直接用即可
	public static long power(long n, long p, long mod) {
		long ans = 1;
		while (p > 0) {
			if ((p & 1) == 1) {
				ans = (ans * n) % mod;
			}
			n = (n * n) % mod;
			p >>= 1;
		}
		return ans;
	}

}

大于10^9

package class097;

// 判断较大的数字是否是质数(Miller-Rabin测试)
// 测试链接 : https://www.luogu.com.cn/problem/U148828
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的code,提交时请把类名改成"Main",可以通过所有测试用例
// 本文件可以搞定任意范围数字的质数检查,时间复杂度O(s * (logn)的三次方)
// 为什么不自己写,为什么要用BigInteger中的isProbablePrime方法
// 原因在于long类型位数不够,乘法同余的时候会溢出,课上已经做了说明

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.math.BigInteger;

public class Code02_LargeNumberIsPrime2 {

	// 测试次数,次数越多失误率越低,但速度也越慢
	public static int s = 10;

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
		int t = Integer.valueOf(br.readLine());
		for (int i = 0; i < t; i++) {
			BigInteger n = new BigInteger(br.readLine());
			// isProbablePrime方法包含MillerRabin和LucasLehmer测试
			// 给定测试次数s即可
			out.println(n.isProbablePrime(s) ? "Yes" : "No");
		}
		out.flush();
		out.close();
		br.close();
	}

}

较大数字读入

package class097;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;

// 读入long类型数字的注意点
// 讲解019的扩展,没有看过讲解019的同学去看一下
public class Code02_InputLong {

	public static void main(String[] args) throws IOException {
		f1();
		f2();
	}

	public static void f1() throws IOException {
		System.out.println("f1函数测试读入");
		// 尝试读入 : 131237128371723187
		// in.nval读出的是double类型
		// double类型64位
		// long类型也是64位
		// double的64位会分配若干位去表达小数部分
		// long类型的64位全用来表达整数部分
		// 所以读入是long范围的数,如果用以下的写法
		// in.nval会先变成double类型,如果再转成long类型,就可能有精度损耗
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StreamTokenizer in = new StreamTokenizer(br);
		PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
		in.nextToken();
		long num = (long) in.nval;
		out.println(num);
		out.flush();
	}

	public static void f2() throws IOException {
		System.out.println("f2函数测试读入");
		// 尝试读入 : 131237128371723187
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
		// 直接读出字符串
		String str = br.readLine();
		// 然后把字符串转成long
		// 不可能有精度损耗
		long num = Long.valueOf(str);
		out.println(num);
		out.flush();
	}

}

题目3:质因子分解

时间复杂度O(根号n)
有关质因数分解,课上讲的方法足够了
有兴趣的同学可以继续研究
pollard_rho启发式方法分解质因数

void f(int n) {
    for (int j = 2; j * j <= n; j++) {      
        if (n % j == 0) {
            print(j);
            while (n % j == 0) { n /= j; }
        }
    }
    if (n > 1) {
        print(n);
    }
}

题目4:按公因数计算最大组件大小

给定一个由不同正整数的组成的非空数组 nums
如果 nums[i] 和 nums[j] 有一个大于1的公因子,那么这两个数之间有一条无向边
返回 nums中最大连通组件的大小
测试链接 : https://leetcode.cn/problems/largest-component-size-by-common-factor/

class Solution {
public:
    vector<int> p,s;
    int find(int x){
        if(x!=p[x])p[x]=find(p[x]);
        return p[x];
    }
    int largestComponentSize(vector<int>& nums) {
        int n=nums.size();
        for(int i=0;i<n;i++){
            p.push_back(i);
            s.push_back(1);
        }

        unordered_map<int,vector<int>> q;
        for(int i=0;i<n;i++){
            int x=nums[i];
            for(int j=1;j*j<=x;j++){
                if(x%j==0){
                    if(j>1) q[j].push_back(i);
                    q[x/j].push_back(i);
                }
            }
        }

        int res=1;
        for(auto [k,v]:q){
            for(int i=1;i<v.size();i++){
                int a=v[0],b=v[i];
                if(find(a)!=find(b)){
                    s[find(a)]+=s[find(b)];
                     p[find(b)]=find(a);
                    res=max(res,s[find(a)]);
                }
            }
        }
        return res;
    }
};

题目5:给定整数n,返回 1~n 范围上所有的质数

埃氏筛,时间复杂度O(n * log(logn)),我们图解一下
欧拉筛,时间复杂度O(n),很精妙,我们图解一下
其实掌握埃氏筛足够了,因为时间复杂度非常接近线性了,而且常数时间很不错
给定整数n,返回 小于n 的质数的数量
测试链接 : https://leetcode.cn/problems/count-primes/

public static int countPrimes(int n) {
    return ehrlich(n - 1);
}

// 埃氏筛统计0 ~ n范围内的质数个数
// 时间复杂度O(n * log(logn))
public static int ehrlich(int n) {
    // visit[i] = true,代表i是合数
    // visit[i] = false,代表i是质数
    // 初始时认为0~n所有数都是质数
    boolean[] visit = new boolean[n + 1];
    for (int i = 2; i * i <= n; i++) {
        if (!visit[i]) {
            for (int j = i * i; j <= n; j += i) {
                visit[j] = true;
            }
        }
    }
    int cnt = 0;
    for (int i = 2; i <= n; i++) {
        if (!visit[i]) {
            // 此时i就是质数,可以收集,也可以计数
            cnt++;
        }
    }
    return cnt;
}

// 欧拉筛统计0 ~ n范围内的质数个数,防止重复,埃氏筛法优化
// 时间复杂度O(n)
public static int euler(int n) {
    // visit[i] = true,代表i是合数
    // visit[i] = false,代表i是质数
    // 初始时认为0~n所有数都是质数
    boolean[] visit = new boolean[n + 1];
    // prime收集所有的质数,收集的个数是cnt
    int[] prime = new int[n / 2 + 1];
    int cnt = 0;
    for (int i = 2; i <= n; i++) {
        if (!visit[i]) {
            prime[cnt++] = i;
        }
        for (int j = 0; j < cnt; j++) {
            if (i * prime[j] > n) {
                break;
            }
            visit[i * prime[j]] = true;
            if (i % prime[j] == 0) {
                break;
            }
        }
    }
    return cnt;
}

// 只是计数的话
// 埃氏筛还能改进
public static int ehrlich2(int n) {
    if (n <= 1) {
        return 0;
    }
    // visit[i] = true,代表i是合数
    // visit[i] = false,代表i是质数
    // 初始时认为0~n所有数都是质数
    boolean[] visit = new boolean[n + 1];
    // 先把所有的偶数去掉,但是算上2
    // 估计的质数数量,如果发现更多合数,那么cnt--
    int cnt = (n + 1) / 2;
    for (int i = 3; i * i <= n; i += 2) {
        if (!visit[i]) {
            for (int j = i * i; j <= n; j += 2 * i) {
                if (!visit[j]) {
                    visit[j] = true;
                    cnt--;
                }
            }
        }
    }
    return cnt;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值