计算给定数的除数的算法

Posted

技术标签:

【中文标题】计算给定数的除数的算法【英文标题】:Algorithm to calculate the number of divisors of a given number 【发布时间】:2010-09-11 17:46:02 【问题描述】:

计算给定数的除数的最佳算法(性能方面)是什么?

如果您能提供伪代码或某个示例的链接,那就太好了。

编辑:所有答案都非常有帮助,谢谢。我正在实施阿特金筛,然后我将使用类似于 Jonathan Leffler 指出的东西。 Justin Bozonier 发布的链接包含我想要的更多信息。

【问题讨论】:

考虑到您提出的因素数量是模糊的。我猜您正在寻找非唯一素数除数的数量,因为如果您不希望我编写代码,只需编写一个程序,如果要因子的数字是 1,则始终返回 1,如果是其他任何东西,则始终返回 2。 0 可能需要更改... @sker:是否存在需要除数的值范围。计算因子的方法有很多种,每种方法都更适合特定的范围。 这是一个相关的有趣问题projecteuler.net/problem=12 即使是编辑过的 Wikipedia 文章中的阿特金天真的筛子也永远不会比埃拉托色尼的最大***分解筛子快到不切实际的极限,页面分段版本甚至更倾向于 SoE (请参阅 Atkin 的合作伙伴 Bernstein 实施的 SoE primesieve 与 SoA primegen。通常不正确的互联网知识是他们的研究证明 SoA 更快,但他们人为地限制了用于证明这一点的 SoE 的优化。请参阅my SoA answer 了解更多信息解释 【参考方案1】:

Dmitriy 是对的,您希望阿特金筛子生成主要列表,但我认为这不能解决整个问题。现在您有了一个素数列表,您需要查看其中有多少个素数充当除数(以及频率)。

Here's some python for the algo Look here 并搜索“主题:数学 - 需要除数算法”。只需计算列表中的项目数,而不是返回它们。

Here's a Dr. Math 解释了你需要在数学上做什么。

基本上归结为如果您的号码n 是:n = a^x * b^y * c^z (其中 a、b 和 c 是 n 的主要除数,x、y 和 z 是除数重复的次数) 那么所有除数的总数为:(x + 1) * (y + 1) * (z + 1)

编辑:顺便说一句,如果我理解正确的话,要找到 a、b、c 等,你会想做相当于贪婪算法的事情。从你的最大素数除数开始,然后将它自己相乘,直到进一步的乘法超过数字 n。然后移动到下一个最低因子并乘以前一个素数 ^ 乘以当前素数的次数,并继续乘以素数,直到下一个将超过 n... 等等。跟踪您乘以的次数除数在一起并将这些数字应用到上面的公式中。

不是 100% 确定我的算法描述,但如果不是这样,它就是类似的东西。

【讨论】:

如果您要分解一个大数,您甚至不想查看 素数列表。您想尽快消除各种可能性!有关更多信息,请参阅我的答案。 我知道这是 2 年前的事了,但是你的 python 算法链接坏了,碰巧知道它现在在哪里? 所以n = (a^x * b^y * c^z)-(x + 1) * (y + 1) * (z + 1) 是规则 正如@Shashank 所说,“编辑:”部分中的算法是错误的:假设 n = 45 = 3*3*5。最大的素数除数是 5,但将其与自身相乘直到超过 n 会导致算法报告它具有因子 5 的 2 个副本(因为 5*5 = 25 “阿特金筛”的运行时复杂度最多为 O(N / log(log(N)))。蛮力检查所有可能的除数 1 ... Sqrt(n) 的运行时复杂度为 O(Sqrt(N)),这要好得多。这个答案怎么被接受了?【参考方案2】:

有比阿特金筛法更多很多的分解技术。例如,假设我们想要分解 5893。那么它的 sqrt 是 76.76... 现在我们将尝试将 5893 写为平方的乘积。那么 (77*77 - 5893) = 36 是 6 的平方,所以 5893 = 77*77 - 6*6 = (77 + 6)(77-6) = 83*71。如果这不起作用,我们会看看 78*78 - 5893 是否是一个完美的正方形。等等。使用这种技术,您可以比测试单个素数更快地测试 n 的平方根附近的因子。如果您将这种排除大素数的技术与筛子结合使用,您将获得比单独使用筛子更好的分解方法。

这只是已开发的大量技术中的一种。这是一个相当简单的。你需要很长时间才能学习足够的数论来理解基于椭圆曲线的因式分解技术。 (我知道它们存在。我不理解它们。)

因此,除非您正在处理小整数,否则我不会尝试自己解决该问题。相反,我会尝试找到一种方法来使用已经实现了高效解决方案的PARI 库之类的东西。有了它,我可以在大约 0.05 秒内分解一个随机的 40 位数字,例如 124321342332143213122323434312213424231341。 (如果您想知道,它的分解是 29*439*1321*157907*284749*33843676813*4857795469949。我非常有信心它没有使用 Atkin 的筛子解决这个问题......)

【讨论】:

你的技术很聪明,但它并没有告诉我这个数字有多少个因素,是吗? 一旦你有了质因数分解,弄清楚有多少因数是很简单的。假设质因子是 p1, p2, ..., pk 并且它们被重复 m1, m2, ..., mk 次。那么有(1+m1)(1+m2)...(1+mk)个因数。 一个有趣的筛子是quadratic sieve。这使用数论 - 二次同余和一些线性代数。我在大学二年级的数论课程中学到了足够的知识。【参考方案3】:

@Yasky

你的除数函数有一个错误,它不能正确地用于完美的正方形。

试试:

int divisors(int x) 
    int limit = x;
    int numberOfDivisors = 0;

    if (x == 1) return 1;

    for (int i = 1; i < limit; ++i) 
        if (x % i == 0) 
            limit = x / i;
            if (limit != i) 
                numberOfDivisors++;
            
            numberOfDivisors++;
        
    

    return numberOfDivisors;

【讨论】:

当 i = 0 时 (x % i) 不会导致除以零吗?我应该 = 1..limit 吗? @rhu 检查 0 无论如何都是没有意义的,因为 0 不是任何数字的因数。【参考方案4】:

我不同意 Atkin 的筛子是要走的路,因为检查 [1,n] 中的每个数字的素数可能比通过除法减少数字要花费更长的时间。

这里有一些代码,虽然稍微有点hack,但通常要快得多:

import operator
# A slightly efficient superset of primes.
def PrimesPlus():
  yield 2
  yield 3
  i = 5
  while True:
    yield i
    if i % 6 == 1:
      i += 2
    i += 2
# Returns a dict d with n = product p ^ d[p]
def GetPrimeDecomp(n):
  d = 
  primes = PrimesPlus()
  for p in primes:
    while n % p == 0:
      n /= p
      d[p] = d.setdefault(p, 0) + 1
    if n == 1:
      return d
def NumberOfDivisors(n):
  d = GetPrimeDecomp(n)
  powers_plus = map(lambda x: x+1, d.values())
  return reduce(operator.mul, powers_plus, 1)

ps这是解决这个问题的python代码。

【讨论】:

【参考方案5】:

这是一个简单的 O(sqrt(n)) 算法。我用这个解决了project euler

def divisors(n):
    count = 2  # accounts for 'n' and '1'
    i = 2
    while i ** 2 < n:
        if n % i == 0:
            count += 2
        i += 1
    if i ** 2 == n:
        count += 1
    return count

【讨论】:

但是为什么你总是将计数增加 2?...你应用了一个定理吗? 因为你只在 sqrt(n) 之前继续。例如:如果您试图找到 36 的所有除数 - 您将从 2 数到 6。您知道 1&36,2&18, 3&12, 4&9, 6,6 都是除数,它们成对出现。 非常感谢安东尼,我现在明白了:D!一个小附录:我认为它应该单独处理 sqrt(n) 值,因为现在它考虑了两次而不是一次,我认为 虽然 O(sqrt(n)) 不算太差,但也不是最优的。计算素因子分解可以更快地完成,并且足以计算除数的数量。 每次迭代都要计算i²,将i和√n比较会不会更快(只计算一次)?【参考方案6】:

这个有趣的问题比看起来要难得多,而且还没有答案。该问题可以分解为 2 个非常不同的问题。

1给定N,求N的质因数列表L

2 给定 L,计算唯一组合数

到目前为止,我看到的所有答案都提到了#1,并且没有提到它对于大量数字来说是难以处理的。对于中等大小的 N,甚至 64 位数字,这很容易;对于巨大的 N,因式分解问题可能需要“永远”。公钥加密依赖于此。

问题 #2 需要更多讨论。如果 L 只包含唯一的数字,则使用组合公式从 n 个项目中选择 k 个对象是一个简单的计算。实际上,您需要将应用公式的结果求和,同时将 k 从 1 变为 sizeof(L)。但是,L 通常会包含多次出现的多个素数。比如L = 2,2,2,3,3,5就是N = 360的因式分解,现在这个问题还挺难的!

重述#2,给定包含 k 个项目的集合 C,例如项目 a 有 a' 个重复项,项目 b 有 b' 个重复项,等等。有多少个 1 到 k-1 个项目的唯一组合?例如,2、2,2、2,2,2、2,3、2,2,3,3 在 L = 2,2 时必须分别出现一次且仅出现一次,2,3,3,5。每个这样的唯一子集合都是 N 的唯一除数,乘以子集合中的项目。

【讨论】:

这里是一些伪代码的链接,用于与 2 非常相似的问题。answers.google.com/answers/threadview/id/392914.html 问题 #2 有一个众所周知的解决方案。对于 p_i, k_i 的因式分解,其中p_i 是具有k_i 重数的数的质因数,该数的除数总数为(k_1+1)*(k_2+1)*...*(k_n+1)。我想你现在已经知道了,但我写下来是为了方便这里的随机读者。【参考方案7】:

您的问题的答案很大程度上取决于整数的大小。小数的方法,例如小于 100 位,对于数字 ~1000 位(例如用于密码学)是完全不同的。

概述:http://en.wikipedia.org/wiki/Divisor_function

n 的值和一些有用的参考:A000005: d(n) (also called tau(n) or sigma_0(n)), the number of divisors of n.

真实世界示例:factorization of integers

【讨论】:

【参考方案8】:

只有一行 我对您的问题进行了非常仔细的考虑,并尝试编写了一段高效且高性能的代码 要在屏幕上打印给定数字的所有除数,我们只需要一行代码! (通过 gcc 编译时使用选项 -std=c99)

for(int i=1,n=9;((!(n%i)) && printf("%d is a divisor of %d\n",i,n)) || i<=(n/2);i++);//n is your number

要查找除数的数量,您可以使用以下非常非常快速的函数(对除 1 和 2 之外的所有整数都正常工作)

int number_of_divisors(int n)

    int counter,i;
    for(counter=0,i=1;(!(n%i) && (counter++)) || i<=(n/2);i++);
    return counter;

或者如果您将给定的数字视为除数(对于除 1 和 2 之外的所有整数都可以正常工作)

int number_of_divisors(int n)

    int counter,i;
    for(counter=0,i=1;(!(n%i) && (counter++)) || i<=(n/2);i++);
    return ++counter;

注意:以上两个函数对除数字 1 和 2 之外的所有正整数都正常工作 所以它适用于所有大于 2 的数字 但是如果你需要覆盖 1 和 2 ,你可以使用以下函数之一(慢一点)

int number_of_divisors(int n)

    int counter,i;
    for(counter=0,i=1;(!(n%i) && (counter++)) || i<=(n/2);i++);
    if (n==2 || n==1)
    
    return counter;
    
    return ++counter;

int number_of_divisors(int n)

    int counter,i;
for(counter=0,i=1;(!(i==n) && !(n%i) && (counter++)) || i<=(n/2);i++);
    return ++counter;

小就是美:)

【讨论】:

【参考方案9】:

阿特金筛子是埃拉托色尼筛子的优化版本,它给出了所有素数,直到给定整数。你应该可以用谷歌搜索更多细节。

一旦你有了这个列表,就很容易将你的数字除以每个素数,看看它是否是一个精确的除数(即余数为零)。

计算一个数(n)的除数的基本步骤是[这是从真实代码转换而来的伪代码,所以我希望我没有引入错误]:

for z in 1..n:
    prime[z] = false
prime[2] = true;
prime[3] = true;

for x in 1..sqrt(n):
    xx = x * x

    for y in 1..sqrt(n):
        yy = y * y

        z = 4*xx+yy
        if (z <= n) and ((z mod 12 == 1) or (z mod 12 == 5)):
            prime[z] = not prime[z]

        z = z-xx
        if (z <= n) and (z mod 12 == 7):
            prime[z] = not prime[z]

        z = z-yy-yy
        if (z <= n) and (x > y) and (z mod 12 == 11):
            prime[z] = not prime[z]

for z in 5..sqrt(n):
    if prime[z]:
        zz = z*z
        x = zz
        while x <= limit:
            prime[x] = false
            x = x + zz

for z in 2,3,5..n:
    if prime[z]:
        if n modulo z == 0 then print z

【讨论】:

【参考方案10】:

你可以试试这个。这有点骇人听闻,但速度相当快。

def factors(n):
    for x in xrange(2,n):
        if n%x == 0:
            return (x,) + factors(n/x)
    return (n,1)

【讨论】:

虽然此函数在合理的时间内提供了 n 的素因子分解,但它 a) 不是最优的,并且 b) 没有根据 OP 的问题计算给定数字的除数数 并且由于它的递归性而不适用于大数字 虽然这不是最优的,而不是计算因素,它实际上列出它们,它的简单和美丽是惊人的并且是合理的快速地。 ^^【参考方案11】:

