说在前面
还记得小学数学课上的“质因数分解”吗?这个看似基础的概念,实际上是现代数论的基石。在草稿纸上进行 质因数分解 大家应该都会,那怎么通过代码来实现呢?它又能解决什么问题?
什么是质因数分解?
概念
质因数分解 = 把一个合数,拆成「若干个质数相乘」的形式
例子
12 = 2 × 2 × 3
- 2、3 都是质数(只能被 1 和自己整除)
- 12 是合数(能继续拆)
- 拆到不能再拆,只剩质数,就叫质因数分解
定理
数学里有一条超级重要的定理:
任何一个大于 1 的整数,只有唯一一种质因数分解方式。
怎么做质因数分解?
最实用、最好用的方法:短除法
步骤
- 1.从最小的质数 2 开始试
- 2.能除就除,除到不能除为止
- 3.再换下一个质数 3、5、7、11…
- 4.直到最后结果是 1
例子
对 180 进行质因数分解
180 ÷ 2 = 90
90 ÷ 2 = 45
45 ÷ 3 = 15
15 ÷ 3 = 5
5 ÷ 5 = 1
所以: 180 = 2² × 3² × 5¹
质因数分解有什么用?
1. 将“乘除”降维成“加减”
在编程中进行算数乘除运算很容易会遇到两个问题:
- 数字溢出:几个数一相乘,结果可能超出计算机能表示的最大整数范围
- 精度丢失:一旦引入除法,就可能出现小数,而浮点数的存储和比较天生存在精度误差
1 / 6 * 5 * 5 * 2 * 3
上面这个式子我们快速过一遍不难看出最后的结果应该是 25,但是电脑算出来的结果却是 24.999999999999996
质因子分解 便可以比较优雅的避免这两个问题
例子
我们可以把每个数字“升维”,用一个指数向量来表示它:
12 = 2² × 3¹ × 5⁰ => 向量 [2, 1, 0]
10 = 2¹ × 3⁰ × 5¹ => 向量 [1, 0, 1]
-
乘法 → 向量加法 12 × 10 = 120 对应的向量运算是:[2, 1, 0] + [1, 0, 1] = [3, 1, 1]
验证一下:120 = 8 × 3 × 5 = 2³ × 3¹ × 5¹。向量正是 [3, 1, 1]
-
除法 → 向量减法 120 / 10 = 12 对应的向量运算是:[3, 1, 1] - [1, 0, 1] = [2, 1, 0]
结果 [2, 1, 0] 正是 12 的向量表示
通过质因数分解,我们可以将复杂的、易出错的乘除法,转换成了简单、精确的整数加减法。
2.最大公因数、最小公倍数
辗转相除法 求最大公因数大家都知道吧,那质因数分解 也能求最大公因数你们知道吗?
比如求 18 和 30 的 GCD 和 LCM:
分解
18 = 2¹ × 3²
30 = 2¹ × 3¹ × 5¹
求最大公因数 (GCD)
取每个公共质因子的最低次幂,然后相乘
- 公共质因子是 2 和 3。
- 2 的最低次幂是 min(1, 1) = 1。
- 3 的最低次幂是 min(2, 1) = 1。
- GCD = 2¹ × 3¹ = 6。
求最小公倍数 (LCM)
取所有出现过的质因子的最高次幂,然后相乘
- 所有质因子是 2, 3, 5。
- 2 的最高次幂是 max(1, 1) = 1。
- 3 的最高次幂是 max(2, 1) = 2。
- 5 的最高次幂是 max(0, 1) = 1。
- LCM = 2¹ × 3² × 5¹ = 90。
3.现代密码学的基石
我们每天都在使用的 HTTPS、网上银行、数字签名,其安全性的根基,都与质因数分解的“不对称性”有关。
如 RSA 加密算法。其核心思想可以通俗地理解为:
给你两个巨大的质数 p 和 q,让你把它们乘起来得到 N,这在计算上非常容易。 但是,反过来,只告诉你乘积 N,让你找出原始的 p 和 q 是什么,这在计算上极其困难。
代码实现
说了这么多,那我们如何用代码来实现质因数分解呢?其实非常简单:
/**
* 对一个正整数进行质因数分解
* @param {number} n - 需要分解的正整数
* @returns {Map<number, number>} - 返回一个 Map,键是质因子,值是其指数
*/
function primeFactorize(n) {
if (n <= 1) {
return new Map();
}
const factors = new Map();
// 不断除以2,处理所有偶数因子
while (n % 2 === 0) {
factors.set(2, (factors.get(2) || 0) + 1);
n /= 2;
}
// 从3开始遍历奇数,直到 n 的平方根
// 如果 n 有一个大于其平方根的因子,必然会有一个小于其平方根的因子
for (let i = 3; i * i <= n; i += 2) {
while (n % i === 0) {
factors.set(i, (factors.get(i) || 0) + 1);
n /= i;
}
}
// 如果最后 n 还大于1,那么 n 本身也是一个质数
if (n > 1) {
factors.set(n, (factors.get(n) || 0) + 1);
}
return factors;
}
console.log(primeFactorize(120));
// 输出: Map { 2 => 3, 3 => 1, 5 => 1 }
console.log(primeFactorize(999));
// 输出: Map {3 => 3, 37 => 1}
公众号
关注公众号『 前端也能这么有趣 』,获取更多有趣内容~
发送 加群 还能加入前端交流群,和大家一起讨论技术、分享经验,偶尔也能摸鱼聊天~
说在后面
🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。