![](/img/trans.png)
[英]Are these Big-O notations correct for simple modulus and power functions (Python)?
[英]Are these Big-O notations correct for simple loop functions (Python)?
假設我們有以下兩個函數:
def is_prime(x):
"""Take an integer greater than 1 and check if it is a prime number."""
for i in range(2, int(x**0.5) + 1):
if x % i == 0:
return False
return True
def multiply(a, b):
"""Take two integers and compute their product."""
res = 0
for i in range(1, b + 1):
res += a
return res
說is_prime
的 Big-O 表示法是O(c)
而multiply
是O(n)
是否正確?
我有點困惑,因為我認為循環在復雜性方面至少是O(n)
,但是使用is_prime
function,它只需要一個輸入並計算一個結果,我認為它不會隨其大小而變化? 澄清贊賞以更好地理解簡單函數的 Big-O 表示法。
說 is_prime 的 Big-O 表示法是 O(c) 並且乘法是 O(n) 是否正確?
不。
讓我們采用第一個算法。
for i in range(2, int(x**0.5) + 1):
執行sqrt(x) - 1
次,因為for循環從2
開始。 所以復雜度是O(sqrt(x))
。
讓我們采用第二種算法。
for i in range(1, b + 1):
執行b
次。 所以復雜度是O(b)
如果不解釋“n”到底是什么以及你到底在數什么,就說“這是 O(n)”是沒有意義的。
您還需要指定您是在談論空間復雜度、步復雜度還是時間復雜度,以及您是在談論最佳情況、預期情況、平均情況、攤銷最壞情況還是最壞情況。
讓我們從頭開始。 通常,我們將復雜度表示為輸入長度的 function。 這很重要,因為這里的輸入是數字,數字的長度當然不是數字的值,而是位數。 但是,正如我們稍后將看到的,在這種特殊情況下,查看數字本身的值實際上更方便。 完全允許這樣做,我們只需要清楚地定義我們在談論的是什么。
另外,假設我們對步長復雜度(而不是時間復雜度或空間復雜度)感興趣並且我們對最壞情況感興趣。
最后,我們需要定義我們要計算的是什么,所以假設我們正在計算固定大小整數上的“原始操作”。 (其中“原始操作”是 +、-、*、/、%、&、|、^、~)。 我們將假設這些原始操作采取有限數量的步驟,並以某個常數為界。
好的,現在我們已經定義了我們感興趣的內容(最壞情況的步長復雜度)、我們正在計算的內容(對固定大小整數的原始操作)以及“n”是什么(輸入的值),我們可以 go並仔細研究算法。
在is_prime
中, for
循環在最壞的情況下迭代范圍從 2 到 floor(sqrt(x)+1) 的項目。 (這里最壞的情況是數字x
是素數。)因此,循環體執行到 floor(sqrt(x)) 次。
但是,我們還需要查看循環體的內容。 模運算a % b
的步長復雜度為 O(length(a))或 O(log_2 a)。
因此,總的來說, is_prime(x)
的最壞情況步復雜度是 O(floor(sqrt(x)) * log_2 x) 對固定大小整數的原始操作,或者簡化一點 O(sqrt(x) *日志 x)。
我們可以對multiply
進行類似的分析:循環執行b
次。 循環體由一個加法組成。 add(x, y) 的最壞情況步復雜度為 O(length(x + y))或 O(max(length(x), length(y))) 或 O(length(max(x, y))) 或 O(log_2(max(x, y)))。
因此,在循環的每次迭代中,成本為 O(log_2(max( res
, a
)))。 由於循環的第二次迭代后res
總是大於a
,我們可以將其簡化為 O(log_2( res
)) 或 O(log ( a
* i
))。
因此,總時間復雜度為 O(SUM[log_2( a
* i) over i from 1 to b
]), 根據 Wolfram Alpha為 O(log( a
b
* Pochhammer[1, b
])),其中Pochhammer 符號是上升階乘。
不幸的是,我真的不知道如何進一步簡化。 我們能做的就是退后一步,對循環體做一個最壞情況的假設:最壞的情況是最后一次迭代,其中res
= ( b
-1) * a
,因此加法大約需要 O(日志( b
* a
))。 那么我們可以說multiply
是 O( b
* log( b
* a
)) 或 O( b
* (log b
+ log a
)),我們知道這被高估了。
還要注意,到目前為止我們完全忽略了循環的每次迭代都有一個隱藏操作:我們需要分配數字i
。 分配數字是 O(length(i)) 或 O(log_2 i)。 我將把這個操作作為一個練習,但請注意,例如,對於is_prime
,它還涉及 Pochhammer 符號。
請注意,步驟復雜性如此復雜並不特別令人驚訝。 例如,如果您查看Wikipedia 上各種眾所周知的乘法算法的時間復雜度,您還會看到類似 O(n log 2k-1 / log k ) 的k-way Toom-Cook 乘法結果,其中 n 是數字較長數字的位數,k是算法的參數。
大O
對於第一個循環,正如我的評論O(sqrt(x))
中提到的那樣,因為您正在循環從某個范圍到最大sqrt(x)+c
的數字,其中 c 是一個常數。
由於循環中的內容不會影響循環過程,因此時間復雜度保持不變。
同樣,對於第二個,您從range(1,b+1)
循環,即O(b+1) = O(b)
。 b
增長得越多,您的算法實現其目的所需的時間就越多。 ( Linearity
)。
但是,例如,如果循環受到循環內發生的任何事情的影響,則復雜性可能會發生變化。
類似於第一個循環的簡單示例
b=50
for i in range(1,b):
if i> b**0.5:
break
else:
print(i)
基於條件的這個循環的復雜度是O(sqrt(b))
。 換句話說,不要只閱讀for...
行並假設其復雜性。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.