簡體   English   中英

這些 Big-O 符號對於簡單的循環函數 (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)multiplyO(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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM