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

发布于 2024-07-10 19:45:10 字数 601 浏览 7 评论 0原文

好吧,我的这段代码极大地减慢了程序的速度,因为它是线性复杂性的,但被调用了很多次,使得程序的复杂性变成了二次方。 如果可能的话,我想降低其计算复杂性,但否则我会尽可能优化它。 到目前为止,我已经简化为:

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 字典记住了该函数......

Well, I have this bit of code that is slowing down the program hugely because it is linear complexity but called a lot of times making the program quadratic complexity. If possible I would like to reduce its computational complexity but otherwise I'll just optimize it where I can. So far I have reduced down to:

def table(n):
    a = 1
    while 2*a <= n:
      if (-a*a)%n == 1: return a

      a += 1

Anyone see anything I've missed? Thanks!

EDIT: I forgot to mention: n is always a prime number.

EDIT 2: Here is my new improved program (thank's for all the contributions!):

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

EDIT 3: And testing it out in its real context it is much faster! Well this question appears solved but there are many useful answers. I should also say that as well as those above optimizations, I have memoized the function using Python dictionaries...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

忆离笙 2024-07-17 19:45:11

您正在做的一件事是一遍又一遍地重复计算 -a*a 。

创建一个值表一次,然后在主循环中查找。

另外,虽然这可能不适用于您,因为您的函数名称是表,但如果您调用一个需要时间来计算的函数,您应该将结果缓存在表中,并且如果您使用相同的名称再次调用它,则只需进行表查找价值。 这可以节省您首次运行时计算所有值的时间,但您不会浪费时间多次重复计算。

One thing that you are doing is repeating the calculation -a*a over and over again.

Create a table of the values once and then do look up in the main loop.

Also although this probably doesn't apply to you because your function name is table but if you call a function that takes time to calculate you should cache the result in a table and just do a table look up if you call it again with the same value. This save you the time of calculating all of the values when you first run but you don't waste time repeating the calculation more than once.

空城仅有旧梦在 2024-07-17 19:45:11

我检查并修复了哈佛版本,使其可以与 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

I went through and fixed the Harvard version to make it work with python 3.
http://modular.fas.harvard.edu/ent/ent_py

I made some slight changes to make the results exactly the same as the OP's function. There are two possible answers and I forced it to return the smaller answer.

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)

This version takes about 3ms to run through the test cases above.

For comparison using the prime number 1234577
OP Edit2 745ms
The accepted answer 522ms
The above function 0.2ms

烂人 2024-07-17 19:45:10

暂时忽略算法(是的,我知道,坏主意),只需从 while 切换到 for< /代码>。

for a in range(1, n / 2 + 1)

(希望这不会出现相差一的错误。我很容易犯这些错误。)

我会尝试的另一件事是看看步长是否可以增加。

Ignoring the algorithm for a moment (yes, I know, bad idea), the running time of this can be decreased hugely just by switching from while to for.

for a in range(1, n / 2 + 1)

(Hope this doesn't have an off-by-one error. I'm prone to make these.)

Another thing that I would try is to look if the step width can be incremented.

二智少女猫性小仙女 2024-07-17 19:45:10

看看http://modular.fas.harvard.edu/ent/ent_py .
如果设置 a = -1 且 p = n,则函数 sqrtmod 即可完成这项工作。

你错过了一个小点,因为你的改进算法的运行时间仍然是n的平方根的数量级。 只要您只有小素数 n(比方说小于 2^64),就可以了,并且您可能应该更喜欢您的实现而不是更复杂的实现。

如果素数 n 变得更大,您可能必须切换到使用一点数论的算法。 据我所知,您的问题只能在时间 log​​(n)^3 内使用概率算法来解决。 如果我没记错的话,假设黎曼假设成立(大多数人都是这样做的),可以证明以下算法的运行时间(在 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 整数运算); 对于 512 位素数求 -1 的平方根仍然需要不到一秒的时间。

Take a look at http://modular.fas.harvard.edu/ent/ent_py .
The function sqrtmod does the job if you set a = -1 and p = n.

