好吧,我有一些代码正在大大减慢程序的速度,因为它是线性复杂性,但很多时候都会使程序二次复杂化。如果可能的话,我想降低其计算复杂度,否则我会尽可能地优化它。到目前为止,我已经减少到:
def table(n):
a = 1
while 2*a <= n:
if (-a*a)%n == 1: return a
a += 1
有人看到我错过的任何东西吗?谢谢!
编辑:我忘了提及:n总是一个素数。
编辑2:这是我的新改进计划(感谢所有的贡献!):
def table(n):
if n == 2: return 1
if n%4 != 1: return
a1 = n-1
for a in range(1, n//2+1):
if (a*a)%n == a1: return a
编辑3:在真实环境中进行测试,它很多更快!那么这个问题似乎已经解决,但有很多有用的答案。我还应该说,除了上面那些优化之外,我还使用Python词典对这个函数进行了记忆......
答案 0 :(得分:6)
暂时忽略算法(是的,我知道,糟糕的主意),只需从while
切换到for
,就可以大大减少非常的运行时间
for a in range(1, n / 2 + 1)
(希望这不会有一个错误。我很容易做出这些。)
我要尝试的另一件事是查看步长是否可以递增。
答案 1 :(得分:5)
看看http://modular.fas.harvard.edu/ent/ent_py。 如果设置= -1和p = n,则函数sqrtmod可以完成工作。
您错过的一个小点是,改进算法的运行时间仍然是n的平方根的顺序。只要你只有小素数n(比如说小于2 ^ 64)就可以了,你应该更喜欢你的实现更复杂的。
如果素数n变大,则可能必须使用一些数论来切换到算法。据我所知,您的问题只能通过时间log(n)^ 3中的概率算法来解决。如果我没记错的话,假设Riemann假设成立(大多数人都这样做),可以证明以下算法的运行时间(在ruby中 - 抱歉,我不知道python)是log(log(n))*的log(n)^ 3:
class Integer
# calculate b to the power of e modulo self
def power(b, e)
raise 'power only defined for integer base' unless b.is_a? Integer
raise 'power only defined for integer exponent' unless e.is_a? Integer
raise 'power is implemented only for positive exponent' if e < 0
return 1 if e.zero?
x = power(b, e>>1)
x *= x
(e & 1).zero? ? x % self : (x*b) % self
end
# Fermat test (probabilistic prime number test)
def prime?(b = 2)
raise "base must be at least 2 in prime?" if b < 2
raise "base must be an integer in prime?" unless b.is_a? Integer
power(b, self >> 1) == 1
end
# find square root of -1 modulo prime
def sqrt_of_minus_one
return 1 if self == 2
return false if (self & 3) != 1
raise 'sqrt_of_minus_one works only for primes' unless prime?
# now just try all numbers (each succeeds with probability 1/2)
2.upto(self) do |b|
e = self >> 1
e >>= 1 while (e & 1).zero?
x = power(b, e)
next if [1, self-1].include? x
loop do
y = (x*x) % self
return x if y == self-1
raise 'sqrt_of_minus_one works only for primes' if y == 1
x = y
end
end
end
end
# find a prime
p = loop do
x = rand(1<<512)
next if (x & 3) != 1
break x if x.prime?
end
puts "%x" % p
puts "%x" % p.sqrt_of_minus_one
慢速部分现在找到素数(需要大约log(n)^ 4整数运算);找到-1的平方根需要512位素数仍然不到一秒。
答案 2 :(得分:4)
考虑预先计算结果并将其存储在文件中。如今许多平台都拥有巨大的磁盘容量。然后,获得结果将是O(1)操作。
答案 3 :(得分:3)
(以Adam的回答为基础。) 查看quadratic reciprocity上的维基百科页面:
当且仅当p≡1(mod 4)时,x ^2≡-1(mod p)是可解的。
然后你可以避免为那些与1模4不一致的奇数素数n精确搜索根:
def table(n):
if n == 2: return 1
if n%4 != 1: return None # or raise exception
...
答案 4 :(得分:2)
看起来你正试图找到-1 modulo n
的平方根。不幸的是,这不是一个简单的问题,取决于您的函数输入n
的值。根据{{1}},可能甚至没有解决方案。有关此问题的详细信息,请参阅Wikipedia。
答案 5 :(得分:2)
编辑2:令人惊讶的是,强度降低平方减少了很多时间,至少在我的Python2.5安装上如此。 (我很惊讶,因为我认为解释器开销占用了大部分时间,这并没有减少内循环中的操作次数。)将表(1234577)的时间从0.572s减少到0.146s。
def table(n):
n1 = n - 1
square = 0
for delta in xrange(1, n, 2):
square += delta
if n <= square: square -= n
if square == n1: return delta // 2 + 1
strager发布了the same idea,但我认为编码不太紧密。同样,jug's answer是最好的。
原始回答:康拉德鲁道夫的另一个微不足道的编码调整:
def table(n):
n1 = n - 1
for a in xrange(1, n // 2 + 1):
if (a*a) % n == n1: return a
在我的笔记本电脑上加速测量。 (表格(1234577)约为25%。)
编辑:我没注意到python3.0标签;但主要的变化是将部分计算从循环中提升,而不是使用xrange
。 (学术以来,因为有更好的算法。)
答案 6 :(得分:2)
基于OP的第二次编辑:
def table(n):
if n == 2: return 1
if n%4 != 1: return
mod = 0
a1 = n - 1
for a in xrange(1, a1, 2):
mod += a
while mod >= n: mod -= n
if mod == a1: return a//2 + 1
答案 7 :(得分:1)
您是否可以缓存结果?
当你计算一个大的n时,你会得到较低n的结果几乎是免费的。
答案 8 :(得分:1)
您正在做的一件事是一遍又一遍地重复计算-a * a。
创建一个值表,然后在主循环中查找。
此外,虽然这可能不适用于您,因为您的函数名称是表,但如果您调用一个需要时间来计算的函数,您应该将结果缓存到表中,如果再次调用它,只需查看一个表具有相同的价值。这样可以节省您第一次运行时计算所有值的时间,但不会浪费时间多次重复计算。
答案 9 :(得分:1)
我经历并修复了哈佛版本以使其与python 3一起使用。 http://modular.fas.harvard.edu/ent/ent_py
我做了一些细微的修改,使结果与OP的功能完全一致。有两种可能的答案,我强迫它返回较小的答案。
import timeit
def table(n):
if n == 2: return 1
if n%4 != 1: return
a1=n-1
def inversemod(a, p):
x, y = xgcd(a, p)
return x%p
def xgcd(a, b):
x_sign = 1
if a < 0: a = -a; x_sign = -1
x = 1; y = 0; r = 0; s = 1
while b != 0:
(c, q) = (a%b, a//b)
(a, b, r, s, x, y) = (b, c, x-q*r, y-q*s, r, s)
return (x*x_sign, y)
def mul(x, y):
return ((x[0]*y[0]+a1*y[1]*x[1])%n,(x[0]*y[1]+x[1]*y[0])%n)
def pow(x, nn):
ans = (1,0)
xpow = x
while nn != 0:
if nn%2 != 0:
ans = mul(ans, xpow)
xpow = mul(xpow, xpow)
nn >>= 1
return ans
for z in range(2,n) :
u, v = pow((1,z), a1//2)
if v != 0:
vinv = inversemod(v, n)
if (vinv*vinv)%n == a1:
vinv %= n
if vinv <= n//2:
return vinv
else:
return n-vinv
tt=0
pri = [ 5,13,17,29,37,41,53,61,73,89,97,1234577,5915587277,3267000013,3628273133,2860486313,5463458053,3367900313 ]
for x in pri:
t=timeit.Timer('q=table('+str(x)+')','from __main__ import table')
tt +=t.timeit(number=100)
print("table(",x,")=",table(x))
print('total time=',tt/100)
此版本需要大约3毫秒才能完成上述测试用例。
使用素数1234577进行比较
OP Edit2 745ms
接受的答案522ms
以上功能0.2ms