前言
整理了一些長相清奇的時間複雜度分析……
約定:
1.如果沒有特殊說明,默認T(0)=T(1)=1
2.如果沒有特殊說明,默認log的底數爲2。
3.一些地方會說T(n)=O(...),意會就好qwq
0.一些定義
0.1 大O記號定義
對於函數f,g,若∃常數n0,c,使得對於∀n>n0,f(n)<c∗g(n),那麼說f(n)是O(g(n))的,即f(n)∈O(g(n))。
0.2 時間複雜度定義
對於一個程序所需要執行的運算次數T(n),若T(n)∈O(f(n)),則把O(f(n))稱爲這個程序的時間複雜度。
例如當T(n)=31n3+100n+10086時,我們說這個程序的時間複雜度是O(n3)。證明是顯然的,隨便取個n0=100,c=100就可以了,不多說。
當然,上面的T(n)也可以說是O(n4)的,這並不矛盾。一般來說,我們用的時間複雜度是增長速度儘量慢的,也就是說取儘量貼近實際的T(n)的增長速度的複雜度。所以一般我們用O(n3)而不是O(n4)來表示上面的T(n)。
再舉幾個例子:
T(n)=n+logn,那麼T(n)是O(n)的。對於n>10,n+logn<2n。
T(n)=2n+logn=n2n,T(n)是O(n2n)的。注意這裏不能從n+logn是O(n)的推出2n+logn是2n的,要注意。至於爲什麼這個不是O(2n),因爲n0和c必須是常數。
1.基本時間複雜度分析
1.0 主定理
對於遞歸函數,如果滿足T(n)=aT(bn)+f(n),分類討論:
假設c是一個正常數。
(1)若f(n)=nlogba,那麼T(n)=O(nlogbalogn)
(2)若f(n)=nlogba−c,那麼T(n)=O(nlogba)
(3)若f(n)=nlogba+c,且對於所有足夠大的n有af(bn)<f(n),那麼T(n)=O(f(n))
1.1 線性遞推式
1.T(n)=T(n−1)+c(c是常數),則T(n)=O(cn)
2.T(n)=T(n−1)+n,則T(n)=O(n2)
奇水……
1.2 普通分治
T(n)=2T(2n)+n,則T(n)=O(nlogn)
證明:展開,T(n)=O(n)+2T(2n)
=O(n)+2∗O(2n)+4T(4n)
=...
=O(n)+O(n)+...+O(n) (每次除以2,除logn次到1,所以共logn個O(n))
=O(nlogn)
主定理版:a=2,b=2,logba=log22=1
f(n)=O(n1)=O(nlogba)(打不出中間有一橫的符號……意會一下qwq)
=>T(n)=O(f(n)∗logn)=O(nlogn)
1.3 詭異的分治(也許這不是分治……)
T(n)=2T(2n)+k,則T(n)=O(nk)
證明:展開,T(n)=O(k)+2T(2n)
=O(k)+2O(k)+4T(4n)
=...
=O(k)+2O(k)+4O(k)+...+2t−1O(k)+2tT(1) (爲了方便,記t=logn)
=(2t−1)O(k)+O(2t+1)
=O(nk)+O(2n)
=O(nk)
主定理:????
1.4 玄學分治
T(n)=2T(2n)+nlogn,則T(n)=O(nlog2n)
證明:考慮T(2n)
T(2n)=2T(2n−1)+2n∗n
=2(2T(2n−2)+2n−1∗(n−1))+2n∗n
=4T(2n−2)+2n∗(n−1)+2n∗n
=...
=O(2n)∗1+O(2n)∗2+...+O(2n)∗n
=O(2nn2)
然後T(n)=T(2logn)=O(2logn(logn)2)=O(nlog2n)
之前嘗試用數學歸納法……發現竟然弄出普通分治也是O(nlog2n)……然後才發現那樣用數學歸納法是不嚴謹的……
主定理版:a=2,b=2,logba=log22=1
f(n)=O(nlogn)=O(nlogbalogn)
=>T(n)=O(f(n)∗logn)=O(nlog2n)
2.長相奇特的遞歸式
2.1 巨神mhy弄出的奇怪東西
有一次巨神mhy在CF比賽的時候弄出一個T(n)=2T(n)+logn的東西……
那麼T(n)=O(logn∗loglogn)
證明:上式不好直接證,考慮它的等價形式:
因爲2logn=n,所以上式等價於T(2n)=O(nlogn)
由已知,T(2n)=2T(2n)+O(log(2n))
化簡,T(2n)=2T(22n)+O(n)
發現n每次都變成2n,所以類比普通分治的證明,T(2n)=O(nlogn)
於是T(n)=O(lognloglogn)
主定理:????
2.2 一道詭異的初賽題
給出式子T(n)=2T(4n)+n
感覺比上面的那一個還水……思路很自然qwq
同樣考慮T(4n)=2T(4n−1)+4n
=2(2T(4n−2)+4n−1)+2n
=4T(4n−2)+2∗4n−1+2n
=4T(4n−2)+2n+2n
=...
=2nT(1)+2n+2n+...+2n (共n個2n)
=O(2n∗n)
然後T(n)=T(4log4n)=O(2log4nlog4n)
因爲4log4n=n,所以2log4n=n,而log4n與logn同級
所以T(n)=O(nlogn)
主定理版:a=2,b=4,logba=log42=0.5
f(n)=O(n)=O(n21)=O(nlogba)
=>T(n)=O(f(n)∗logn)=O(nlogn)
2.3 惡臭學軍題
T(n)=25T(5n)+nn
考慮T(5n)=25T(5n−1)+5n5n
=252T(5n−2)+25∗5n−15n−1+5n∗sqrt5n
=...
=25nT(1)+25n−155+25n−22525+...+5n5n
=54n+54n−1+54n−2+...+53n
=5−154n+1−53n (等比數列求和)
=5−15∗52n−5n5n
把5n換成n,得T(n)=5−15n2−nn=O(n2)
主定理版:a=25,b=5,f(n)=O(nn)=O(n1.5)
logba=log525=2
f(n)=O(n1.5)=O(nlogba−0.5)
=>T(n)=O(nlogba)=O(n2)
2.4 證明主定理
3.攤還分析
注:爲了方便,部分內容裏寫了O(0)qwq(不嚴謹,但是是這個意思)
3.1 進棧1個/出棧k個
你需要維護一個棧,初始爲空,要求資磁兩種操作:
1.把x壓進棧
2.彈出k個元素(k≤ 棧大小)
總共q中操作,棧大小<=n
暴力即珂……時間複雜度?1操作O(1),2操作O(k)
時間複雜度O(qk)=O(nq)?
口胡出時間複雜度爲線性qwq
發現每次彈出k個元素,k的範圍是與棧大小,也就是有多少個x沒被彈出的1操作有關。
覈算法:(覈算法大法吼!)
每一個1操作,以後最多把這個元素彈出一次(廢話qwq)
記1操作的攤還代價爲1,則2操作的複雜度變成O(0)
然後1操作的總代價爲O(1),共q次操作,時間複雜度O(q)
聚合分析:(聚合分析也還珂以)
發現如果1操作有x個,那麼2操作的總時間複雜度最高爲O(x)
所以考慮最壞情況,1操作有q個時,2操作的總時間複雜度爲O(q)
所以總時間複雜度爲O(q)+O(q)=O(q)
勢能法:(惡臭)
勢能法蜃孫……
令T(i,勢能)表示前i次操作珂能達到的最高複雜度(即勢能qwq),T(i,實際)表示前i次操作實際達到的複雜度。
令f(i)表示執行了第i個操作後的勢能
勢能公式:T(i)=T(i,實際)+f(i)−f(i−1)
即:第i步操作的(最後平均)總代價等於第i步操作的實際代價加上從第i−1步操作到第i步操作的勢能變化。
回到例子,讓勢能=棧中有多少個元素,那麼1操作會讓勢能+1,2操作會讓勢能-k。
於是1操作的總代價爲1+1=2,2操作總代價爲k−k=0
因此1操作總複雜度爲O(1),2操作總複雜度爲O(0)
最後的總複雜度爲O(q)
惡臭無比……
3.2 bzoj3133 ball machine複雜度分析
推銷一波題解
懶得寫了……和3.1沒多大差別……去題解裏看吧qwq
3.3 表擴張(vector的push_back?)
維護一個數組,初始大小爲1,要求資磁插入一個數,當數組滿了的時候,開一個大小爲原來的兩倍的數組,把當前的數據複製過去。(規定單次擴容複雜度爲O(擴容前數組大小))
每一次最壞複雜度爲O(n)……時間複雜度O(n2)?
一共logn次擴容,每次O(n),時間複雜度O(nlogn)?
然而並不是qwq
覈算法:(蜃香)
每次把數組擴大到原來的兩倍的時候,這O(n)的複雜度均攤到每個插入操作上qwq
於是每個插入操作的攤還代價爲O(1),擴容操作變爲O(0)
插入操作總複雜度是O(1),所以最後總複雜度O(n)
聚合分析:
假設最後數組大小爲n,則擴容了logn次
擴容複雜度爲O(1)+O(2)+O(4)+...+O(2logn)=O(n)
插入複雜度爲O(1)∗n=O(n)
所以總複雜度爲O(n)
勢能法:(惡臭)
還沒想到什麼直觀的勢能函數……但是想到一個詭異的東西不知道是否正確……口胡一波qwq
令1操作的勢能變化爲1(意義大概是在擴容的道路上走了多遠?),那麼2操作的勢能變化爲-n。
口胡得出1操作總代價爲O(1),2操作總代價爲O(0)
於是總複雜度爲O(n)
//兩條分割線以表敬意
令勢能函數f(i)=2num−siz,其中num表示數組內元素的個數,siz表示數組大小。
易證若一個插入操作沒有觸發擴容,引起的勢能變化是2。攤還代價爲O(1)。
若某次插入操作引起了擴容,假設插入前的數組大小是n,那麼裏面的元素個數也是n。
擴容前:勢能f=2num−siz=2n−n=n。
擴容後:勢能f=2num−siz=2(n+1)−2n=2。
所以勢能變化爲2−n,那麼引起擴容操作的插入操作的攤還代價爲n+(2−n)=O(1)
綜上,插入操作的攤還複雜度爲O(1)。
不得不說,勢能函數還是很妙的……
3.4 表擴張&刪除(push_back,pop_back)
//咕咕咕
問:如果你需要維護一個數據結構,需要資磁在尾部插入/刪除數,還要使浪費的空間儘量少,怎麼做?
(擴容,收縮複雜度均爲O(數的個數))
如果模仿3.3,當數組個數小於2siz時收縮,顯然會gg(當數的個數爲siz−1時不斷插入、刪除、插入、刪除……)
所以考慮減少收縮次數:
不妨讓數的個數少於4siz時收縮,這樣就不會有毒瘤(LXL)來卡掉了qwq
時間複雜度分析:
覈算法:
每次擴容的複雜度分攤到至少n個插入操作上,每個插入操作分攤到O(1)
收縮時,假設最後一次擴容時數組大小爲siz,那麼數的個數最多的時候最少也是2siz個,而收縮時數的個數會變成4siz個,所以中間最少會有4siz級別的刪除操作。
把O(siz)的複雜度分攤到4siz級別的操作上,每個操作分到O(1)。
綜上,插入/刪除的攤還複雜度均爲O(1)
聚合分析:
假設插入、刪除操作共有n個。那麼插入和收縮操作最多都是n的數量級。
由3.3的聚合分析珂知,擴容操作的總複雜度爲O(n)
假設O(2x)的收縮操作出現了ax次,那麼收縮操作複雜度F(n)=O(1)∗a1+O(2)∗a2+...+O(2n)∗an(忽略一些邊界問題qwq)
因爲若一個收縮操作是O(2siz)的,那麼這個收縮操作珂以支配2siz級別的刪除操作,
所以ax個O(2x)的收縮操作珂以支配2x∗ax個收縮操作。
因爲收縮操作的總數不超過n,所以1∗a1+2∗a2+...+2n∗an≤n
所以F(n)=O(n)
單次插入/刪除的複雜度是O(1),n次就是O(n)
綜上,總時間複雜度爲O(n)
勢能法:
咕咕咕……
暫時就這些qwq