(最壞)時間復雜度((worst case) time complexity)
當談到時間復雜度(time complexity)時,我們最常用的,無明確說明默認使用的就是最壞時間復雜度(worst case time complexity)。
最壞時間復雜度考慮的是在各種數據輸入情況下,一個算法所能表現出的最差執行時間界限。因為算法復雜度分析理論確保在任何情況下,算法的執行時間不會越過這個界限。所以在實際使用時,可以利用(最壞)時間復雜度對一個使用算法的程序做運行時間估計,確保在一定數據規模下,算法運行的時間不會超出我們的預期。
這是一個非常可靠的結果,因此(最壞)時間復雜度也成了我們最常用,也最好用的時間復雜度。
均攤(攤余)時間復雜度(amortized time complexity)
然而一些(最壞)時間復雜度表現不佳的算法,在實際應用中卻依然有不錯的表現。
比如在伸展樹(Splay tree)數據結構下的查找(find)-插入(insert)-刪除(delete)操作的(最壞)時間復雜度均為O(n),但實際表現並不遜色於時間復雜度為O(logn)的其他查找樹。而且伸展樹的實現和維護相對簡單,因此也有一定的實用價值。
這種類型算法的特點是:雖然單次操作的時間復雜度較高,但是單次操作后會對整個數據的組成狀況帶來一定的改進,因而后續的的系列操作時間復雜度會下降。這樣最壞時間復雜度就不會在系列操作中連續出現,也就使得算法的實際運行時間並沒有那么糟糕。
借用一個術語,可以把這種類型算法的一個特點看做是“late(lazy) operation”。以Splay tree為例,影響查找樹操作效率的關鍵因素是樹各分枝的平衡。“平衡”的樹結構操作時間復雜度較低,因而AVL tree 或 Red Black tree 都會在每次操作后對樹的平衡情況進行調整,使得各個操作的時間復雜度能達到O(logn)。而Splay tree並不主動調整樹的平衡,所以特殊情況下(尤其是在一些初始狀態下)樹可能會極不平衡,使得單次最壞時間復雜度到了O(n)。但是Splay tree在后續的操作中不斷的 splay 能使樹更加平衡,這樣系列操作的總時間復雜度就不高了。這里的“late operation” 就是“late balance”,並不在每次操作后馬上就對樹結構進行平衡,而是在后續操作中根據需要逐漸balance。
(上圖中一個極不平衡的Splay樹,執行一次查找操作后樹的高度就減小了,變得更加平衡了。其中的秘訣在於splay使用的雙旋轉(zig-zag)能降低樹高度。)
另外一個例子是Dynamic array,也就是變長數列。這種數列的一個實現方式是,依然采用定長數列,只是當數列中數據個數大於或小於某個界限時,重新申請一塊空間來生成一個改變了長度(擴大或縮小)的數列,然后再把原數列的數據拷貝過去。這個算法的(最壞)時間復雜度為O(n),發生在需要重新生成新數列的時候。但這種情況並不會一直發生,只在數列中存儲的數據規模增大或減小到一定規模時才發生,因而系列操作的時間復雜度並不高,實際使用效果也很好。這里的“late operation” 就是“late resizing array lenth”,有需要時再調整數列的儲存空間。
對這類算法再以傳統方式進行(最壞)時間復雜度分析明顯不能反映算法的真實表現情況,因而研究者們采用均攤(攤余)時間復雜度(amortized time complexity)來處理這種情況。對均攤時間復雜度的簡單理解就是把單次操作的復雜度平攤到一系列操作上,如Splay tree就是n次操作的復雜度為O(logn),單次均攤下來就是O(logn)。Dynamic array則是n次操作的復雜度為O(n),單次均攤下來就是O(1)。
均攤時間復雜度的分析方法被稱為potential method,勢能法。就是為數據的組成狀況生成一個勢能函數,通過勢能函數來分析一系列操作的總體時間復雜度。一個簡單的比方是重力勢能函數,假如我們站在10樓,往下跳一次最多能跳10樓,但是如果跳10次,每次跳下來不往上爬的話,那這10次下來無論怎樣跳一共也只能跳10樓的距離,這樣均攤下來,跳一次實際上也就只跳了一層樓的高度。
再說一句,amortized均攤這個詞來源於會計術語,指的也就是把單次經濟活動的結果(借入或貸出)平攤到一段會計時間的做法。比方說我在618剁了手,但如果平攤到5,6,7三個月來看的話,可能每次我就只剁了一個手指頭。。。而且一般情況下,無論我們剁幾次手,一共也只有兩只手可以剁。。。
期望時間復雜度(expected time complexity)
有些時候,我們還會發現,一些系列操作時間復雜度高的算法,也可能會有很好的運行效果。
這類算法往往就是隨機性算法(Randonmized Algorithm),其中就有大名鼎鼎的快速(選擇)排序(quick sort)與快速選擇(quick select)。他們的特點是在特殊情況下,最壞時間復雜度和均攤時間復雜度(均攤時間復雜度也是最壞均攤時間復雜度,考慮的是系列操作最壞的情況)都不佳,就是說單次操作和系列操作都可能花費很長時間。但是這種特殊情況出現的概率非常低,以至於在實際運行中幾乎不可能出現。而在一般情況下,算法又表現得非常好。。。
為了合理評價這類算法,研究人員采用了期望時間復雜度(expected time complexity),也可以說是(加權)平均時間復雜度((weighted)average time complexity)。就是說一定數據規模下,我們計算出各種數據情況出現的可能性以及相應的算法時間復雜度,將可能性和復雜度相乘后求和,就得到了期望的時間復雜度。這里的可能性如果都取相同,那就是平均時間復雜度。當然,可能性也可以根據不同的算法應用情況來確定,這樣有時會得到不同的期望時間復雜度。
總的來說
(最壞)時間復雜度指的是算法單次運行可能出現的最長時間,也是最常用的時間復雜度,每次操作一定能滿足。
均攤(攤余)時間復雜度指的是系列操作下最長運行時間平攤到單次操作下的均值,系列操作下的總時間一定能滿足。
期望時間復雜度指的是各種數據輸入下算法的運行時間按概率求得的一個平均理想值,在大多情況下能滿足(實際也夠用了)。。。
表面上看這幾個復雜度的嚴格程度是一個不如一個,但實際上這是在計算機從理論走向實際大規模應用的發展。一個算法好,理論分析上專家要說好,實際應用中群眾也要說好,而實際的應用場景往往會和嚴格理論分析下的假定有所差別。
但如果你要嚴格問我當提到一個算法的時間復雜度指的是哪個復雜度的時候,我只能說往往是表現最好的那個復雜度:)