质因数个数问题:高效分解算法详解

一、题目理解

核心要求

给定正整数 N1 \< N \< 10\^9),计算其质因数的总个数(含重复计数)

例如:120 = 2 \\times 2 \\times 2 \\times 3 \\times 5 → 输出 5

输入输出

  • 输入:多组测试数据,每行一个正整数 N
  • 输出:每组数据输出对应的质因数个数

关键约束

  • N 的范围:1 \< N \< 10\^9
  • 可能有多组测试数据
  • 需要高效处理大数情况

二、解题思路演进

方法一:暴力枚举(不可行)

思路:从 2 到 N-1 逐个检查是否为因数,如果是则继续分解。

问题

  • 时间复杂度:O(N)
  • N = 10\^9 时,需要循环近 10\^9 次,超时
  • 无法在 1 秒内完成

方法二:优化试除法(当前代码思路)

核心思想:利用数学性质优化

关键观察

  1. 质因数范围限制:任何合数 N 的最小质因数必然 ≤ \\sqrt{N}
  2. 大质因数唯一性:如果 N 有大于 \\sqrt{N} 的质因数,则最多只有一个

算法步骤

  1. 从 2 开始试除,直到 \\sqrt{N}
  2. 每找到一个因数 i,循环除尽所有 i 因子(保证重复计数)
  3. 循环结束后,如果 N \> 1,说明剩余部分是大质因数

