平攤分析(英語:Amortized analysis)在计算机科学中,是用於算法分析中的方法,平攤分析常用於分析資料結構(動態的資料結構),在使用平攤分析前須知道資料結構各種操作所可能發生的時間,並計算出最壞情况下的操作情況並加以平均,得到操作的平均耗费时間。平摊分析只能確保最坏情况性能的每次操作耗费的平均时间,並不能確認平均情况性能。
一個簡單的例子,一個長度為
的list,在list的最後要加入一筆新的資料此時要花費的操作時間為
,此時也是加入新的資料的最糟糕的情況。但是,一个
个插入的操作序列仍然可以在
的时间内完成,因为剩下的插入可以在常数时间内完成,因此
个插入可以在
的时间内完成。因此每操作的平摊耗费为
。
注意平摊分析与平均时间分析和概率算法的概率分析不同。在平均时间分析中,我们平均化所有可能的输入;在概率算法的概率分析中,我们平均化所有可能的随机选择;在平摊分析中,我们平均化一系列操作的耗费。平摊分析假设的是最坏情况输入并且通常不运行随机选择。[1]
平摊分析中几种常用的技术:
- 聚合分析决定
个操作序列的耗费上界
,然后计算平均耗费为
。[1]
- 记账方法确定每个操作的耗费,结合它的直接执行时间及它在对运行时中未来操作的影响。通常来说,许多短操作增量累加成「债」,而通过减少长操作的次数来「偿还」。[1]
- 势能方法类似记账方法,但通过预先储蓄「势能」而在需要的时候释放。[1]
聚集法(Aggregate method)
[编辑]
計算n個操作的時間複雜度上限T(n)
平攤T(n)至每一個操作,每一個操作的平攤成本是T(n)/n
記帳法(Accounting method)
[编辑]
執行花費較低的operations時先存credit未雨綢繆, 供未來花費較高的operations使用
對每個操作定義一個合法的平攤成本(amortized cost)
假設
為第i個操作的actual cost,
為第i個操作的amortized cost
若
,則credit=
,我們把credit存起來(deposited),未來可以提取(withdraw)
若
,則提取credit
設定每個操作的平攤成本(amortized cost)後,要做valid check確保credit不可以是0,也就是說
位能法(Potential method)
[编辑]
定義一個位能函數(potential function)
,將資料結構D(例如: 堆疊)的狀態對應到一個實數
: 資料結構D的初始狀態
: 資料結構D經過
個操作後的狀態
: 第
個操作的actual cost
: 第
個操作的amortized cost
定義
為了滿足
我們定義
, 通常令
和
我們定義一個堆疊有下列操作
操作(operation) |
說明 |
actual cost
|
S.push(x) |
將一個元素x放入堆疊S中 |
|
S.pop() |
把堆疊S中最上面的元素取出 |
|
S.multi-pop(k) |
一次pop k個元素 |
|
S.mult-pop(k)的程式碼如下
def multi_pop(k):
while (not S.empty()) and (k>0):
S.pop()
k -= 1
接下來我們分別使用聚集法(aggregate method), 記帳法(Accounting method), 位能法(Potential method)求出"堆疊一個操作的平攤成本是O(1)"
使用聚集法(aggregate method)分析堆疊操作的平攤成本
[编辑]
令
是S.push(x)的執行次數,
是S.pop()的執行次數,
是S.multi-pop(k)的執行次數
為總執行次數
操作(operation) |
actual cost |
執行次數
|
S.push(x) |
![{\displaystyle O(1)}](https://wikimedia.org/api/rest_v1/media/math/render/svg/e66384bc40452c5452f33563fe0e27e803b0cc21) |
|
S.pop() |
![{\displaystyle O(1)}](https://wikimedia.org/api/rest_v1/media/math/render/svg/e66384bc40452c5452f33563fe0e27e803b0cc21) |
|
S.multi-pop(k) |
![{\displaystyle O(\min(\left\vert S\right\vert ,k))}](https://wikimedia.org/api/rest_v1/media/math/render/svg/bd0b7560fd25b1314436a3922f3dfea26b46db56) |
|
因為一個堆疊S如果是空的,就不能執行pop了,也就是說可以pop或multi-pop的元素個數不會超過S中push進去的元素個數
所以
假設
是執行
個操作的時間複雜度上限
![{\displaystyle T(n)=O(1)\times n_{push}+O(1)\times n_{push}=O(n)}](https://wikimedia.org/api/rest_v1/media/math/render/svg/c20c2c9b484030f55e1e31b1c8d06076b828d4bf)
所以堆疊一個操作的平攤成本為
使用記帳法(Accouting method)分析堆疊操作的平攤成本
[编辑]
我們假設S.push(x), S.pop(), S.multi-pop(k)的amortized cost分別為2, 0, 0,如下表所示
操作(operation) |
actual cost ![{\displaystyle c_{i}}](https://wikimedia.org/api/rest_v1/media/math/render/svg/01acb7953ba52c2aa44264b5d0f8fd223aa178a2) |
amortized cost
|
S.push(x) |
1 |
2
|
S.pop() |
1 |
0
|
S.multi-pop(k) |
![{\displaystyle O(\min(\left\vert S\right\vert ,k))}](https://wikimedia.org/api/rest_v1/media/math/render/svg/bd0b7560fd25b1314436a3922f3dfea26b46db56) |
0
|
Valid Check
- 證明:
![{\displaystyle \sum _{k=1}^{n}{\hat {c_{i}}}\geq \sum _{k=1}^{n}c_{i}}](https://wikimedia.org/api/rest_v1/media/math/render/svg/56228c6be85dace676a2d35ad069ce658975d517)
|
- push進入堆疊S的元素會存入credit $1
- pop(S), multi-pop(S, k) 會取出這些元素的credit $1
|
因此每個操作的平攤成本是O(1)
使用位能法(potential method)分析堆疊操作的平攤成本
[编辑]
我們定義位能函數
為執行i個操作後,堆疊內的元素個數
,因為堆疊一開始是空的
,因為堆疊的元素個數一定![{\displaystyle \geq 0}](https://wikimedia.org/api/rest_v1/media/math/render/svg/da05245eb4e91f2f05daa9f0c42fd48975f9a690)
計算堆疊S每一個操作的平攤成本
操作(operation) |
平攤成本(amortized cost)
|
S.push(x) |
|
S.pop() |
|
S.multi-pop(k) |
|
總平攤成本
,所以堆疊單一個操作的平攤成本是
动态数组的平摊分析
考虑一个随元素个数增加而增长的动态数组,比如Java的ArrayList或者C++的std::vector。如果我们的数组大小从4开始,那么来向其中增加四个元素的时间就是一个常数。然而,若要将第五个元素加入其中,那么会花费更多时间,因为我们此时必须要创建一个两倍于当前数组大小的数组(8个元素),把老元素拷贝到新数组中,然后增加一个新元素。接下来的三次加入操作也同样会花费常数时间,然后在数组被填满后则又需要一轮新的加倍扩充。
一般地,如果我们考虑任意一个任意的n大小的数组并对其进行n + 1次加入操作。我们注意到,所有的加入操作都是常数时间的,除了最后一个,它会花费
时间在大小加倍上。因为我们进行了n + 1次加入操作,我们可以将数组加倍的时间平摊到所有的加入操作上,因此得到加入操作的平均时间是
。它是一个常数。[1]
使用Ruby實現的佇列,一个先進先出資料結構:
class Queue
def initialize
@input = []
@output = []
end
def enqueue(element)
@input << element
end
def dequeue
if @output.empty?
while @input.any?
@output << @input.pop
end
end
@output.pop
end
end
佇列操作及特性參考佇列條目,enqeue及deqeue操作時間複雜度為常數,
否則,dequeue需要
時間將所有元素從輸入數組添加到輸出數組中,其中n是輸入數組的當前長度。 從輸入複製n元素後,我們可以在輸出數組再次為空之前執行n出隊操作,每次操作都需要一個恆定的時間。 因此,我們可以僅在
時間執行一系列n出列操作,這意味著每個出列操作的攤銷時間是
。[2]
或者,我們可以收取將任何項目從輸入數組複製到輸出數組的成本,以及該項目的早期排隊操作。 該計費方案將入隊的攤還時間加倍,但將出列的攤還時間減少到
。
- 在常见场合,我们把能很好平摊分析的算法称为“平摊算法”。
- 在线算法通常使用平摊分析。
[3]
- ^ 1.0 1.1 1.2 1.3 1.4 Kozen, Dexter. CS 3110 Lecture 20: Amortized Analysis. Cornell University. Spring 2011 [14 March 2015]. (原始内容存档于2018-10-03).
- ^ Grossman, Dan. CSE332:Data Abstractions (PDF). cs.washington.edu. [2015年3月14日]. (原始内容存档 (PDF)于2015年4月2日).
- ^ MIT 6.046J Design and Analysis of Algorithms, Spring 2015. MIT. [2018-10-21]. (原始内容存档于2018-11-25).