我可以降低这个的计算复杂度吗?

时间:2008-12-20 20:51:36

标签: algorithm python-3.x complexity-theory time-complexity

好吧,我有一些代码正在大大减慢程序的速度,因为它是线性复杂性,但很多时候都会使程序二次复杂化。如果可能的话,我想降低其计算复杂度,否则我会尽可能地优化它。到目前为止,我已经减少到:

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词典对这个函数进行了记忆......

10 个答案:

答案 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