一旦你有了质因数分解,就有一种方法可以找到除数的数量。将每个单独因子的每个指数加一,然后将这些指数相乘。

例如: 36 素数分解:2^2*3^2 除数:1、2、3、4、6、9、12、18、36 除数数:9

每个指数加一 2^3*3^3 乘指数:3*3 = 9

【讨论】:

【参考方案12】:

在您提交解决方案之前,请考虑 Sieve 方法在典型情况下可能不是一个好的答案。

不久前有一个素数问题,我做了一个时间测试——对于 32 位整数,至少确定它是否是素数比蛮力慢。有两个因素在发生:

1) 虽然人类需要一段时间来进行除法,但他们在计算机上的速度非常快——类似于查找答案的成本。

2) 如果您没有素数表,您可以创建一个完全在 L1 缓存中运行的循环。这使它更快。

【讨论】:

【参考方案13】:

这是一个有效的解决方案:

#include <iostream>
int main() 
  int num = 20; 
  int numberOfDivisors = 1;

  for (int i = 2; i <= num; i++)
  
    int exponent = 0;
    while (num % i == 0) 
        exponent++; 
        num /= i;
       
    numberOfDivisors *= (exponent+1);
  

  std::cout << numberOfDivisors << std::endl;
  return 0;

【讨论】:

