关于性能:计算给定数量的除数数的算法

Algorithm to calculate the number of divisors of a given number

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

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

编辑:所有的答案都很有帮助,谢谢。我正在实施阿特金的筛选,然后我将使用类似于乔纳森·莱弗勒所说的东西。JustinBozonier发布的链接提供了我想要的更多信息。


德米特里是对的,你会希望筛选阿特金生成主要名单,但我不认为这照顾到整个问题。现在你有了一个素数列表,你需要看看这些素数中有多少是作为除数的(以及有多少次)。

here's some python for the algolook here and search for"subject:math-need divisors algorithm".只需计算列表中的项目数,而不是返回它们。

这是一个数学博士,他解释了你需要在数学上做什么。

基本上可以归结为,如果您的数字n是:江户十一〔一〕号(其中a、b和c是n的素数除数,x、y和z是除数重复的次数)那么所有除数的总数是:(x + 1) * (y + 1) * (z + 1)

编辑:顺便说一句,要找到A、B、C等,如果我理解正确的话,你会想做一个贪婪的算法。从最大的素数除数开始,将其自身相乘,直到进一步的相乘超过n。然后移到下一个最低的因子,乘以前一个素数乘以当前素数,并一直乘以素数,直到下一个素数超过n。等等,跟踪你将除数相乘的次数,然后把这些数字应用到上面的公式中。

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


有比阿特金筛更多的分解技术。例如,假设我们想要系数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位数字(如124321342332143213123234331221321342423141)乘以因数。(如果您想知道,它的因子分解是29*439*1321*157907*284749*33843676813*4857795469949。我很有信心,用阿特金的筛子没能搞清楚……)


@ Yasky

你的除数函数有一个缺陷,它不能正确地适用于完全平方。

尝试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}


我不同意阿特金筛子是一种方法,因为检查[1,n]中的每一个数字的初等性比按分区减少数字容易花费更长的时间。

下面是一些代码,虽然稍微有点粗糙,但通常速度要快得多:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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代码来解决这个问题的。


这个有趣的问题比看上去难得多,而且还没有得到回答。这个问题可以分解成两个截然不同的问题。

1给定n,找出n的素因子列表l2给定l,计算唯一组合数

到目前为止,我所看到的所有答案都涉及到1,而且没有提到它对于庞大的数字是不可理解的。对于中等大小的n,即使是64位数字,也很容易;对于巨大的n,阶乘问题可能需要"永远"。公钥加密依赖于此。

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

重申2,给定集合C包含k个项目,这样项目A有一个‘重复’,项目B有一个‘重复’,等等。1到k-1项目有多少个独特的组合?例如,2,2,2,2,2,2,2,3,2,2,3,3如果l=2,2,2,3,3,5,则每个事件必须发生一次且仅发生一次。每一个这样的唯一子集合都是一个n的唯一除数,乘以子集合中的项。


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

  • 概述:http://en.wikipedia.org/wiki/divisor_函数

  • 小的n和一些有用的参考值:a000005:d(n)(也称为tau(n)或sigma_(n)),n的除数。

  • 实数例子:整数的因式分解


这里是一个直接的O(sqrt(n))算法。我用这个解项目欧拉

1
2
3
4
5
6
7
8
9
def divisors(n):
    count=2 # accounts for 'n' and '1'
    i=2
    while(i**2 < n):
        if(n%i==0):
            count+=2
        i+=1
    count+=(1 if i**2==n else 0)
    return count


只有一行我对你的问题考虑得很仔细,我试着写一段效率很高、性能很好的代码。要在屏幕上打印给定数字的所有除数,我们只需要一行代码!(通过gcc编译时使用选项-std=c99)

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

对于查找除数,可以使用以下非常快的函数(适用于除1和2之外的所有整数)

1
2
3
4
5
6
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之外的所有整数都正确工作)

1
2
3
4
5
6
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,可以使用以下功能之一(稍微慢一点)

1
2
3
4
5
6
7
8
9
10
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;
}

1
2
3
4
5
6
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;
}

小是美的:)


你可以试试这个。它有点黑,但速度相当快。

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


阿特金筛是埃拉托斯滕筛的一个优化版本,它使所有素数都达到一个给定的整数。你应该能够在谷歌上搜索到更多细节。

一旦你有了这个列表,用你的数字除以每一个素数,看看它是不是一个精确的除数(即余数是零)。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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


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

例如:三十六主因子分解:2^2*3^2除数:1,2,3,4,6,9,12,18,36除数:9

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


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

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

