【问题标题】:How many numbers below N are coprimes to N?N以下有多少个数与N互质?
【发布时间】:2009-06-19 17:09:57
【问题描述】:

简而言之:

假设 ab 互质,如果 GCD(a,b) = 1(其中 GCD 代表 great common divisor), N以下有多少个正整数与N互质?

有什么聪明的办法吗?


不必要的东西

这是最愚蠢的方法:

def count_coprime(N):
    counter = 0
    for n in xrange(1,N):
        if gcd(n,N) == 1:
            counter += 1
    return counter

它可以工作,但它很慢,而且很笨。我想使用一个聪明和更快的算法。 我尝试使用 N 的质因数和除数,但我总是得到一些不适用于较大 N 的东西。

我认为该算法应该能够计算它们而无需像最愚蠢的算法那样计算所有它们:P

编辑

看来我找到了一个可行的:

def a_bit_more_clever_counter(N):
    result = N - 1
    factors = []
    for factor, multiplicity in factorGenerator(N):
        result -= N/factor - 1
        for pf in factors:
            if lcm(pf, factor) < N:
                result += N/lcm(pf, factor) - 1
        factors += [factor]
    return result

其中 lcm 是最小公倍数。谁有更好的?

注意

我正在使用python,我认为即使不了解python的人也应该可以阅读代码,如果您发现任何不清楚的地方,请在cmets中询问。我对算法和数学、想法很感兴趣。

【问题讨论】:

  • 家庭作业?欧拉计划?
  • 请注意,正如下面的一些答案所指出的,这是欧拉的全部。如果你能找到真正聪明的方法,NSA 会很乐意知道(好像你能有效地找到 phi(n),你可以解 RSA 公钥算法)

标签: algorithm math


【解决方案1】:

[编辑] 最后一个想法,哪个(IMO)足够重要,我将把它放在开头:如果你一次收集一堆 totients,你可以避免很多多余的工作。不必费心从大数开始寻找较小的因数,而是对较小的因数进行迭代并为较大的数累积结果。

class Totient:
    def __init__(self, n):
        self.totients = [1 for i in range(n)]
        for i in range(2, n):
            if self.totients[i] == 1:
                for j in range(i, n, i):
                    self.totients[j] *= i - 1
                    k = j / i
                    while k % i == 0:
                        self.totients[j] *= i
                        k /= i
    def __call__(self, i):
        return self.totients[i]
if __name__ == '__main__':
    from itertools import imap
    totient = Totient(10000)
    print sum(imap(totient, range(10000)))

这在我的桌面上只需要 8 毫秒。


Euler totient function 上的维基百科页面有一些很好的数学结果。

计算与 的每个除数互质且小于每个除数的数字:这有一个简单的* 映射来计算从 到 的整数,所以总和是。

* 由第二个定义 trivial

这对于 Möbius inversion formula 的应用来说是完美的,这是一个巧妙的技巧,用于反转这种精确形式的总和。

这自然会导致代码

def totient(n):
    if n == 1: return 1
    return sum(d * mobius(n / d) for d in range(1, n+1) if n % d == 0)
def mobius(n):
    result, i = 1, 2
    while n >= i:
        if n % i == 0:
            n = n / i
            if n % i == 0:
                return 0
            result = -result
        i = i + 1
    return result

Möbius function 存在更好的实现,可以记忆它以提高速度,但这应该很容易理解。

totient函数的计算比较明显

换句话说,将数字完全分解为唯一的素数和指数,然后从那里做一个简单的乘法。

from operator import mul
def totient(n):
    return int(reduce(mul, (1 - 1.0 / p for p in prime_factors(n)), n))
def primes_factors(n):
    i = 2
    while n >= i:
        if n % i == 0:
            yield i
            n = n / i
            while n % i == 0:
                n = n / i
        i = i + 1

同样,prime_factors 存在更好的实现,但这是为了便于阅读。


# helper functions

from collections import defaultdict
from itertools import count
from operator import mul
def gcd(a, b):
    while a != 0: a, b = b % a, a
    return b
