web-dev-qa-db-ja.com

時間計算量O(1) pow(x、y)の場合、O(n) x ** yの場合)はなぜですか?

時間計算量O(1) of pow(x,y)であるのに、O(n) for x ** y

agfここ からのコメントを確認してください

7
Kumar Tanmay

文が間違っています。

  • pow**とほぼ同じです。
  • powおよび**は、引数が整数の場合、整数のべき乗を行います。 (Python 3は自動bignumサポートを備えているため、たとえば、a ** bは、aまたはbが非常に大きい場合でも、常に正確な整数結果を提供します。)これにはO(log(b))二乗による指数化を伴う乗算ですが、bignum乗算は一定の時間ではないため、時間計算量は使用する乗算アルゴリズムの詳細によって異なります(また、Pythonは指数関数をまったく使用しません)二乗することによって、しかしPythonが使用するものはまだO(log(b))乗算を取ります。)
  • 一方、math.powは異なります。常に浮動小数点のべき乗を行い、常にO(1)です。 O(1)複雑さは、powまたは**よりも効率的だからではありません。浮動小数点が精度と範囲を犠牲にしているためです。整数のべき乗の非定数の複雑さは実際に重要です。math.powははるかに精度の低い結果を与えるか、OverflowErrorをスローします。

詳細(Stack Overflowの otherquestions の確認、およびPython source)でのちょっとした突っ込みから):

  • powここ を参照)と**ここ を参照)は両方とも同じPyNumber_Power関数を呼び出します。実際には、**は、余分なシンボルルックアップと関数呼び出しのオーバーヘッドを回避するため、より高速になります。
  • pow/**の整数実装を見ることができます ここ
  • 一方、math.powは、常にCライブラリのpow関数を呼び出します。この関数は、常に浮動小数点演算を実行します。 ( ここ および ここ を参照してください。)これは多くの場合高速ですが、正確ではありません。 powを実装する1つの方法については、 ここ を参照してください。
  • 浮動小数点数の場合、pow/**はCライブラリのpow関数も呼び出すため、違いはありません。 ここ および ここ を参照してください。

これを自分で試してみたい場合は、これらのコマンドをIPythonセッションに貼り付けることができます。

import timeit

def show_timeit(command, setup):
    print(setup + '; ' + command + ':')
    print(timeit.timeit(command, setup))
    print()

# Comparing small integers
show_timeit('a ** b', 'a = 3; b = 4')
show_timeit('pow(a, b)', 'a = 3; b = 4')
show_timeit('math.pow(a, b)', 'import math; a = 3; b = 4')

# Compare large integers to demonstrate non-constant complexity
show_timeit('a ** b', 'a = 3; b = 400')
show_timeit('pow(a, b)', 'a = 3; b = 400')
show_timeit('math.pow(a, b)', 'import math; a = 3; b = 400')

# Compare floating point to demonstrate O(1) throughout
show_timeit('a ** b', 'a = 3.; b = 400.')
show_timeit('pow(a, b)', 'a = 3.; b = 400.')
show_timeit('math.pow(a, b)', 'import math; a = 3.; b = 400.')
12
Josh Kelley