平攤分析在計算機科學中，是用於演算法分析中的方法，平攤分析常用於分析資料結構（動態的資料結構），在使用平攤分析前須知道資料結構各種操作所可能發生的時間，並計算出最壞情況下的操作情況並加以平均，得到操作的平均耗費時間。平攤分析只能確保最壞情況效能的每次操作耗費的平均時間，並不能確認平均情況效能。 一個簡單的例子，一個長度為 n 的 list，在 list 的最後要加入一筆新的資料此時要花費的操作時間為 O n，此時也是加入新的資料的最糟糕的情況。但是，一個 n 個插入的操作序列仍然可以在 O n 的時間內完成，因為剩下的插入可以在常數時間內完成，因此 n 個插入可以在 O n 的時間內完成。因此每操作的平攤耗費為 O n n O 1。 注意平攤分析與平均時間分析和機率演算法的機率分析不同。在平均時間分析中，我們平均化所有可能的輸入；在機率演算法的機率分析中，我們平均化所有可能的隨機選擇；在平攤分析中，我們平均化一系列操作的耗費。平攤分析假設的是最壞情況輸入並且通常不執行隨機選擇。 平攤分析中幾種常用的技術 聚合分析決定 n 個操作序列的耗費上界 T n，然後計算平均耗費為 T n n。 記賬方法確定每個操作的耗費，結合它的直接執行時間及它在對執行時中未來操作的影響。通常來說，許多短操作增量累加成「債」，而透過減少長操作的次數來「償還」。 勢能方法類似記賬方法，但透過預先儲蓄「勢能」而在需要的時候釋放。 平攤分析種類 聚集法 Aggregate method 計算 n 個操作的時間複雜度上限 T n 平攤 T n 至每一個操作，每一個操作的平攤成本是 T nn 記帳法 Accounting method 執行花費較低的 operations 時先存 credit 未雨綢繆，供未來花費較高的 operations 使用 對每個操作定義一個合法的平攤成本 amortized cost 假設 c_i 為第 i 個操作的 actual cost，hat c_i 為第 i 個操作的 amortized cost 若 c_ihat c_i，則 credithat c_ic_i，我們把 credit 存起來 deposited，未來可以提取 withdraw 若 c_ihat c_i，則提取 credit 設定每個操作的平攤成本 amortized cost 後，要做 valid check 確保 credit 不可以是 0，也就是說 sum_k1n hat c_i ge sum_k1n c_i 位能法 Potential method 定義一個位能函式 potential function Phi D ，將資料結構 D 例如：堆疊 的狀態對應到一個實數 D_0 資料結構 D 的初始狀態 D_i 資料結構 D 經過 i 個操作後的狀態 c_i 第 i 個操作的 actual cost hat c_i 第 i 個操作的 amortized cost 定義 hat c_ic_iPhi D_iPhi D_i1 sum_k1n hat c_isum_k1n c_iPhi D_iPhi D_i1 sum_k1n c_iPhi D_nPhi D_0 為了滿足 sum_k1n hat c_i ge sum_k1n c_i 我們定義 Phi D_nPhi D_0 ge 0 通常令 Phi D_00 和 Phi D_n ge 0 例子 堆疊 stack 的平攤分析 我們定義一個堆疊有下列操作 操作 operation 說明 actual cost Spush x 將一個元素 x 放入堆疊 S 中 O 1 Spop 把堆疊 S 中最上面的元素取出 O 1 Smultipop k 一次 pop k 個元素 O min leftvert S rightvert k Smultpop k 的程式碼如下 def multi_pop k while not Sempty and k0 Spop k 1 接下來我們分別使用聚集法 aggregate method 記帳法 Accounting method 位能法 Potential method 求出 堆疊一個操作的平攤成本是 O 1 使用聚集法 aggregate method 分析堆疊操作的平攤成本 令 n_push 是 Spush x 的執行次數， n_pop 是 Spop 的執行次數， n_multipop 是 Smultipop k 的執行次數 nn_pushn_popn_multipop 為總執行次數 操作 operation actual cost 執行次數 Spush x O 1 n_push Spop O 1 n_pop Smultipop k O min leftvert S rightvert k n_multipop 因為一個堆疊 S 如果是空的，就不能執行 pop 了，也就是說可以 pop 或 multipop 的元素個數不會超過 S 中 push 進去的元素個數 所以 n_popn_multipop le n_push 假設 T n 是執行 n 個操作的時間複雜度上限 T n O 1times n_pushO 1times n_pushO n 所以堆疊一個操作的平攤成本為 O nnO 1 使用記帳法 Accouting method 分析堆疊操作的平攤成本 我們假設 Spush x Spop Smultipop k 的 amortized cost 分別為 2 0 0，如下表所示 操作 operation actual cost c_i amortized cost hat c_i Spush x 1 2 Spop 1 0 Smultipop k O min leftvert S rightvert k 0 Valid Check 證明 sum_k1n hat c_i ge sum_k1n c_i color Red proof push 進入堆疊 S 的元素會存入 credit 1 pop S multipop S k 會取出這些元素的 credit 1 因此每個操作的平攤成本是 O 1 使用位能法 potential method 分析堆疊操作的平攤成本 我們定義位能函式 Phi D 為執行 i 個操作後，堆疊內的元素個數 Phi D_00 ，因為堆疊一開始是空的 Phi D_ige 0 ，因為堆疊的元素個數一定 ge 0 計算堆疊 S 每一個操作的平攤成本 操作 operation 平攤成本 amortized cost hat c_i Spush x hat c_ic_iPhi D_iPhi D_i11leftvert S rightvert1leftvert S rightvert2 Spop hat c_ic_iPhi D_iPhi D_i11leftvert S1 rightvertleftvert S rightvert0 Smultipop k hat c_ic_iPhi D_iPhi D_i1kleftvert Sk rightvertleftvert S rightvertkk0 總平攤成本 sum_k1n hat c_i 2times n_push O n，所以堆疊單一個操作的平攤成本是 O nnO 1 動態陣列 考慮一個隨元素個數增加而增長的動態陣列，比如 Java 的 ArrayList 或者 C 的 stdvector。如果我們的陣列大小從 4 開始，那麼來向其中增加四個元素的時間就是一個常數。然而，若要將第五個元素加入其中，那麼會花費更多時間，因為我們此時必須要建立一個兩倍於當前陣列大小的陣列（8 個元素），把老元素複製到新陣列中，然後增加一個新元素。接下來的三次加入操作也同樣會花費常數時間，然後在陣列被填滿後則又需要一輪新的加倍擴充。 一般地，如果我們考慮任意一個任意的 n 大小的陣列並對其進行 n 1 次加入操作。我們注意到，所有的加入操作都是常數時間的，除了最後一個，它會花費時間在大小加倍上。因為我們進行了 n 1 次加入操作，我們可以將陣列加倍的時間平攤到所有的加入操作上，因此得到加入操作的平均時間是 tfrac nO 1O nn1 O 1。它是一個常數。 佇列 使用 Ruby 實現的佇列，一個先進先出資料結構 class Queue def initialize input output end def enqueue element input element end def dequeue if outputempty while inputany output inputpop end end outputpop end end 佇列操作及特性參考佇列條目，enqeue 及 deqeue 操作時間複雜度為常數， 否則，dequeue 需要時間將所有元素從輸入陣列新增到輸出陣列中，其中 n 是輸入陣列的當前長度。 從輸入複製 n 元素後，我們可以在輸出陣列再次為空之前執行 n 出隊操作，每次操作都需要一個恆定的時間。 因此，我們可以僅在時間執行一系列 n 出列操作，這意味著每個出列操作的攤銷時間是 。 或者，我們可以收取將任何專案從輸入陣列複製到輸出陣列的成本，以及該專案的早期排隊操作。 該計費方案將入隊的攤還時間加倍，但將出列的攤還時間減少到。 通常用法 在常見場合，我們把能很好平攤分析的演算法稱為 平攤演算法。 線上演算法通常使用平攤分析。 參考資料 Category 演算法分析