【参考方案14】:

除数做了一些惊人的事情:它们完全除数。如果你想检查一个数字的除数,n,显然跨越整个频谱是多余的,1...n。我没有对此进行任何深入研究,但我解决了Project Euler's problem 12 on Triangular Numbers。我对大于 500 个除数测试的解决方案运行了 309504 微秒(~0.3 秒)。我为解决方案编写了这个除数函数。

int divisors (int x) 
    int limit = x;
    int numberOfDivisors = 1;

    for (int i(0); i < limit; ++i) 
        if (x % i == 0) 
            limit = x / i;
            numberOfDivisors++;
        
    

    return numberOfDivisors * 2;

每个算法都有一个弱点。我认为这对素数来说很弱。但由于三角数没有打印出来,它完美地发挥了它的作用。从我的分析来看,我认为它做得很好。

节日快乐。

【讨论】:

在第一次迭代中你会被除以 0 很遗憾没有。 ++i 与 i++ 不同(这会导致被零除错误) 我用 php 编写了你的​​函数并运行了它 - 这就是我得到的 - i.minus.com/iKzuSXesAkpbp.png 出于某种奇怪的原因,这对我来说完美无缺。哦,好吧,我的坏。开始numberOfDivisors,迭代器从1开始;这应该消除除以零错误 您的算法不适用于完美的正方形。例如,对于输入 x = 4,它返回 4,因为它两次计数 2...1、2、2、4。答案应该是 3:1、2、4【参考方案15】:

