182

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

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

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

4

26 回答 26

77

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

这是算法的一些 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% 确定我的算法描述,但如果不是这样,它就是类似的东西。

于 2008-09-21T06:03:34.293 回答
48

比阿特金筛法更多的分解技术。例如,假设我们想要分解 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。我很有信心它没有用阿特金的筛子解决这个问题......)

于 2008-09-21T08:47:26.643 回答
35

@亚斯基

您的除数函数有一个错误,即它对于完美的正方形不能正常工作。

尝试:

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;
}
于 2011-03-23T14:32:23.587 回答
30

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

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

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代码。

于 2008-09-23T01:53:44.487 回答
15

这是一个简单的 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

于 2013-04-05T04:32:40.190 回答
11

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

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 的唯一除数。

于 2008-11-04T02:52:27.333 回答
10

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

于 2008-09-21T18:38:04.073 回答
7

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

小就是美:)

于 2011-11-11T02:59:27.923 回答
6

Atkin 筛法是 Eratosthenes 筛法的优化版本,它给出了一个给定整数以内的所有素数。你应该能够谷歌这个以获得更多细节。

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

计算数字 (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
于 2008-09-21T06:36:17.910 回答
6

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

def factors(n):
    for x in xrange(2,n):
        if n%x == 0:
            return (x,) + factors(n/x)
    return (n,1)
于 2009-07-18T03:31:27.657 回答
5

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

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

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

于 2010-02-02T00:28:59.780 回答
4

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

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

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

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

于 2009-07-18T04:11:54.253 回答
4

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

#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;
}
于 2014-12-01T14:01:19.630 回答
3

除数做了一些惊人的事情:他们完全分开。如果您想检查一个数字 的除数个数,n跨越整个频谱 显然是多余的1...n。我没有对此进行任何深入的研究,但我解决了Project Euler 的关于 Triangular Numbers 的问题 12。我对大于 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;
}

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

节日快乐。

于 2010-12-30T21:19:34.097 回答
2

你想要阿特金的筛子,在这里描述:http ://en.wikipedia.org/wiki/Sieve_of_Atkin

于 2008-09-21T05:53:17.577 回答
2

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

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

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

  1. 测试数字是否为素数(快速)
  2. 如果是,则返回 2
  3. 否则,对数字进行因式分解(如果有多个大素因数,则速度较慢)
  4. 从因式分解计算 τ(n)
于 2013-07-14T12:15:33.400 回答
2

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

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

    }
}
于 2014-12-02T03:25:27.310 回答
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  .
于 2013-01-09T23:12:36.013 回答
1

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

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

该算法适用于完美平方数和非完美平方数。

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

请注意,将上限存储在单独的变量中也可以节省时间,您不应该在 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++)
{
    ...
}
于 2014-08-19T13:35:05.060 回答
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;
}
于 2014-12-01T13:02:47.297 回答
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;
}
于 2016-11-11T15:32:34.793 回答
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

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

于 2008-09-21T05:59:48.093 回答
0

我想这就是你要找的。我完全按照你的要求做。将其复制并粘贴到记事本中。另存为 *.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
于 2016-02-07T21:24:50.720 回答
0

我想这个既方便又精确

脚本.pyton

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

于 2017-01-23T15:57:52.633 回答
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;
}
于 2017-01-23T16:01:49.877 回答
-1

我不知道最有效的方法,但我会做以下事情:

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

应该工作\o/

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

于 2008-09-21T06:16:10.627 回答