1)当一个人需要一段时间来做除法时,他们在计算机上的速度非常快——这与查找答案的成本类似。

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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;
}


除数做了一些惊人的事情:它们完全分裂。如果你想检查一个数的除数,n,显然是多余的,跨越整个光谱,1...n。我没有对此做过深入的研究,但是我解决了项目欧拉关于三角形数的问题12。我的大于500除数测试的解决方案运行了309504微秒(~0.3秒)。我为解写了这个除数函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}

对于每一种算法,都有一个弱点。我认为这对素数来说是弱的。但由于三角数字不是印刷品,所以它完美地发挥了作用。从我的资料来看,我认为它做得很好。

节日快乐。


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

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

该算法不仅适用于完全平方数,而且适用于非完全平方数。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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
",numberOfDivisors);
    return 0;
}

除了上面的for循环之外,您还可以使用下面的循环,因为这样可以消除查找数字平方根的需要,从而更加高效。

1
2
3
4
for(i=2;i*i<=n;i++)
{
    ...
}


@肯德尔

我测试了您的代码并做了一些改进,现在速度更快了。我也用@?????????????????代码,这也比他的代码快。

1
2
3
4
5
6
7
8
9
10
11
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;
}

质数法在这里非常清楚。p[]是小于或等于sq=sqrt(n)的素数列表;

1
2
3
4
5
6
7
8
9
10
11
12
13
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  .

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

现在注意,对于pA素数,τ(p**k)=k+1(p的幂)。因此,可以很容易地从它的因式分解中计算τ(n)。

然而,分解大数的过程可能很慢(rsa-crytophy的安全性取决于两个大素数的乘积很难分解)。这表明了这种优化算法

  • 测试数字是否为质数(快速)
  • 如果是,返回2
  • 否则,对数字进行因式分解(如果有多个大素数,则速度较慢)
  • 根据因式分解计算τ(n)

  • 这是我写的一个函数。最糟糕的时间复杂度是O(sqrt(n)),另一方面最好的时间是O(log(n))。它给出了所有的素数除数及其发生的次数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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;
    }


    你想要atkin的筛子,描述如下:http://en.wikipedia.org/wiki/sive-of-atkin


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    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))


    我想这个既方便又精确

    script.pyton脚本

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


    您可以预先计算素数,直到最大可能n的平方根,然后计算一个数的每个素数因子的指数。n的除数(n=p1^a p2^b p3^c…)是(a+1)(b+1)(c+1),因为它与计算这个因子的素数的方法相同(这将计算除数)。如果你预先计算质数,这会很快

    有关此方法的详细信息:

    https://mathschallenge.net/library/number/number_除数

    https://www.math.upenn.edu/~deturck/m170/wk2/numdivisors.html

    http://primes.utm.edu/glossary/xpage/tau.html(英文)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    #include <iostream>
    #include <cmath>
    #include
    #include <vector>
    using namespace std;

    int divisors_count(const vector<int>& primes, int n)
    {
        int divisors = 1;
        for (int i = 0; i < primes.size(); ++i) {
            int factor = primes[i];
            int factor_exponent = 0;
            while (n % factor == 0) {
                ++factor_exponent;
                n /= factor;
            }
            divisors *= (factor_exponent + 1);
        }
        if (n > 1)
            return 2*divisors; // prime factor > sqrt(MAX_N)
        return divisors;
    }

    int main()
    {
        const int MAX_N = 1e6;
        int max_factor = sqrt(MAX_N);

        vector<char> prime(max_factor + 1, true);
        for (int i = 3; i <= max_factor; i += 2) {
            if (prime[i]) {
                for (int j = 3*i; j <= max_factor; j += 2*i) {
                    prime[j] = false;
                }  
            }
        }

        vector<int> primes;
        primes.reserve(max_factor/2);
        primes.push_back(2);
        for (int i = 3; i <= max_factor; i += 2) {
            if (prime[i]) {
                primes.push_back(i);
            }
        }

        int n;
        while (cin >> n) {
            cout << divisors_count(primes, n) << endl;
        }
    }


    尝试以下方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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
    2
    3
    4
    5
    6
    7
    8
    9
    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

    然后由你来结合这些因素来决定剩下的答案。


    我想这就是你要找的。我按你的要求做。复制并粘贴到记事本中。另存为*.bat.run.enter number。将进程乘以2,即除数。我故意这样做,以便它更快地确定除数:

    请注意,cmd varriable不能支持超过999999999的值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    @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


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

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

    应该工作o/

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