您想要阿特金筛子,此处描述:http://en.wikipedia.org/wiki/Sieve_of_Atkin

【讨论】:

这会让你得到低于给定数字的素数 - 但不能保证这些素数会是除数? (除非我遗漏了什么) 这是一个快速的飞跃,从这里找到所有能整除 N 的素数 这可能是一个快速的飞跃,但测试所有素数 测试素数是 O(N),找到素数是困难的部分。但即使使用未经优化的埃拉托色尼筛,您仍然可以在一秒钟内找到数百万以下的所有素数。这涵盖了任何 64b 数字,我敢肯定我们不是在这里谈论考虑加密级别的东西【参考方案16】:

数论教科书将除数计数函数称为 tau。第一个有趣的事实是它是乘法的,即。 τ(ab) = τ(a)τ(b) ,当 a 和 b 没有公因数时。 (证明:a 和 b 的每一对除数给出 ab 的一个不同除数)。

现在请注意,对于 p 是素数,τ(p**k) = k+1(p 的幂)。因此,您可以通过分解轻松计算 τ(n)。

但是,分解大数可能会很慢(RSA 加密的安全性取决于两个大素数的乘积很难分解)。这表明这种优化算法

    Test if the number is prime (fast) 如果是,返回 2 否则,factorise the number(如果有多个大素因数,则速度较慢) 从因式分解计算 τ(n)

