繁体   English   中英

计算所有数字的除数数,直到 N

[英]Count number of Divisors for all the numbers till N

我需要计算1 to n范围内每个数字的所有除数。 我在下面写了一个实现,给定一个整数num ,它计算num的除数。 它的复杂度是O(sqrt(n)) 因此,所有复杂性都是O(n * sqrt(n)) 可以减少吗? 如果是,那么你能给出一个算法吗?

代码

 public static int countDivisors(int num)
    {
        int limit = (int)Math.sqrt(num);
        int count = 2;
        for(int i = 2 ; i <= limit ; i++)
        {
            if(num % i == 0)
            {
                count++;
                if(num / i != i)
                {
                    count++;
                }
            }
        }
        return count;
    }

PS:
该函数将被调用n次。

您可以使用一种广义的Eratosthenes筛子来改进朴素的方法。 不仅将数字标记为复合数,还存储您找到的第一个除数(我在下面的computeDivs函数中执行此操作)。

class Main
{
    // using Sieve of Eratosthenes to factorize all numbers
    public static int[] computeDivs(int size) {
      int[] divs = new int[size + 1];
      for (int i = 0; i <  size + 1; ++i) {
        divs[i] = 1;
      }
      int o = (int)Math.sqrt((double)size);
      for (int i = 2; i <= size; i += 2) {
        divs[i] = 2;
      }

      for (int i = 3; i <= size; i += 2) {
        if (divs[i] != 1) {
          continue;
        }
        divs[i] = i;
        if (i <= o) {
          for (int j = i * i; j < size; j += 2 * i) {
            divs[j] = i;
          }
        }
      }
      return divs;
    }

    // Counting the divisors using the standard fomula
    public static int countDivisors(int x, int[] divs) {
      int result = 1;
      int currentDivisor = divs[x];
      int currentCount = 1;
      while (currentDivisor != 1) {
        x /= currentDivisor;
        int newDivisor = divs[x];
        if (newDivisor != currentDivisor) {
          result *= currentCount + 1;
          currentDivisor = newDivisor;
          currentCount = 1;
        } else {
          currentCount++;
        }
      }
      if (x != 1) {
        result *= currentCount + 1;
      }

      return result;
    }


    public static int countAllDivisors(int upTo) {
      int[] divs = computeDivs(upTo + 1);
      int result = 0;
      for (int i = 1; i <= upTo; ++i) {
        result += countDivisors(i, divs);
      }
      return result;

    }

    public static void main (String[] args) throws java.lang.Exception {
      System.out.println(countAllDivisors(15));
    }
}

您还可以在此处查看在ideone上执行的代码。

简而言之,我使用筛子来计算每个数字的最大素数。 使用它,我可以非常有效地计算每个数字的因子分解(并且在countDivisors中使用它)。

很难计算筛子的复杂度,但是标准估计值为O(n * log(n)) 同样,我非常有信心无法改善这种复杂性。

通过使用简单的迭代,您可以比O(n.sqrt(n))做的更好。 该代码是C ++,但是您可以轻松理解。

#include <iostream>
#include <vector>
using namespace std;

void CountDivisors(int n) {
    vector<int> cnts(n + 1, 1);
    for (int i = 2; i <= n; ++i) {
        for (int j = i; j <= n; j += i) {
            cnts[j]++;
        }
    }
    for (int i = 1; i <= n; ++i) {
        cout << cnts[i] << " \n"[i == n];
    }
}

int main() {
    CountDivisors(100);
    return 0;
}

运行时间为n/1 + n/2 + n/3 + n/4 + ... + n/n ,可以用O(nH(n))近似,其中H(n)谐波序列 我认为该值不大于O(nlog(n))

对于相对较小的数字,使用迭代是可以的。 一旦除数的数量越来越大(超过 100-200),迭代将花费大量时间。

更好的方法是在数的素因数分解的帮助下计算除数的数量。

所以,用素数分解来表达这个数字,如下所示:

  public static List<Integer> primeFactorizationOfTheNumber(long number) {
    List<Integer> primes = new ArrayList<>();
    var remainder = number;
    var prime = 2;

    while (remainder != 1) {
      if (remainder % prime == 0) {
        primes.add(prime);
        remainder = remainder / prime;
      } else {
        prime++;
      }
    }
    return primes;
  }

接下来,给定素数分解,以指数形式表示,得到指数并对每个指数加1 接下来,将结果数字相乘。 结果将是一个数字的除数计数。 更多关于这个here

  private long numberOfDivisorsForNumber(long number) {
    var exponentsOfPrimeFactorization = primeFactorizationOfTheNumber(number)
      .stream()
      .collect(Collectors.groupingBy(Integer::intValue, Collectors.counting()))
      .values();
    return exponentsOfPrimeFactorization.stream().map(n -> n + 1).reduce(1L, Math::multiplyExact);
  }

该算法运行速度非常快。 对我来说,它会在一秒钟内找到一个有 500 个除数的数字。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM