程序员都该掌握的“质因数分解”

说在前面

还记得小学数学课上的"质因数分解 "吗?这个看似基础的概念,实际上是现代数论的基石。在草稿纸上进行 质因数分解 大家应该都会,那怎么通过代码来实现呢?它又能解决什么问题?

什么是质因数分解?

概念

质因数分解 = 把一个合数,拆成「若干个质数相乘」的形式

例子

ini 复制代码
12 = 2 × 2 × 3
  • 2、3 都是质数(只能被 1 和自己整除)
  • 12 是合数(能继续拆)
  • 拆到不能再拆,只剩质数,就叫质因数分解

定理

数学里有一条超级重要的定理:

任何一个大于 1 的整数,只有唯一一种质因数分解方式

怎么做质因数分解?

最实用、最好用的方法:短除法

步骤

  • 1.从最小的质数 2 开始试
  • 2.能除就除,除到不能除为止
  • 3.再换下一个质数 3、5、7、11...
  • 4.直到最后结果是 1

例子

180 进行质因数分解

ini 复制代码
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

质因子分解 便可以比较优雅的避免这两个问题

例子

我们可以把每个数字"升维",用一个指数向量来表示它:

ini 复制代码
12 = 2² × 3¹ × 5⁰ => 向量 [2, 1, 0]
ini 复制代码
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.最大公因数、最小公倍数

辗转相除法 求最大公因数大家都知道吧,那质因数分解 也能求最大公因数你们知道吗?

比如求 1830GCDLCM

分解

ini 复制代码
18 = 2¹ × 3²
30 = 2¹ × 3¹ × 5¹

求最大公因数 (GCD)

取每个公共质因子的最低次幂,然后相乘

  • 公共质因子是 23
  • 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 加密算法。其核心思想可以通俗地理解为:

给你两个巨大的质数 pq ,让你把它们乘起来得到 N ,这在计算上非常容易。 但是,反过来,只告诉你乘积 N ,让你找出原始的 pq 是什么,这在计算上极其困难。

代码实现

说了这么多,那我们如何用代码来实现质因数分解呢?其实非常简单:

javascript 复制代码
/**
 * 对一个正整数进行质因数分解
 * @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,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。

相关推荐
薛定谔的算法1 小时前
有了HTML、CSS、JS为什么还需要React?
前端·javascript·react.js
方安乐1 小时前
react之shadcn(一)
前端·react.js·前端框架
阿珊和她的猫1 小时前
优化过多并发请求的技术策略
前端·javascript·vue.js
with-the-flow1 小时前
从数学底层的底层原理来讲 random 的函数是怎么实现的
c语言·python·算法
阿里云云原生1 小时前
Agent 越用越聪明?AgentScope Java 在线训练插件来了!
前端·agent
tyb3333331 小时前
leetcode:吃苹果和队列
算法·leetcode·职场和发展
fengci.1 小时前
ctfshow渔人杯
前端
多恩Stone1 小时前
【3D-AICG 系列-15】Trellis 2 的 O-voxel Shape: Flexible Dual Grid 代码与论文对应
人工智能·python·算法·3d·aigc
weixin_448119941 小时前
Datawhale 大模型算法全栈基础篇 202602第4次笔记
笔记·算法