[算法Tutorial]Amortized Analysis,平攤分析


對於一個操作的序列來講,平攤分析得出的是在特定問題中這個序列下每個操作的平攤開銷。

一個操作序列中,可能存在一、兩個開銷比較大的操作,在一般地分析下,如果割裂了各個操作的相關性或忽視問題的具體條件,那么操作序列的開銷分析結果就可能會不夠緊確,導致對於操作序列的性能做出不准確的判斷。用平攤分析就可以得出更好的、更有實踐指導意義的結果。因為這個操作序列中各個操作可能會是相互制約的,所以開銷很大的那一兩個操作,在操作序列總開銷中的貢獻也會被削弱和限制。所以最終會發現,對於序列來講,每個操作平攤的開銷是比較小的。

換句話說,對於一個操作序列來講,平攤分析得出的是這個序列下每個操作的平攤開銷。

基本公式為 amortized cost = actual cost + accounting cost,我們記為$\hat{c}_i = c_i + a_i$

這里,$\forall n, \sum_{i=0}^{n}a_i \geq 0$

 


 

1. Stack

這里的操作有push,pop和multipop($k$) —— 彈出棧頂的$k$個元素,顯然,直觀上來說,push和簡單pop的操作都是高效的,即其性能都在$O(1)$的樣子,也就是說,他們的Actual Cost都是1。

而multipop則不同,它每次可能會要求彈出至多$n$個元素,如果進行長度為n的操作序列都進行這樣的操作,那么最壞情況下的復雜度可以達到$O(n^2)$。這是一個不太緊的上界。

我們發現,由於棧中每次pop元素都是建立在push的操作次數的基礎上,所有的pop操作受到push操作的限制。每個元素進棧的同時,都隱含着一次出棧的“未來”,所以,push操作最多能夠push共$O(n)$個元素,那么pop的次數也至多為$O(n)$。而每個push、pop的代價均為$O(1)$,所以整個棧上的數據操作的復雜度上界可以是$O(n)$!

說得生動而形象一點,我們用上面的公式來記賬。

 

操作 Amortized Cost Actual Cost Accounting Cost
push 2 1 1
pop 0 1 -1
multipop 0 $min(k, s)$,$s$為當時棧中的元素個數。 -$min(k, s)$

 

用錢來說,因為Amortized本身有“分期償還”的意思呀~

假設你每次在進行push操作的時候,都花2塊錢,那么你在push的時候,只花了1塊,還有1塊錢就作為存款了。每當你進行pop操作,或者是multipop操作,我們不再需要花錢啦!!每次出棧一個元素就從存款中扣掉1塊錢,而每時每刻棧中剩余元素個數都大於等於0的啦,所以存款的總和不會為負,$\forall n, \sum_{i=0}^{n}a_i \geq 0$ 自然成立。

  


 

 2.Doubling Array


就是一開始分配一定量的內存A,如果在向這個內存空間插入對象時發現空間不足的話,就重新分配一塊比原來大的內存B,我們就認為$size(B)=2size(A)$,把原內存A中所有的對象都復制到內存B中,這時候的代價為$O(n)$然后把新加入的對象增插入到B的空余位置中,最后把內存A釋放掉。呵呵,記得內存泄漏!!

乍一想,可能每次插入都可能要再malloc一塊空間,代價就是$O(n)$啊,那么$n$次操作復雜度就是$O(n^2)$啊!So terrible!!

好了,平攤分析會告訴你上界是$O(n)$

我們假設一開始這個表是空的,然后向這個表中依次插入n個元素,想一想,就會發現,這個操作序列中是不可能連續$n$次出現最壞性能的(怎么可能每次插入都要double空間呢?),甚至說,出現最壞性能的機會是比較小的,僅在第2的冪的數。除此以外,直接插入就是了,不需要額外申請空間,代價僅僅為$O(1)$。

所以,我們有Actual Cost