def lcm(a, b): return a * b / gcd(a, b)
primes_cache, prime_jumps = [], defaultdict(list)
def primes():
    prime = 1
    for i in count():
        if i < len(primes_cache): prime = primes_cache[i]
        else:
            prime += 1
            while prime in prime_jumps:
                for skip in prime_jumps[prime]:
                    prime_jumps[prime + skip] += [skip]
                del prime_jumps[prime]
                prime += 1
            prime_jumps[prime + prime] += [prime]
            primes_cache.append(prime)
        yield prime
def factorize(n):
    for prime in primes():
        if prime > n: return
        exponent = 0
        while n % prime == 0:
            exponent, n = exponent + 1, n / prime
        if exponent != 0:
            yield prime, exponent

# OP's first attempt

def totient1(n):
    counter = 0
    for i in xrange(1, n):
        if gcd(i, n) == 1:
            counter += 1
    return counter

# OP's second attempt

# I don't understand the algorithm, and just copying it yields inaccurate results

# Möbius inversion

def totient2(n):
    if n == 1: return 1
    return sum(d * mobius(n / d) for d in xrange(1, n+1) if n % d == 0)
mobius_cache = {}
def mobius(n):
    result, stack = 1, [n]
    for prime in primes():
        if n in mobius_cache:
            result = mobius_cache[n]
            break
        if n % prime == 0:
            n /= prime
            if n % prime == 0:
                result = 0
                break
            stack.append(n)
        if prime > n: break
    for n in stack[::-1]:
        mobius_cache[n] = result
        result = -result
    return -result

# traditional formula

def totient3(n):
    return int(reduce(mul, (1 - 1.0 / p for p, exp in factorize(n)), n))

# traditional formula, no division

def totient4(n):
    return reduce(mul, ((p-1) * p ** (exp-1) for p, exp in factorize(n)), 1)

使用此代码计算我桌面上从 1 到 9999 的所有数字的总和,平均运行 5 次以上,

  • totient1 需要永远
  • totient2 需要 10 秒
  • totient3 需要 1.3 秒
  • totient4 需要 1.3 秒

【讨论】:

    【解决方案2】:

    这是Euler totient function,phi。

    它具有令人兴奋的乘法特性:如果 gcd(m,n) = 1 则 phi(mn) = phi(m)phi(n)。并且 phi 很容易计算素数的幂,因为它们下面的所有内容都是互质的,除了同一个素数的较小幂的倍数。

    显然,因式分解仍然不是一个微不足道的问题,但即使是 sqrt(n) 试除法(足以找到所有质因数)也比欧几里得算法的 n-1 次应用要好。

    如果你记忆,你可以降低计算大量它们的平均成本。

    【讨论】:

      【解决方案3】:

      这是 wikipedia 页面上给出的公式的简单、直接的实现,使用 gmpy 进行简单的因式分解(我有偏见,但如果你关心在 Python 中玩有趣的整数东西,你可能想要 gmpy...;-) :

      import gmpy
      
      def prime_factors(x):
          prime = gmpy.mpz(2)
          x = gmpy.mpz(x)
          factors = {}
          while x >= prime:
              newx, mult = x.remove(prime)
              if mult:
                  factors[prime] = mult
                  x = newx
              prime = prime.next_prime()
          return factors
      
      def euler_phi(x):
          fac = prime_factors(x)
          result = 1
          for factor in fac:
            result *= (factor-1) * (factor**(fac[factor]-1))
          return result
      

      例如,在我的普通工作站上,计算 euler_phi(123456789) [我得到 82260072] 需要 937 微秒(使用 Python 2.5;使用 2.4 为 897),这似乎是相当合理的性能。

      【讨论】:

      • Alex,在我相当新的机器上,在 Windows XP 上使用 Python 2.5,与使用超过素数的纯 Python 因子分解器的疏忽时间相比,你的 prime_factors 在 N = 2389 * 5640689 上需要 5.5 秒。为什么?
      • 大部分工作是找到素数(我的答案代码中的 prime.next_prime() 调用)——如果您已经知道所有感兴趣的素数,那么代码当然更快.. . 但是当你第一次探索一个比你硬编码的素数更大的素数时你会怎么做?-)
      【解决方案4】:
      猜你喜欢
      • 2020-02-06
      • 2012-02-24
      • 1970-01-01
      • 1970-01-01
      • 2022-01-04
      • 2011-06-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多