分解质因数

2023年01月04日 1556点热度 0人点赞 0条评论

今天空闲,忽然想到一个挺有意思的小学学过的内容,分解质因数!
每个合数都可以写成几个质数相乘的形式,其中每个质数都是这个合数的因数,把一个合数用质因数相乘的形式表示出来,叫做分解质因数。
看起来很容易的样子,但怎么用程序计算出来呢?今天来研究下算法。

比如要分解一个很大的数的质因数,比如 PHP_INT_MAX,应该怎么做呢?
算法一:获取 PHP_INT_MAX 内所有的质数,循环一下,挑出质数放入数组 $primes。
算法二:用递归方法,从小到大循环 $primes 内所有数,看是否能被 PHP_INT_MAX 整除,如能整除则将些数放入数组 $factor,再递归调用自己至到不能被整除后计算下一个数是否能被整除,直到循环结束。

说起来很简单,先来研究获取质数算法。
在网上看到个思路:

function get_prime($max_num){
    $max_num = floor($max_num);
    $prime = array();
    for($x = 1; $x <= $max_num; $x++){
        $flag = true; // 当flag为false时表示该数不是素数
        for($y = 2; $y < $max_num; $y++){ //$y从2开始,因为除数为1时,肯定能整除
            if($x > $y){ // $y如果比$x还大,取模肯定不为0,没有比较的意义
                if($x % $y == 0 ){ //当除数$y为$x以内时,如果取模为0,表示该数不是素数
                    $flag = false;
                }
            }
        }
        if($flag) $prime[] = $x; // 如果$flag为真,则$x是质数
    }
    array_shift($prime); // 把1从质数数组中剔除
    return $prime;
}

双层循环,循环次数根据 $max_num 变大而成指数上升,效率非常低!

后来想到可以用试除法判定质数,如下:

function is_prime1($n) {
    if($n < 2) return false;
    for($i = 2; $i < $n; $i++){
        if($n % $i == 0) return false;
    }
    return true;
}

但是,也可以看出但是了,时间复杂度是O(N),很容易看出若n是一个很大的数会导致超时!

对上面代码进行了下改进:

由于判定n是否为质数。根据质数的性质,若在2~sqrt(n)中没有可以把它整除的,那么在sqrt(n)~n-1中也没有可以把n整除的,因此可以改进为以下三种写法,此时时间复杂度为O():

function is_prime2($n){
    if ($n < 2) return false;
    for ($i = 2; $i * $i <= $n; $i++) {
        if ($n % $i == 0) return false;
    }
    return true;
}
function is_prime3($n){
    if($n < 2) return false;
    $t = sqrt($n);
    for($i = 2; $i <= $t; $i++){
        if($n % $i == 0) return false;
    }
    return true;
}
function is_prime4($n){
    if($n < 2) return false;
    for($i = 2; $i <= $n / $i; $i++){
        if($n % $i == 0) return false;
    }
    return true;
}

经过分析提倡is_prime4()的写法,因为is_prime2()的写法在判断 i * i ≤ n 时,i * i可能大于整型范围从而导致溢出,进而影响结果。不推荐使用is_prime3()是因为在调用sqrt(n)函数时可能会导致超时,因此综合分析is_prime4()是最合适的。

算法二:很简单的,不用解释太多

function resolving(&$factor, $num, $primes, $i=0){
    if(!is_int($num) || $num < 0) return;
    if(in_array($num, $primes)){
        // 如果该数为质数
        $factor[] = $num;
    } else {
        $ceil = ceil($num / $primes[$i]);
        if($ceil == ($num / $primes[$i])){
            $factor[] = $primes[$i];
            // 如果当前质数还能被$ceil整除,则继续用该质数(不用$i++)
            // 比如90分解为2、3、3、5,否则让$i++再递归
            if($ceil % $primes[$i]!=0) $i++;
            resolving($factor, intval($ceil), $primes, $i);
        } else {
            // 对于99这样的,不是质数,但也没第一次被整除的,用下一个质数($i++)测试它
            resolving($factor, $num, $primes, $i+1);
        }
    }
}

到目前为止,组装所有零件就能运行啦,效率还行!

完整代码如下:

<?php
set_time_limit(300);
function is_prime4($n){
    if($n < 2) return false;
    for($i = 2; $i <= $n / $i; $i++){
        if($n % $i == 0) return false;
    }
    return true;
}
function resolving(&$factor, $num, $primes, $i=0){
    if(!is_int($num) || $num < 0) return;
    if(in_array($num, $primes)){
        // 如果该数为质数
        $factor[] = $num;
    } else {
        $ceil = ceil($num / $primes[$i]);
        if($ceil == ($num / $primes[$i])){
            $factor[] = $primes[$i];
            // 如果当前质数还能被$ceil整除,则继续用该质数(不用$i++)
            // 比如90分解为2、3、3、5,否则让$i++再递归
            if($ceil % $primes[$i]!=0) $i++;
            resolving($factor, intval($ceil), $primes, $i);
        } else {
            // 对于99这样的,不是质数,但也没第一次被整除的,用下一个质数($i++)测试它
            resolving($factor, $num, $primes, $i+1);
        }
    }
}

// 计算目标数,如果你电脑够强的话可以试试 PHP_INT_MAX
$target = 100005;
$primes = array();
for($i = 0; $i <= $target; $i++) {
    if(is_prime4($i)) $primes[] = $i;
}

$factor = array();
resolving($factor, $target, $primes);
print_r($factor);
?>

同时做了一个JS版的,有兴趣的同学可以看看:《在线分解质因数

路灯

这个人很懒,什么都没留下

文章评论