$$c_i = \left\{
\begin{array}{ll}
1 & i \neq 2^x,x \in N \\
i+1 & i=2^x,x \in N
\end{array}
\right.$$

直接對$c_i$求和,我們可以得到
$$\sum_{i=0}^{n} c_i=n+\sum_{i=0}^{\log n} 2^i \leq n + 2n = 3n$$
可見,如果平攤的話,每個操作的平均cost為3!所以這樣的一個操作,上界是$O(n)$!

那么為什么不是2?我們助教也是很認真負責噠~如果每個數插入的時候,插入花了1,那么在一次double的時候,另一塊錢就被用完了,以后插入要double的時候,就沒有存款啦!所以,當代價為3的時候,還有兩塊錢的存款,一塊錢用於double時自己的花費,另一塊錢就是給它前面的所有的已經沒有存款的元素用啦~~好心人吶!!!

  


 

 3. Binary Counting

這是一個二進制計數器,用一個$k$-bit的數進行計數,就像當年數字邏輯電路的面包板上的那個玩意兒...基本的置位set操作和復位reset操作本身的代價都為$O(1)$。

直觀上,最壞情況每個bit位都要發生改變,代價就是$O(k)$,$n$次計數操作就是$O(kn)$。

可是,平攤分析告訴你,上界是$O(n)$,你應該已經習以為常了吧~~
假設一個8-bit的數從高位到低位分別存入數組$A[7..0]$中用於計數,那么每次計數加1,最低位$A[0]$都需要置位或者是復位。容易知道,$A[1]$每+2置位或復位,$A[2]$每+4置位或復位,$\cdots$,更一般地,$A[i]$每+$2^{i}$都要置位或者是復位,那么該bit就發生變化,所以第$i$位發生set或reset的次數總共為$n/2^i$

對這個通項$n/2^i$求和,值為$n$那么最多也就是($\log n+1$)個bit就可以表示
$$\sum_{i=0}^{\log n}\frac{n}{2^i}<n\sum_{i=0}^{\infty}\frac{1}{2^i}=2n$$

下面我們來記賬:

操作 Amortized Cost Actual Cost Accounting Cost
Set 2 1 1
Reset 0 1 -1

 


 

 

 4. Two Stacks, One Queue

用兩個棧來實現隊列!我們知道棧是先進后出,而隊列是先進先出。我們可以用兩個棧來實現隊列。方法如下:
如果是Enque操作,就把元素push到棧S1中,如果是Deque操作,則分為兩種情況

 

$$\left\{
\begin{array}{ll}
popall~S1, pushall~S2, pop~S2 & S2=\emptyset \\
pop~S2 & S2\neq \emptyset
\end{array}
\right.$$

 

我們假定基本的pop和push操作、以及判斷是否棧為空的代價均為$O(1)$

所以,比較簡單的Deque的Actual Cost為$1+1=2$,而比較復雜的Deque操作,則實際代價為$1+2t$,$t$為棧S1中的元素個數。
下面,我們進行平攤分析,這里的內在聯系是,我們在push的時候,先存款2,用於將這個元素從S1中pop,再push到S2中去的代價,這樣我們的兩個Deque操作的代價就都是2了,只要S1中有元素,我們的存款就是非負的,記賬如下

操作 Amortized Cost Actual Cost Accounting Cost
Enqueue 3 1 2
Dequeue(normal)  2 2 0
Dequeue(complex) 2 2+2t -2t

 


 

 5. Two Queues, One Stack

 

這是偽5,用兩個隊列來實現棧。情況略比上一種情況復雜些。實現方法:每次在進行pop操作的時候,要把隊列中除最后一個元素外都轉移到另一個隊列中,然后再進行Deque來模擬pop。總是能保證一個隊列為空,另一個隊列非空。我們規定判斷是否空隊列的代價為$O(1)$,基本的Deque和Enque操作也都為$O(1)$

push操作:
如果兩個隊列都空,則Q1.Enque();
如果Q1非空但Q2空,則Q1.Enque();
如果Q1空但Q2非空,則Q2.Enque();

pop操作:
如果兩個隊列都空,則返回false;
如果Q1非空但Q2空,則循環執行Q2.Enque(Q1.Deque())一直到Q1中只剩下一個元素,此時返回Q1.Deque()的值,即為最后進入Q1的元素;執行之后Q1變為空,Q2非空(如果之前Q1中不止一個元素的話)。
如果Q1空但Q2非空,則循環執行Q1.Enque(Q2.Deque())一直到Q2中只剩下一個元素,此時返回Q2.Deque()的值,即為最后進入Q2的元素;執行之后Q2變為空,Q1非空(如果之前Q2中不止一個元素的話)。

算法分析:我們每次pop的時候最壞情況下都需要移動隊列,每次的代價都是$n\times O(1)=O(n)$,所以我們的復雜度為$O(n)$;下面我們看看平攤分析的結果,假設非空隊列中有$t$個元素:

push的Actual Cost為$2=1+1=$判空+Enque;

pop的Actual Cost為$2t=1+2(t-1)+1=$判空+轉移+Deque;

每次的push的Accounting Cost都是$2n-2$,這樣的話,我倒是覺得其實沒有這么多啊,如果遇到pop操作的話,只需要存下$2t-2$這樣的存款,這樣pop操作的Amortized Cost就是0了,但是這樣似乎失去了平攤分析的意義了啊。。。所以我覺得還是存下$2n-2$的存款吧,每個元素在pop的時候最多轉移$n-1$次,所以最終push的Amortized Cost就是$n$,最后的復雜度為$O(n^2)$,顯然不高效啊!!

偽5說的就是這個意思,是我大概隨便寫寫的。也不知道這樣分析對不對。。。


 

<下期預告: Adversary Argument >


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM