查找整数的所有因子的有效算法是什么?

时间:2016-02-17 22:55:48

标签: c algorithm math primes square-root

我正在编写一个非常简单的程序来检查一个数字是否可以均匀地划分另一个数字:

// use the divider squared to reduce iterations
for(divider = 2; (divider * divider) <= number; divider++)
    if(number % divider == 0)
        print("%d can divided by %d\n", number, divider);

现在我很好奇是否可以通过找到数字的平方根并将其与分频器进行比较来完成任务。但是,似乎sqrt()并不能真正提高效率。如何在C中处理sqrt()以及如何提高sqrt()的效率?此外,还有其他方法可以更高效地解决问题吗?

另外,

number % divider == 0

用于测试分隔符是否可以均匀划分数字,除了使用%之外还有更有效的方法进行测试吗?

4 个答案:

答案 0 :(得分:3)

  

然而,似乎sqrt()并不能真正提高效率

这是可以预料到的,因为每次迭代保存的乘法很大程度上取决于循环内慢得多的除法运算。

  

此外,number % divider = 0用于测试分频器是否可以均匀划分数字,除了使用%之外还有更有效的方法进行测试吗?

不是我知道的。检查a % b == 0是否至少与检查某些c的a % b = c一样困难,因为我们可以使用前者计算后者(添加一个额外的)。至少在英特尔架构上,计算后者的计算成本与分区一样昂贵,这是典型的现代处理器中最慢的操作。

如果您希望显着更好的性能,则需要更好的分解算法,其中there are plenty。运行时O(n 1/4 )的一个特别简单的是Pollard's ρ algorithm。您可以找到简单的C ++实现in my algorithms library。对C的改编留给读者练习:

int rho(int n) { // will find a factor < n, but not necessarily prime
  if (~n & 1) return 2;
  int c = rand() % n, x = rand() % n, y = x, d = 1;
  while (d == 1) {
    x = (1ll*x*x % n + c) % n;
    y = (1ll*y*y % n + c) % n;
    y = (1ll*y*y % n + c) % n;
    d = __gcd(abs(x - y), n);
  }
  return d == n ? rho(n) : d;
}

void factor(int n, map<int, int>& facts) {
  if (n == 1) return;
  if (rabin(n)) { // simple randomized prime test (e.g. Miller–Rabin)
    // we found a prime factor
    facts[n]++;
    return;
  }
  int f = rho(n);
  factor(n/f, facts);
  factor(f, facts);
}

从其主要因素构建n因子是一项容易的任务。只需将所有可能的指数用于找到的素数因子,并以各种可能的方式将它们组合起来。

答案 1 :(得分:3)

我不打算解决查找整数所有因子的最佳算法。相反,我想评论你当前的方法。

有条件测试案例需要考虑

  1. (divider * divider) <= number
  2. divider <= number/divider
  3. divider <= sqrt(number)
  4. 有关更多详情,请参阅Conditional tests in primality by trial division

    使用的情况取决于您的目标和硬件。

    案例1的优点是它不需要分割。但是,当divider*divider大于最大整数时,它可能会溢出。案例2没有溢出问题,但需要划分。对于case3,sqrt只需要计算一次,但它要求sqrt函数得到正确的正方形。

    但是还有其他东西需要考虑许多指令集,包括x86指令集,在进行除法时也会返回余数。由于您已经在执行number % divider,这意味着您在执行number / divider时可以免费获得此内容。

    因此,情况1仅适用于在一条指令中不计算除法和余数的系统,并且您不担心溢出。

    在案例2和案例3之间我认为主要问题是指令集。如果sqrt与case2相比太慢,或者sqrt函数无法正确计算完美正方形,请选择案例2。如果指令集没有计算一条指令中的除数和余数,则选择情况3。

    对于x86指令集案例1,案例2和案例3应该提供基本相同的性能。所以应该没有理由使用案例1(但是请看下面的一个细微点)。 C标准库保证正确完成sqrt完美正方形。所以对案例3也没有不利之处。

    但是关于案例2有一个微妙的观点。我发现有些编译器不会认识到除法和余数是一起计算的。例如,在以下代码中

    for(divider = 2; divider <= number/divider; divider++)
        if(number % divider == 0)
    

    GCC生成两个除法指令,即使只需要一个。解决这个问题的一种方法是保持分区和提醒关闭,就像这样

    divider = 2, q = number/divider, r = number%divider
    for(; divider <= q; divider++, q = number/divider, r = number%divider)
        if(r == 0)
    

    在这种情况下,GCC只生成一个除法指令,case1,case 2和case 3具有相同的性能。但是这段代码比

    的可读性差一点
    int cut = sqrt(number);
    for(divider = 2; divider <= cut; divider++)
        if(number % divider == 0)
    

    所以我认为总体情况3是至少使用x86指令集的最佳选择。

答案 2 :(得分:2)

在C中,您可以使用标题sqrt()中的<math.h>函数族来获取浮点数的平方根。

取平方根通常比分割慢,因为取平方根的算法比除法算法更复杂。这不是C语言的属性,而是执行程序的硬件属性。在现代处理器上,取平方根可以和分割一样快。例如,这适用于Haswell微体系结构。

但是,如果算法改进良好,sqrt()调用的速度稍慢通常无关紧要。

要仅比较number的平方根,请使用以下代码:

#include <math.h>

/* ... */

int root = (int)sqrt((double)number);
for(divider = 2; divider <= root; divider++)
    if(number % divider = 0)
        print("%d can divided by %d\n", number, divider);

答案 3 :(得分:0)

这只是我随意的想法,所以如果它错了,请评论并批评它。

这个想法是预先计算低于特定范围的所有素数并将其用作表格。

通过表循环,检查素数是否是一个因子,如果是,则递增该素数的计数器,如果不是则增加索引。当索引到达结尾或要检查的素数超过输入时终止。

最后,结果是输入的所有主要因素及其计数的表格。那么产生所有自然因素应该是微不足道的,不是吗?

最坏的情况是,循环需要结束,然后需要6542次迭代。

考虑到输入是[0,4294967296],这类似于O(n^3/8)

这是实现此方法的MATLAB代码:

如果p生成p=primes(65536);,则此方法适用于[0, 4294967296]之间的所有输入(但未经过测试)。

function [ output_non_zero ] = fact2(input, p)
    output_table=zeros(size(p));
    i=1;
    while(i<length(p));
        if(input<1.5)
            break; 
            % break condition: input is divided to 1,
            % all prime factors are found. 
        end
        if(rem(input,p(i))<1)
            % if dividable, increament counter and don't increament index
            % keep checking until not dividable
            output_table(i)=output_table(i)+1;
            input = input/p(i);
        else
            % not dividable, try next
            i=i+1;
        end
    end

    % remove all zeros, should be handled more efficiently
     output_non_zero = [p(output_table~=0);...
                        output_table(output_table~=0)];
     if(input > 1.5)
         % the last and largest prime factor could be larger than 65536
         % hence would skip from the table, add it to the end of output
         % if exists
         output_non_zero = [output_non_zero,[input;1]];
     end
end

<强>测试

p=primes(65536);
t = floor(rand()*4294967296);
b = fact2(t, p);
% check if all prime factors adds up and they are all primes
assert((prod(b(1,:).^b(2,:))==t)&&all(isprime(b(1,:))), 'test failed');
相关问题