[英]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.