计算给定数除数的算法


177

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

如果您可以提供伪代码或一些示例的链接,那就太好了。

编辑:所有的答案都非常有帮助,谢谢。我正在实现Atkin筛分法,然后将使用类似于Jonathan Leffler所指出的方法。贾斯汀·博佐尼尔(Justin Bozonier)发布的链接提供了有关我想要的更多信息。


鉴于您提出的要求,因素的数量很模糊。我猜您正在寻找非唯一素数的数量,因为如果您不希望我编写代码,则只需编写一个程序,以在要分解的数为1时始终返回1,否则就返回2。0可能需要更改...
Justin Bozonier

@sker:是否存在需要除数的一系列值。有多种计算因子的方法,每种方法都更适合特定范围。
安德·特纳

2
这是一个相关的有趣问题projecteuler.net/problem=12
daniloquio 2012年

1
甚至来自编辑过的Wikipedia文章的朴素的Atkin筛网也永远不会比最大轮式分解的Eratosthenes筛网快到巨大的不切实际的极限,而且页面分段版本甚至更支持SoE(请参阅SoE primesieve与SoA primegen作为常见的错误互联网知识是,他们的研究证明了SoA更快,但他们人为地限制了所用SoE的优化,以证明这一点。请参阅我的SoA答案以获取进一步的解释
GordonBGood 2015年

Answers:


78

德米特里(Dmitriy)是对的,您希望让《阿特金筛子》(Sieve of Atkin)生成主要清单,但我认为这不能解决整个问题。现在您已经有了素数的列表,您将需要查看其中有多少素数可以用作除数(以及除数)。

这是用于算法的一些python。在 这里搜索“主题:数学-需要除数算法”。只是计算列表中的项目数,而不是返回它们。

这是一位数学博士,解释了您需要做的确切的数学运算

本质上可以归结为您的数字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%知道我的算法描述,但是如果不是,那是类似的事情。


1
如果要考虑大量因素,甚至不需要查看主要清单。您想尽快消除所有可能性!看到我的答案更多。
user11318

我知道这是2年前,但是您的python算法链接已损坏,碰巧知道它现在存在于何处?
jb。

2
n = (a^x * b^y * c^z)-(x + 1) * (y + 1) * (z + 1)规则也是如此
-SIslam

1
正如@Shashank所说,“ EDIT:”部分中的算法是错误的:假设n = 45 = 3 * 3 * 5。最大除数为5,但是将其自身乘以直到超过n,将导致算法报告它具有2个因数5的副本(因为5 * 5 = 25 <45)。
j_random_hacker

1
“ Atkin筛子”的运行时复杂度最多为O(N / log(log(N)))。从1 ... Sqrt(n)进行蛮力检查所有可能的除数,其运行时复杂度为O(Sqrt(N)),这要好得多。为什么这个答案被接受了?
le_m

47

这里有很多更多的技术,以保比·阿特金的筛。例如,假设我们要分解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位数字,例如124321342332143143213122323434312213424231341。(如果您想知道,它的因式分解为29 * 439 * 1321 * 157907 * 284749 * 33843676813 * 4857795469949。我非常有信心它没有使用Atkin的筛子解决这个问题...)


1
您的技术非常聪明,但是并不能告诉我这个数字有多少因素,对吗?
sker

23
一旦有了素因数分解,就可以很容易地找出有多少个因数。假设素因子为p1,p2,...,pk,并且重复了m1,m2,...,mk次。然后有(1 + m1)(1 + m2)...(1 + mk)个因子。
user11318

一个有趣的筛子是二次筛子。这使用了数论-二次同余和一些线性代数。我在大学的第二年数论课程中学到了足够的知识,可以使用它。
Tanner 2012年

33

@雅斯基

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

尝试:

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;
}

6
当i = 0时(x%i)是否会导致被零除?我应该= 1..limit吗?
rhu 2012年

@rhu反正检查0毫无意义,因为0不是任何数字的因数。
EJoshuaS-恢复莫妮卡

29

我不同意Atkin的筛分方法,因为检查[1,n]中的每个数字是否为素数比将其除以除法要容易得多。

这里的一些代码虽然有些骇客,但通常速度要快得多:

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代码来解决此问题。


11

这是一个简单的O(sqrt(n))算法。我用它来解决项目欧拉

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?...您应用了一个定理吗?
SummerCode 2015年

3
因为您只在sqrt(n)之前才有条件。例如:如果您试图找到36的所有除数-您将计算2到6。您知道1&36,2&18、3&12、4&9、6,6都是除数,它们成对出现。
安东尼·托马斯

2
非常感谢安东尼,我现在明白了:D!一个小的附录:我认为应该将sqrt(n)值分开处理,因为现在我考虑将它代替两次,而不是一次,我想
SummerCode 2015年

虽然O(sqrt(n))不太差,但不是最佳的。计算素因数分解可以更快地完成,并且足以计算除数的数量。
le_m

在每次迭代中,您都必须计算i²,将i与√n(仅计算一次)进行比较会更快吗?
Yukulélé

10

这个有趣的问题比看起来要难得多,并且尚未得到回答。该问题可以分解为两个非常不同的问题。

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的因式分解。现在这个问题相当困难!

在给定集合C包含k个项目的情况下重述#2,使得项目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
mR_fr0g

3
问题2有一个众所周知的解决方案。对于{p_i,k_i}的因式分解,其中p_i是一个具有k_i多重性的数的质因子,该数的除数的总数为(k_1+1)*(k_2+1)*...*(k_n+1)。我想您现在已经知道了,但是如果在这里随机阅读,我为了方便起见将其写下来。
尼斯将于2012年


6

只需一行,
我就对您的问题非常谨慎,并且我尝试编写了一段高效且高性能的代码。要在屏幕上打印给定数字的所有除数,我们只需要一行代码!(通过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;
}