You missed a small point because the running time of your improved algorithm is still in the order of the square root of n. As long you have only small primes n (let's say less than 2^64), that's ok, and you should probably prefer your implementation to a more complex one.

If the prime n becomes bigger, you might have to switch to an algorithm using a little bit of number theory. To my knowledge, your problem can be solved only with a probabilistic algorithm in time log(n)^3. If I remember correctly, assuming the Riemann hypothesis holds (which most people do), one can show that the running time of the following algorithm (in ruby - sorry, I don't know python) is 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

The slow part is now finding the prime (which takes approx. log(n)^4 integer operation); finding the square root of -1 takes for 512-bit primes still less than a second.

兮子 2024-07-17 19:45:10

考虑预先计算结果并将其存储在文件中。 现在许多平台都拥有巨大的磁盘容量。 然后,获得结果将是一个 O(1) 的操作。

Consider pre-computing the results and storing them in a file. Nowadays many platforms have a huge disk capacity. Then, obtaining the result will be an O(1) operation.

┊风居住的梦幻卍 2024-07-17 19:45:10

(以亚当的回答为基础。)
查看关于二次互易的维基百科页面:

x^2 equation -1 (mod p) 当且仅当 p equation 1 (mod 4) 时可解。

然后,您可以避免精确搜索那些与 1 模 4 不全等的奇素数 n 的根:

def table(n):
    if n == 2: return 1
    if n%4 != 1: return None   # or raise exception
    ...

(Building on Adam's answer.)
Look at the Wikipedia page on quadratic reciprocity:

x^2 ≡ −1 (mod p) is solvable if and only if p ≡ 1 (mod 4).

Then you can avoid the search of a root precisely for those odd prime n's that are not congruent with 1 modulo 4:

def table(n):
    if n == 2: return 1
    if n%4 != 1: return None   # or raise exception
    ...
唔猫 2024-07-17 19:45:10

基于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

Based off OP's second edit:

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
弄潮 2024-07-17 19:45:10

看起来您正在尝试求 -1 模 n 的平方根。 不幸的是,这不是一个简单的问题,具体取决于您的函数中输入的 n 值。 根据n,甚至可能没有解决方案。 有关此问题的更多信息,请参阅维基百科

It looks like you're trying to find the square root of -1 modulo n. Unfortunately, this is not an easy problem, depending on what values of n are input into your function. Depending on n, there might not even be a solution. See Wikipedia for more information on this problem.

小…楫夜泊 2024-07-17 19:45:10

编辑2:令人惊讶的是,减少平方强度大大减少了时间,至少在我的Python2.5安装上是这样。 (我很惊讶,因为我认为解释器开销占用了大部分时间,并且这并没有减少内部循环中的操作数量。)将表(1234577)的时间从 0.572 秒减少到 0.146 秒。

 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 发布了同样的想法,但我认为不太严格编码。 同样,jug 的答案是最好的。

原始答案:在康拉德·鲁道夫的基础上进行的另一个简单的编码调整:

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。 (学术性的,因为有更好的算法。)

Edit 2: Surprisingly, strength-reducing the squaring reduces the time a lot, at least on my Python2.5 installation. (I'm surprised because I thought interpreter overhead was taking most of the time, and this doesn't reduce the count of operations in the inner loop.) Reduces the time from 0.572s to 0.146s for table(1234577).

 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 posted the same idea but I think less tightly coded. Again, jug's answer is best.

Original answer: Another trivial coding tweak on top of Konrad Rudolph's:

def table(n):
    n1 = n - 1
    for a in xrange(1, n // 2 + 1):
          if (a*a) % n == n1: return a

Speeds it up measurably on my laptop. (About 25% for table(1234577).)

Edit: I didn't notice the python3.0 tag; but the main change was hoisting part of the calculation out of the loop, not the use of xrange. (Academic since there's a better algorithm.)

゛清羽墨安 2024-07-17 19:45:10

你可以缓存结果吗?

当你计算一个大的 n 时,你几乎可以免费得到较小的 n 的结果。

Is it possible for you to cache the results?

When you calculate a large n you are given the results for the lower n's almost for free.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文