快速素因数分解模块
我在寻找一个能计算出数字 N 的质因数的实现方式或清晰的算法,可以用 Python、伪代码或者其他易读的方式。这里有一些要求:
- N 的范围在 1 到大约 20 位数字之间
- 不能使用预先计算好的查找表,不过可以使用记忆化
- 不需要数学证明(比如可以依赖哥德巴赫猜想)
- 不需要非常精确,可以是概率性的或确定性的
我需要一个快速的质因数分解算法,不仅仅是为了这个算法本身,还可以在其他很多算法中使用,比如计算欧拉函数 phi(n)。
我尝试过维基百科上的其他算法,但要么我看不懂(比如 ECM),要么我无法根据算法创建一个可用的实现(比如 Pollard-Brent 算法)。
我对 Pollard-Brent 算法非常感兴趣,所以如果能提供更多信息或实现代码,那就太好了。
谢谢!
编辑
经过一番折腾,我创建了一个相当快速的质因数分解模块。它结合了优化过的试除法、Pollard-Brent 算法、米勒-拉宾素性测试,以及我在网上找到的最快的质数筛选算法。gcd 是一个常规的欧几里得最大公约数实现(而二进制欧几里得算法要慢得多)。
悬赏
太好了,可以获得悬赏!但我该如何赢得它呢?
- 找出我模块中的优化点或bug。
- 提供替代的或更好的算法/实现。
最完整或最有建设性的回答将获得悬赏。
最后是模块的代码:
import random
def primesbelow(N):
# http://stackoverflow.com/questions/2068372/fastest-way-to-list-all-primes-below-n-in-python/3035188#3035188
#""" Input N>=6, Returns a list of primes, 2 <= p < N """
correction = N % 6 > 1
N = {0:N, 1:N-1, 2:N+4, 3:N+3, 4:N+2, 5:N+1}[N%6]
sieve = [True] * (N // 3)
sieve[0] = False
for i in range(int(N ** .5) // 3 + 1):
if sieve[i]:
k = (3 * i + 1) | 1
sieve[k*k // 3::2*k] = [False] * ((N//6 - (k*k)//6 - 1)//k + 1)
sieve[(k*k + 4*k - 2*k*(i%2)) // 3::2*k] = [False] * ((N // 6 - (k*k + 4*k - 2*k*(i%2))//6 - 1) // k + 1)
return [2, 3] + [(3 * i + 1) | 1 for i in range(1, N//3 - correction) if sieve[i]]
smallprimeset = set(primesbelow(100000))
_smallprimeset = 100000
def isprime(n, precision=7):
# http://en.wikipedia.org/wiki/Miller-Rabin_primality_test#Algorithm_and_running_time
if n < 1:
raise ValueError("Out of bounds, first argument must be > 0")
elif n <= 3:
return n >= 2
elif n % 2 == 0:
return False
elif n < _smallprimeset:
return n in smallprimeset
d = n - 1
s = 0
while d % 2 == 0:
d //= 2
s += 1
for repeat in range(precision):
a = random.randrange(2, n - 2)
x = pow(a, d, n)
if x == 1 or x == n - 1: continue
for r in range(s - 1):
x = pow(x, 2, n)
if x == 1: return False
if x == n - 1: break
else: return False
return True
# https://comeoncodeon.wordpress.com/2010/09/18/pollard-rho-brent-integer-factorization/
def pollard_brent(n):
if n % 2 == 0: return 2
if n % 3 == 0: return 3
y, c, m = random.randint(1, n-1), random.randint(1, n-1), random.randint(1, n-1)
g, r, q = 1, 1, 1
while g == 1:
x = y
for i in range(r):
y = (pow(y, 2, n) + c) % n
k = 0
while k < r and g==1:
ys = y
for i in range(min(m, r-k)):
y = (pow(y, 2, n) + c) % n
q = q * abs(x-y) % n
g = gcd(q, n)
k += m
r *= 2
if g == n:
while True:
ys = (pow(ys, 2, n) + c) % n
g = gcd(abs(x - ys), n)
if g > 1:
break
return g
smallprimes = primesbelow(1000) # might seem low, but 1000*1000 = 1000000, so this will fully factor every composite < 1000000
def primefactors(n, sort=False):
factors = []
for checker in smallprimes:
while n % checker == 0:
factors.append(checker)
n //= checker
if checker > n: break
if n < 2: return factors
while n > 1:
if isprime(n):
factors.append(n)
break
factor = pollard_brent(n) # trial division did not fully factor, switch to pollard-brent
factors.extend(primefactors(factor)) # recurse to factor the not necessarily prime factor returned by pollard-brent
n //= factor
if sort: factors.sort()
return factors
def factorization(n):
factors = {}
for p1 in primefactors(n):
try:
factors[p1] += 1
except KeyError:
factors[p1] = 1
return factors
totients = {}
def totient(n):
if n == 0: return 1
try: return totients[n]
except KeyError: pass
tot = 1
for p, exp in factorization(n).items():
tot *= (p - 1) * p ** (exp - 1)
totients[n] = tot
return tot
def gcd(a, b):
if a == b: return a
while b > 0: a, b = b, a % b
return a
def lcm(a, b):
return abs((a // gcd(a, b)) * b)
9 个回答
即使在当前的代码中,还有几个地方需要注意。
- 不要在每次循环中都做
checker*checker
的计算,应该用s=ceil(sqrt(num))
,然后判断checker < s
。 - 每次
checker
应该加 2,除了 2 以外,其他的偶数都可以忽略。 - 用
divmod
替代%
和//
。
不需要用 primesbelow
来计算 smallprimes
,可以直接用 smallprimeset
。
smallprimes = (2,) + tuple(n for n in xrange(3,1000,2) if n in smallprimeset)
把你的 primefactors
分成两个函数,一个处理 smallprimes
,另一个处理 pollard_brent
,这样可以节省一些循环,因为所有小质数的幂都会从 n 中被除掉。
def primefactors(n, sort=False):
factors = []
limit = int(n ** .5) + 1
for checker in smallprimes:
print smallprimes[-1]
if checker > limit: break
while n % checker == 0:
factors.append(checker)
n //= checker
if n < 2: return factors
else :
factors.extend(bigfactors(n,sort))
return factors
def bigfactors(n, sort = False):
factors = []
while n > 1:
if isprime(n):
factors.append(n)
break
factor = pollard_brent(n)
factors.extend(bigfactors(factor,sort)) # recurse to factor the not necessarily prime factor returned by pollard-brent
n //= factor
if sort: factors.sort()
return factors
根据 Pomerance、Selfridge、Wagstaff 和 Jaeschke 的验证结果,你可以减少 isprime
中的重复计算,这个函数使用了米勒-拉宾素性测试。详细信息可以查看 维基百科。
- 如果 n 小于 1,373,653,只需要测试 a = 2 和 3;
- 如果 n 小于 9,080,191,只需要测试 a = 31 和 73;
- 如果 n 小于 4,759,123,141,只需要测试 a = 2、7 和 61;
- 如果 n 小于 2,152,302,898,747,只需要测试 a = 2、3、5、7 和 11;
- 如果 n 小于 3,474,749,660,383,只需要测试 a = 2、3、5、7、11 和 13;
- 如果 n 小于 341,550,071,728,321,只需要测试 a = 2、3、5、7、11、13 和 17。
编辑 1:修正了 if-else
的返回调用,以便将 bigfactors 添加到 primefactors
的 factors 中。
如果你不想重复造轮子,可以使用这个库 sympy
pip install sympy
使用这个函数 sympy.ntheory.factorint
给定一个正整数
n
,factorint(n)
会返回一个字典,这个字典的键是n
的质因数,值是这些质因数的出现次数。举个例子:
例子:
>>> from sympy.ntheory import factorint
>>> factorint(10**20+1)
{73: 1, 5964848081: 1, 1676321: 1, 137: 1}
你可以对一些非常大的数字进行因数分解:
>>> factorint(10**100+1)
{401: 1, 5964848081: 1, 1676321: 1, 1601: 1, 1201: 1, 137: 1, 73: 1, 129694419029057750551385771184564274499075700947656757821537291527196801: 1}