【讨论】:

【参考方案17】:

这是计算除数的最基本方法:

class PrintDivisors

    public static void main(String args[])
    

    System.out.println("Enter the number");

    // Create Scanner object for taking input
    Scanner s=new Scanner(System.in);

    // Read an int
    int n=s.nextInt();

        // Loop from 1 to 'n'
        for(int i=1;i<=n;i++)
        

            // If remainder is 0 when 'n' is divided by 'i',
            if(n%i==0)
            
            System.out.print(i+", ");
            
        

    // Print [not necessary]    
    System.out.print("are divisors of "+n);

    

【讨论】:

【参考方案18】:

素数法在这里很清楚。 P[] 是一个小于或等于 sq = sqrt(n) 的素数列表;

for (int i = 0 ; i < size && P[i]<=sq ; i++)
          nd = 1;
          while(n%P[i]==0)
               n/=P[i];
               nd++;
               
          count*=nd;
          if (n==1)break;
          
      if (n!=1)count*=2;//the confusing line :D :P .

     i will lift the understanding for the reader  .
     i now look forward to a method more optimized  .

【讨论】:

【参考方案19】:

以下是一个 C 程序,用于查找给定数的除数。

上述算法的复杂度为O(sqrt(n))。

这个算法对于完美平方数和非完美平方数都可以正常工作。

请注意,循环的上限设置为数字的平方根,以使算法最有效。

注意,将上限存储在单独的变量中也可以节省时间,您不应该在 for 循环的条件部分调用 sqrt 函数,这也可以节省您的计算时间。

#include<stdio.h>
#include<math.h>
int main()

    int i,n,limit,numberOfDivisors=1;
    printf("Enter the number : ");
    scanf("%d",&n);
    limit=(int)sqrt((double)n);
    for(i=2;i<=limit;i++)
        if(n%i==0)
        
            if(i!=n/i)
                numberOfDivisors+=2;
            else
                numberOfDivisors++;
        
    printf("%d\n",numberOfDivisors);
    return 0;

除了上面的 for 循环之外,您还可以使用下面的循环,它甚至更有效,因为这消除了查找数字平方根的需要。

for(i=2;i*i<=n;i++)

    ...

【讨论】:

【参考方案20】:

这是我写的一个函数。最差的时间复杂度是 O(sqrt(n)),而最好的时间复杂度是 O(log(n))。它为您提供所有主要除数及其出现次数。

public static List<Integer> divisors(n)    
    ArrayList<Integer> aList = new ArrayList();
    int top_count = (int) Math.round(Math.sqrt(n));
    int new_n = n;

    for (int i = 2; i <= top_count; i++) 
        if (new_n == (new_n / i) * i) 
            aList.add(i);
            new_n = new_n / i;
            top_count = (int) Math.round(Math.sqrt(new_n));
            i = 1;
        
    
    aList.add(new_n);
    return aList;

【讨论】:

我不知道这个函数计算什么,但肯定不是n的除数列表。【参考方案21】:

@肯德尔