正确性证明

  • 假设 N = p_1 \\times p_2 \\times ... \\times p_kp_i 为质数)
  • 如果所有 p_i \\leq \\sqrt{N},则循环会找到所有因子
  • 如果存在 p_i \> \\sqrt{N},则这样的 p_i 最多一个(否则乘积 > N
  • 循环结束后剩余的 N 即为这个大质因数

三、算法设计

使用的算法

  • 试除法(Trial Division)
  • 贪心策略:从小到大找最小质因数

关键步骤拆解

cpp 复制代码
1. 初始化计数器 count = 0
2. 从 i = 2 到 √N 循环:
   a. 如果 i 能整除 N:
      - 循环除以 i,每次 count++
      - 直到 N 不能被 i 整除
3. 如果循环结束后 N > 1:
   - 说明剩余的是大质因数,count++
4. 返回 count

四、代码解析

提供的代码分析

cpp 复制代码
#include<iostream>
#include<cmath>
using namespace std;

int ans(int n){
    int count=0;
    
    for(int i=2; i<sqrt(n); i++){  // ⚠️ 这里有bug!
        while(n%i==0){
            count++;
            n/=i;
        }
    }
    if(n>1)
        count++;
    
    return count;
}

代码Bug分析

问题i < sqrt(n) 应该改为 i * i <= ni <= sqrt(n)

原因

  1. 边界问题 :当 \\sqrt{N} 是整数时,i < sqrt(n) 会漏掉 i = \\sqrt{N} 的情况
  2. 动态变化n 在循环中不断变小,每次调用 sqrt(n) 效率低
  3. 浮点精度sqrt() 返回浮点数,可能存在精度问题

举例说明

  • N = 25\\sqrt{25} = 5
  • i < sqrt(25)i < 5,循环只到 i=4
  • 会漏掉因子 5,导致错误结果

修正后的代码

cpp 复制代码
#include<iostream>
using namespace std;

int ans(int n){
    int count = 0;
    
    // 优化1:使用 i * i <= n 避免浮点运算
    for(int i = 2; i * i <= n; i++){
        while(n % i == 0){
            count++;
            n /= i;
        }
    }
    
    // 处理剩余的大质因数
    if(n > 1)
        count++;
    
    return count;
}

int main(){
    int n;
    while (cin >> n) {
        cout << ans(n) << endl;
    }
    return 0;
}

代码逐行解释

行号 代码 说明
1-2 #include... 引入必要头文件
4 int ans(int n) 定义分解函数
5 int count = 0; 初始化计数器
8 for(int i = 2; i * i <= n; i++) 关键:只试除到 √n
9-12 while(n % i == 0) 关键:除尽所有 i 因子
15-16 if(n > 1) count++; 关键:处理剩余大质因数
19-24 main() 处理多组输入

难点与易错点

问题 说明 解决方案
循环条件 i < sqrt(n) 会漏边界 改为 i * i <= n
重复计数 需要 while 循环除尽 不能用 if 只除一次
大质因数 循环后剩余部分需处理 if(n > 1) count++
效率问题 频繁调用 sqrt() i * i <= n 替代
整数溢出 i * i 可能超范围 使用 long long 转换

五、复杂度分析

时间复杂度

单次查询O(\\sqrt{N})

详细分析

  • 最坏情况:N 是质数,需要试除到 \\sqrt{N}
  • N = 10\^9 时,\\sqrt{N} \\approx 31623
  • 实际运行次数远小于理论值(找到因子后 n 会变小)

多组数据O(T \\times \\sqrt{N}),其中 T 为测试数据组数

空间复杂度

O(1) - 仅使用常数级额外空间


六、优化方案对比

方案一:基础优化(当前方案)

cpp 复制代码
int ans(int n){
    int count = 0;
    for(int i = 2; i * i <= n; i++){
        while(n % i == 0){
            count++;
            n /= i;
        }
    }
    if(n > 1) count++;
    return count;
}

优点 :代码简洁,易于理解
缺点:会检查一些合数(如 4, 6, 8, 9...)


方案二:跳过偶数优化

cpp 复制代码
int ans(int n){
    int count = 0;
    
    // 先处理因子2
    while(n % 2 == 0){
        count++;
        n /= 2;
    }
    
    // 只检查奇数
    for(int i = 3; i * i <= n; i += 2){
        while(n % i == 0){
            count++;
            n /= i;
        }
    }
    
    if(n > 1) count++;
    return count;
}

优化效果

  • 减少约 50% 的循环次数
  • 避免检查所有偶数(除了 2 以外都不是质数)

方案三:质数表预处理(最优)

cpp 复制代码
#include<iostream>
#include<vector>
using namespace std;

const int LIMIT = 32000; // sqrt(10^9) ≈ 31623
vector<int> primes;

// 埃拉托斯特尼筛法生成质数表
void generatePrimes(){
    vector<bool> isPrime(LIMIT + 1, true);
    isPrime[0] = isPrime[1] = false;
    
    for(int i = 2; i <= LIMIT; i++){
        if(isPrime[i]){
            primes.push_back(i);
            for(int j = i * i; j <= LIMIT; j += i)
                isPrime[j] = false;
        }
    }
}

int countPrimeFactors(int n){
    int count = 0;
    
    for(int p : primes){
        if((long long)p * p > n) break;
        
        while(n % p == 0){
            count++;
            n /= p;
        }
    }
    
    if(n > 1) count++;
    return count;
}

int main(){
    generatePrimes(); // 预处理(只执行一次)
    
    int n;
    while(cin >> n){
        cout << countPrimeFactors(n) << endl;
    }
    return 0;
}

优化效果

  • 只用质数试除,避免检查合数
  • 单次查询仅需约 3400 次操作(\\pi(31623) \\approx 3400
  • 多组数据时优势明显

七、推广应用

1. 完整质因数分解

cpp 复制代码
vector<pair<int, int>> factorize(int n){
    vector<pair<int, int>> result;
    
    for(int i = 2; i * i <= n; i++){
        if(n % i == 0){
            int count = 0;
            while(n % i == 0){
                count++;
                n /= i;
            }
            result.push_back({i, count});
        }
    }
    
    if(n > 1) result.push_back({n, 1});
    return result;
}

// 使用示例:factorize(120) → 

应用:计算约数个数、最大公约数、最小公倍数等


2. 判断质数

cpp 复制代码
bool isPrime(int n){
    if(n <= 1) return false;
    if(n == 2) return true;
    if(n % 2 == 0) return false;
    
    for(int i = 3; i * i <= n; i += 2){
        if(n % i == 0) return false;
    }
    return true;
}

3. 计算约数个数

cpp 复制代码
int countDivisors(int n){
    int result = 1;
    
    for(int i = 2; i * i <= n; i++){
        if(n % i == 0){
            int count = 0;
            while(n % i == 0){
                count++;
                n /= i;
            }
            result *= (count + 1); // 约数个数公式
        }
    }
    
    if(n > 1) result *= 2;
    return result;
}

// 示例:countDivisors(12) → 6 (1,2,3,4,6,12)

八、总结

核心要点

  1. 数学性质是关键

    • 质因数 ≤ √N
    • 大于 √N 的质因数最多一个
  2. 算法优化路径

    • 暴力枚举 → 试除到 √N → 跳过偶数 → 质数表预处理
  3. 代码实现要点

    • 使用 i * i <= n 避免浮点运算
    • while 循环除尽所有相同因子
    • 循环后处理剩余大质因数

实际性能对比

方案 N=10\^9 循环次数 适用场景
暴力枚举 ~10\^9 ❌ 不可行
基础试除 ~31623 ✅ 单次查询
跳过偶数 ~15811 ✅ 推荐
质数表 ~3400 ✅✅ 多组数据

最佳实践建议

  • 单次查询:使用跳过偶数的方案
  • 多组数据:使用质数表预处理方案
  • 代码简洁性:基础试除方案已足够
  • 竞赛场景:质数表方案最稳定

这道题完美展示了数学洞察力如何指导算法优化,是理解数论算法的经典入门题目!

相关推荐
大写的z先生1 小时前
【深度学习 | 论文精读】
深度学习·算法·语言模型
新缸中之脑1 小时前
用Claude for Word审查法律合同
开发语言·c#·word
米粒12 小时前
力扣算法刷题Day 49(接雨水)
算法·leetcode·职场和发展
沐知全栈开发2 小时前
SQLite 子查询
开发语言
探物 AI2 小时前
【感知实战·数据增强篇】深度解析目标检测中的图片数据增强算法,多图演示效果
人工智能·算法·目标检测
Codigger官方2 小时前
生态破局:从孤岛工具到协同奇点
开发语言·人工智能·程序人生
莫逸风2 小时前
【java-core-collections】B+ 树深度解析
android·java·开发语言
汪宁宇2 小时前
(C++) Qt5.15.12 + GDAL库 等高线生成示例代码
c++·qt·等高线·gdal·等值线·rec533
gihigo19982 小时前
MATLAB中实现混沌序列的相空间重构
开发语言·matlab·重构