小就是美丽:)


5

阿特金(Atkin)筛是Eratosthenes筛的优化版本,它给出所有质数直至给定的整数。您应该可以在Google上搜索更多详细信息。

一旦有了该列表,将数字除以每个质数就很简单了,以查看它是否是一个精确的除数(即,余数为零)。

计算一个数(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

5

您可以尝试这个。有点破烂,但是相当快。

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

2
尽管此函数在合理的时间内提供了n的素因子分解,但它不是a)最优的,而且b)并未根据OP的问题计算给定数量的除数的数量
le_m

而且由于其递归而不能用于大数字
whackamadoodle3000 '18

虽然这不是最佳的,而不是计数的因素,它实际上列表他们,这样做的简单性和美丽是惊人的,是相当快的。^^
Gaurav Singhal

5

一旦有了素因数分解,便可以找到除数的数量。将每个因子上的每个指数加一个,然后将指数相乘。

例如:36素因数分解:2 ^ 2 * 3 ^ 2除数:1,2,3,4,6,9,9,12,18,36除数的数量:9

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


3

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

前一段时间有一个素数问题,我进行了时间测试-对于32位整数,至少确定它是否素数比强力慢。发生两个因素:

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

2)如果没有主表,则可以创建一个完全在L1缓存中运行的循环。这使其速度更快。


3

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

#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;
}

2

除数做得很壮观:它们完全分开。如果要检查某个数字的除数的数量,n显然跨越整个频谱是多余的1...n。我对此没有做任何深入的研究,但是我解决了三角数上的欧拉计划问题12。我针对大于500的除数测试的解决方案运行了309504微秒(〜0.3s)。我为解决方案编写了这个除数函数。

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;
}

对于每种算法,都有一个弱点。我认为这对素数来说是微不足道的。但是由于三角数字没有打印出来,因此可以完美地实现其目的。从我的分析来看,我认为它做得很好。

节日快乐。


1
你必须在第一次迭代0除这里
barfoon

不幸的是没有。++ i与i ++不同(这将导致被零除的错误)
iGbanam 2011年

我用PHP编写了函数并运行了它-这就是我得到的-i.minus.com/iKzuSXesAkpbp.png
barfoon 2011年

出于某种奇怪的原因,这对我来说是完美的。哦,我的坏。启动numberOfDivisors并在1处进行迭代;这应该摆脱零误差除法
iGbanam 2011年

1
您的算法不适用于完美的正方形。例如,对于输入x = 4,它返回4,因为它计数2两次... 1、2、2、4。答案应为3、1、2、4
迈克尔(Michael)

1

您需要此处描述的Atkin筛子:http : //en.wikipedia.org/wiki/Sieve_of_Atkin


1
这将使您的质数低于给定数字-但不能保证这些质数会被除数吗?(除非我丢失了某些内容)
Andrew Edgecombe

从这里到找到所有素数<sqrt(N)均分N的数都是一个快速的飞跃
。– SquareCog

1
这可能是一个快速的飞跃,但是测试所有≤sqrt(N)的素数仍然是一个不好的分解技术,无论您发现它们的效率如何。有很多方法可以改善这一点。
user11318

测试素数为O(N),它发现素数是最难的部分。但是,即使使用未经优化的erathesthenes筛子,您仍然可以在一秒钟内找到几百万个以下的所有素数。这涵盖了任何64b的数字,我敢肯定我们在这里不打算讨论加密级别的问题
Matthew Scharley's

1

素数法在这里很清楚。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  .

1

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

现在注意,对于pa素数,τ(p ** k)= k + 1(p的幂)。因此,您可以从其分解中轻松计算出τ(n)。

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

  1. 测试数字是否为质数(快速)
  2. 如果是,则返回2
  3. 否则,分解数(如果有多个大素,则慢)
  4. 根据分解计算τ(n)

1

以下是一个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++)
{
    ...
}

1

这是我编写的函数。它的最差时间复杂度是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除数的列表。
le_m

1

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

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);

    }
}

1

@肯德尔

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

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;
}

0

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

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

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

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


0

这是我根据贾斯汀的答案提出的。它可能需要一些优化。

n=int(input())

a=[]
b=[]

def sieve(n):
    np = n + 1
    s = list(range(np)) 
    s[1] = 0
    sqrtn = int(n**0.5)
    for i in range(2, sqrtn + 1): 
        if s[i]:
            s[i*i: np: i] = [0] * len(range(i*i, np, i))
    return filter(None, s)

k=list(sieve(n))

for i in range(len(k)):
        if n%k[i]==0:
                a.append(k[i])

a.sort()

for i in range(len(a)):
        j=1
        while n%(a[i]**j)==0: 
                j=j+1
        b.append(j-1)

nod=1

for i in range(len(b)):
        nod=nod*(b[i]+1)

print('no.of divisors of {} = {}'.format(n,nod))

0

我认为这就是您要寻找的东西。将其复制并粘贴到记事本中,另存为* .bat.Run。输入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除数
Dondon

0

我想这将既方便又精确

script.pyton

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


0

尝试以下方法:

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;
}

-1

我不知道MOST有效的方法,但我会执行以下操作:

  • 创建一个素数表以查找所有小于或等于数字平方根的素数(就我个人而言,我将使用Atkin筛分法)
  • 计算所有小于或等于数字平方根的素数,然后将其乘以2。如果数字的平方根是整数,则从计数变量中减去1。

应该工作\ o /

如果需要,我明天可以在C语言中编写代码进行演示。


2
我很困惑。计算所有小于数字平方根的质数不会得到除数...并非每个小于数字平方根的质数都将是该数字的除数。
Garrett Berg
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.