我测试了您的代码并进行了一些改进,现在它更快了。 我还用@هومن جاویدپور代码进行了测试,这也比他的代码快。

long long int FindDivisors(long long int n) 
  long long int count = 0;
  long long int i, m = (long long int)sqrt(n);
  for(i = 1;i <= m;i++) 
    if(n % i == 0)
      count += 2;
  
  if(n / m == m && n % m == 0)
    count--;
  return count;

【讨论】:

【参考方案22】:

这不只是一个因数分解的问题——确定数的所有因数吗?然后,您可以决定是否需要一个或多个因素的所有组合。

因此,一种可能的算法是:

factor(N)
    divisor = first_prime
    list_of_factors =  1 
    while (N > 1)
        while (N % divisor == 0)
            add divisor to list_of_factors
            N /= divisor
        divisor = next_prime
    return list_of_factors

然后由您来组合这些因素来确定其余的答案。

【讨论】:

【参考方案23】:

我认为这就是你要找的。我完全按照你的要求去做。 将其复制并粘贴到记事本中。另存为 *.bat.Run.Enter Number。将过程乘以 2,这就是除数的数量。我是故意这样做的,这样它可以更快地确定除数:

请注意,CMD 变量不能支持超过 999999999 的值

@echo off

modecon:cols=100 lines=100

:start
title Enter the Number to Determine 
cls
echo Determine a number as a product of 2 numbers
echo.
echo Ex1 : C = A * B
echo Ex2 : 8 = 4 * 2
echo.
echo Max Number length is 9
echo.
echo If there is only 1 proces done  it
echo means the number is a prime number
echo.
echo Prime numbers take time to determine
echo Number not prime are determined fast
echo.

set /p number=Enter Number : 
if %number% GTR 999999999 goto start

echo.
set proces=0
set mindet=0
set procent=0
set B=%Number%

:Determining

set /a mindet=%mindet%+1

if %mindet% GTR %B% goto Results

set /a solution=%number% %%% %mindet%

if %solution% NEQ 0 goto Determining
if %solution% EQU 0 set /a proces=%proces%+1

set /a B=%number% / %mindet%

set /a procent=%mindet%*100/%B%

if %procent% EQU 100 set procent=%procent:~0,3%
if %procent% LSS 100 set procent=%procent:~0,2%
if %procent% LSS 10 set procent=%procent:~0,1%

title Progress : %procent% %%%



if %solution% EQU 0 echo %proces%. %mindet% * %B% = %number%
goto Determining

:Results

title %proces% Results Found
echo.
@pause
goto start

【讨论】:

882161280 - 1282 除数【参考方案24】:

我想这个会很方便也很精确

script.pyton

>>>factors=[ x for x in range (1,n+1) if n%x==0] print len(factors)

【讨论】:

【参考方案25】:

尝试以下方法:

int divisors(int myNum) 
    int limit = myNum;
    int divisorCount = 0;
    if (x == 1) 
        return 1;
    for (int i = 1; i < limit; ++i) 
        if (myNum % i == 0) 
            limit = myNum / i;
            if (limit != i)
                divisorCount++;
            divisorCount++;
        
    
    return divisorCount;

【讨论】:

【参考方案26】:

我不知道最有效的方法,但我会这样做:

创建一个素数表以查找所有小于或等于数字平方根的素数(我个人会使用阿特金筛) 计算所有小于或等于该数平方根的素数并将其乘以 2。如果数字的平方根是整数,则从 count 变量中减一。

应该可以工作\o/

如果你需要,我明天可以用 C 编写一些代码来演示。

【讨论】:

我很困惑。计算所有小于一个数的平方根的素数不会给你它的除数...不是每个小于一个数的平方根的素数都会是那个数的除数。

以上是关于计算给定数的除数的算法的主要内容,如果未能解决你的问题,请参考以下文章

直到啥数字可以在 5 秒内找到给定数字的除数 [关闭]

计算交叉盘数的算法

计算具有相同汉明权重的二进制数的所有排列的最快算法是啥?

求最大公约数伪代码

截止目前为止,我遇到的最难的一道算法题:计算相邻两个数的最大差值

截止目前为止,我遇到的最难的一道算法题:计算相邻两个数的最大差值