N下面有多少个数字是N的互质?

时间:2009-06-19 17:09:58

标签: algorithm math

简而言之:

如果 GCD(a,b)= 1 (其中GCD代表great common divisor a b 相互作用),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的东西。

我认为算法应该能够计算它们而不计算像dumbest算法那样的所有算法: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的人来说,代码也应该是可读的,如果你发现任何不清楚的东西,只需在评论中提问。我对算法和数学,这个想法很感兴趣。

4 个答案:

答案 0 :(得分:34)

[编辑] 最后一个想法,哪个(IMO)非常重要,我会把它放在开头:如果你一次收集一堆东西,你可以避免很多冗余的工作。不要从大数字开始寻找较小的因子 - 而是迭代较小的因子并积累较大数字的结果。

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上的维基百科页面有一些不错的数学结果。

\sum_{d\mid n}\varphi(d)计算互质数且小于n的每个除数:这有一个简单的*映射来计算从1n的整数,所以总和为n

trivial

的第二个定义

*

这适用于Möbius inversion formula的应用,这是一种巧妙的技巧,用于反转这种形式的总和。

\varphi(n)=\sum_{d\mid n}d\cdot\mu\left(\frac nd\right)

这自然导致了代码

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函数的更明显的计算是

\varphi\left(p_1^{k_1}\dots p_r^{k_r}\right)=(p_1-1)p_1^{k_1-1}\dots(p_r-1)p_r^{k_r-1}p_1^{k_1}\dots p_r^{k_r}\prod_{i=1}^r\left(1-\frac1{p_r}\right)

换句话说,将数字完全分解为唯一的素数和指数,并从那里进行简单的乘法。

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.3s
  • totient4需要1.3s

答案 1 :(得分:29)

这是Euler totient function,phi。

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

显然,因子分解仍然不是一个微不足道的问题,但即使是sqrt(n)试验分区(足以找到所有主要因素)也会超过欧几里德算法的n-1个应用程序。

如果你记住,你可以降低计算其中很多的平均成本。

答案 2 :(得分:5)

这是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; 897和2.4),这似乎是一个非常合理的性能。

答案 3 :(得分:1)