一些奇奇怪怪的时间复杂度分析(持续更新)

前言

整理了一些长相清奇的时间复杂度分析……
约定:
1.如果没有特殊说明,默认T(0)=T(1)=1T(0)=T(1)=1
2.如果没有特殊说明,默认log的底数为2。
3.一些地方会说T(n)=O(...)T(n)=O(...),意会就好qwq

0.一些定义

0.1 大O记号定义

对于函数f,gf,g,若\exist常数n0,cn_0,c,使得对于n>n0\forall n>n_0f(n)<cg(n)f(n)<c*g(n),那么说f(n)f(n)O(g(n))O(g(n))的,即f(n)O(g(n))f(n)\in O(g(n))

0.2 时间复杂度定义

对于一个程序所需要执行的运算次数T(n)T(n),若T(n)O(f(n))T(n)\in O(f(n)),则把O(f(n))O(f(n))称为这个程序的时间复杂度。
例如当T(n)=13n3+100n+10086T(n)=\frac{1}{3}n^3+100n+10086时,我们说这个程序的时间复杂度是O(n3)O(n^3)。证明是显然的,随便取个n0=100,c=100n_0=100,c=100就可以了,不多说。
当然,上面的T(n)T(n)也可以说是O(n4)O(n^4)的,这并不矛盾。一般来说,我们用的时间复杂度是增长速度尽量慢的,也就是说取尽量贴近实际的T(n)T(n)的增长速度的复杂度。所以一般我们用O(n3)O(n^3)而不是O(n4)O(n^4)来表示上面的T(n)T(n)

再举几个例子:
T(n)=n+lognT(n)=n+logn,那么T(n)T(n)O(n)O(n)的。对于n>10n>10n+logn<2nn+logn<2n
T(n)=2n+logn=n2nT(n)=2^{n+logn}=n2^nT(n)T(n)O(n2n)O(n2^n)的。注意这里不能从n+lognn+lognO(n)O(n)的推出2n+logn2^{n+logn}2n2^n的,要注意。至于为什么这个不是O(2n)O(2^n),因为n0n_0cc必须是\color{red}常数

1.基本时间复杂度分析

1.0 主定理

对于递归函数,如果满足T(n)=aT(nb)+f(n)T(n)=aT(\frac{n}{b})+f(n),分类讨论:
假设cc是一个正常数。
(1)若f(n)=nlogbaf(n)=n^{log_ba},那么T(n)=O(nlogbalogn)T(n)=O(n^{log_ba}logn)
(2)若f(n)=nlogbacf(n)=n^{log_ba-c},那么T(n)=O(nlogba)T(n)=O(n^{log_ba})
(3)若f(n)=nlogba+cf(n)=n^{log_ba+c},且对于所有足够大的nnaf(nb)<f(n)af(\frac{n}{b})<f(n),那么T(n)=O(f(n))T(n)=O(f(n))

1.1 线性递推式

1.T(n)=T(n1)+c1.T(n)=T(n-1)+ccc是常数),则T(n)=O(cn)T(n)=O(cn)
2.T(n)=T(n1)+n2.T(n)=T(n-1)+n,则T(n)=O(n2)T(n)=O(n^2)
奇水……

1.2 普通分治

T(n)=2T(n2)+nT(n)=2T(\frac{n}{2})+n,则T(n)=O(nlogn)T(n)=O(nlogn)
证明:展开,T(n)=O(n)+2T(n2)T(n)=O(n)+2T(\frac{n}{2})
=O(n)+2O(n2)+4T(n4)=O(n)+2*O(\frac{n}{2})+4T(\frac{n}{4})
=...=...
=O(n)+O(n)+...+O(n)=O(n)+O(n)+...+O(n) (每次除以22,除lognlogn次到1,所以共lognlognO(n)O(n)
=O(nlogn)=O(nlogn)

主定理版:a=2,b=2,logba=log22=1a=2,b=2,log_ba=log_22=1
f(n)=O(n1)=O(nlogba)f(n)=O(n^1)=O(n^{log_ba})(打不出中间有一横的符号……意会一下qwq)
=>T(n)=O(f(n)logn)=O(nlogn)=>T(n)=O(f(n)*logn)=O(nlogn)

1.3 诡异的分治(也许这不是分治……)

T(n)=2T(n2)+kT(n)=2T(\frac{n}{2})+k,则T(n)=O(nk)T(n)=O(nk)
证明:展开,T(n)=O(k)+2T(n2)T(n)=O(k)+2T(\frac{n}{2})
=O(k)+2O(k)+4T(n4)=O(k)+2O(k)+4T(\frac{n}{4})
=...=...
=O(k)+2O(k)+4O(k)+...+2t1O(k)+2tT(1)=O(k)+2O(k)+4O(k)+...+2^{t-1}O(k)+2^{t}T(1) (为了方便,记t=lognt=logn
=(2t1)O(k)+O(2t+1)=(2^{t}-1)O(k)+O(2^{t+1})
=O(nk)+O(2n)=O(nk)+O(2n)
=O(nk)=O(nk)

主定理:????

1.4 玄学分治

T(n)=2T(n2)+nlognT(n)=2T(\frac{n}{2})+nlogn,则T(n)=O(nlog2n)T(n)=O(nlog^2n)
证明:考虑T(2n)T(2^n)
T(2n)=2T(2n1)+2nnT(2^n)=2T(2^{n-1})+2^n*n
=2(2T(2n2)+2n1(n1))+2nn=2(2T(2^{n-2})+2^{n-1}*(n-1))+2^n*n
=4T(2n2)+2n(n1)+2nn=4T(2^{n-2})+2^n*(n-1)+2^n*n
=...=...
=O(2n)1+O(2n)2+...+O(2n)n=O(2^n)*1+O(2^n)*2+...+O(2^n)*n
=O(2nn2)=O(2^nn^2)
然后T(n)=T(2logn)=O(2logn(logn)2)=O(nlog2n)T(n)=T(2^{logn})=O(2^{logn}(logn)^2)=O(nlog^2n)
之前尝试用数学归纳法……发现竟然弄出普通分治也是O(nlog2n)O(nlog^2n)……然后才发现那样用数学归纳法是不严谨的……

主定理版:a=2,b=2,logba=log22=1a=2,b=2,log_ba=log_22=1
f(n)=O(nlogn)=O(nlogbalogn)f(n)=O(nlogn)=O(n^{log_ba}logn)
=>T(n)=O(f(n)logn)=O(nlog2n)=>T(n)=O(f(n)*logn)=O(nlog^2n)

2.长相奇特的递归式

2.1 巨神mhy弄出的奇怪东西

有一次巨神mhy在CF比赛的时候弄出一个T(n)=2T(n)+lognT(n)=2T(\sqrt n)+logn的东西……
那么T(n)=O(lognloglogn)T(n)=O(logn*loglogn)
证明:上式不好直接证,考虑它的等价形式:
因为2logn=n2^{logn}=n,所以上式等价于T(2n)=O(nlogn)T(2^n)=O(nlogn)
由已知,T(2n)=2T(2n)+O(log(2n))T(2^n)=2T(\sqrt{2^n})+O(log(2^n))
化简,T(2n)=2T(2n2)+O(n)T(2^n)=2T(2^\frac{n}{2})+O(n)
发现nn每次都变成n2\frac{n}{2},所以类比普通分治的证明,T(2n)=O(nlogn)T(2^n)=O(nlogn)
于是T(n)=O(lognloglogn)T(n)=O(lognloglogn)

主定理:????

2.2 一道诡异的初赛题

给出式子T(n)=2T(n4)+nT(n)=2T(\frac{n}{4})+\sqrt n
感觉比上面的那一个还水……思路很自然qwq
同样考虑T(4n)=2T(4n1)+4nT(4^n)=2T(4^{n-1})+\sqrt{4^n}
=2(2T(4n2)+4n1)+2n=2(2T(4^{n-2})+\sqrt{4^{n-1}})+2^n
=4T(4n2)+24n1+2n=4T(4^{n-2})+2*\sqrt{4^{n-1}}+2^n
=4T(4n2)+2n+2n=4T(4^{n-2})+2^n+2^n
=...=...
=2nT(1)+2n+2n+...+2n=2^nT(1)+2^n+2^n+...+2^n (共nn2n2^n
=O(2nn)=O(2^n*n)
然后T(n)=T(4log4n)=O(2log4nlog4n)T(n)=T(4^{log_4n})=O(2^{log_4n}log_4n)
因为4log4n=n4^{log_4n}=n,所以2log4n=n2^{log_4n}=\sqrt n,而log4nlog_4nlognlogn同级
所以T(n)=O(nlogn)T(n)=O(\sqrt n logn)

主定理版:a=2,b=4,logba=log42=0.5a=2,b=4,log_ba=log_42=0.5
f(n)=O(n)=O(n12)=O(nlogba)f(n)=O(\sqrt n)=O(n^\frac{1}{2})=O(n^{log_ba})
=>T(n)=O(f(n)logn)=O(nlogn)=>T(n)=O(f(n)*logn)=O(\sqrt nlogn)

2.3 恶臭学军题

T(n)=25T(n5)+nnT(n)=25T(\frac{n}{5})+n\sqrt n
考虑T(5n)=25T(5n1)+5n5nT(5^n)=25T(5^{n-1})+5^n\sqrt{5^n}
=252T(5n2)+255n15n1+5nsqrt5n=25^2T(5^{n-2})+25*5^{n-1}\sqrt{5^{n-1}}+5^n*sqrt{5^n}
=...=...
=25nT(1)+25n155+25n22525+...+5n5n=25^nT(1)+25^{n-1}5\sqrt 5+25^{n-2}25\sqrt{25}+...+5^n\sqrt{5^n}
=54n+54n1+54n2+...+53n=\sqrt{5^{4n}}+\sqrt{5^{4n-1}}+\sqrt{5^{4n-2}}+...+\sqrt{5^{3n}}
=54n+153n51=\frac{\sqrt{5^{4n+1}}-\sqrt{5^{3n}}}{\sqrt 5-1} (等比数列求和)
=552n5n5n51=\frac{\sqrt 5*5^{2n}-5^n\sqrt{5^n}}{\sqrt 5-1}
5n5^n换成nn,得T(n)=5n2nn51=O(n2)T(n)=\frac{\sqrt 5n^2-n\sqrt n}{\sqrt 5-1}=O(n^2)

主定理版:a=25,b=5,f(n)=O(nn)=O(n1.5)a=25,b=5,f(n)=O(n\sqrt n)=O(n^{1.5})
logba=log525=2log_ba=log_5{25}=2
f(n)=O(n1.5)=O(nlogba0.5)f(n)=O(n^{1.5})=O(n^{log_ba-0.5})
=>T(n)=O(nlogba)=O(n2)=>T(n)=O(n^{log_ba})=O(n^2)

2.4 证明主定理

3.摊还分析

注:为了方便,部分内容里写了O(0)O(0)qwq(不严谨,但是是这个意思)

3.1 进栈1个/出栈k个

你需要维护一个栈,初始为空,要求资磁两种操作:
1.把xx压进栈
2.弹出kk个元素(kk\le 栈大小)
总共qq中操作,栈大小<=n<=n
暴力即珂……时间复杂度?1操作O(1)O(1),2操作O(k)O(k)
时间复杂度O(qk)=O(nq)?O(qk)=O(nq)?
口胡出时间复杂度为线性qwq

发现每次弹出kk个元素,kk的范围是与栈大小,也就是有多少个xx没被弹出的1操作有关。

核算法:(核算法大法吼!)
每一个1操作,以后最多把这个元素弹出一次(废话qwq)
记1操作的摊还代价为11,则2操作的复杂度变成O(0)O(0)
然后1操作的总代价为O(1)O(1),共qq次操作,时间复杂度O(q)O(q)

聚合分析:(聚合分析也还珂以)
发现如果1操作有xx个,那么2操作的总时间复杂度最高为O(x)O(x)
所以考虑最坏情况,1操作有qq个时,2操作的总时间复杂度为O(q)O(q)
所以总时间复杂度为O(q)+O(q)=O(q)O(q)+O(q)=O(q)

势能法:(恶臭)
势能法蜃孙……
T(i,)T(i,势能)表示前ii次操作珂能达到的最高复杂度(即势能qwq),T(i,)T(i,实际)表示前ii次操作实际达到的复杂度。
f(i)f(i)表示执行了第ii个操作后的势能
势能公式:T(i)=T(i,)+f(i)f(i1)T(i)=T(i,实际)+f(i)-f(i-1)
即:第ii步操作的(最后平均)总代价等于第ii步操作的实际代价加上从第i1i-1步操作到第ii步操作的势能变化。
回到例子,让势能=栈中有多少个元素,那么1操作会让势能+1,2操作会让势能-k。
于是1操作的总代价为1+1=21+1=2,2操作总代价为kk=0k-k=0
因此1操作总复杂度为O(1)O(1),2操作总复杂度为O(0)O(0)
最后的总复杂度为O(q)O(q)
恶臭无比……

3.2 bzoj3133 ball machine复杂度分析

推销一波题解
懒得写了……和3.1没多大差别……去题解里看吧qwq

3.3 表扩张(vector的push_back?)

维护一个数组,初始大小为1,要求资磁插入一个数,当数组满了的时候,开一个大小为原来的两倍的数组,把当前的数据复制过去。(规定单次扩容复杂度为O()O(扩容前数组大小)
每一次最坏复杂度为O(n)O(n)……时间复杂度O(n2)O(n^2)
一共lognlogn次扩容,每次O(n)O(n),时间复杂度O(nlogn)O(nlogn)
然而并不是qwq

核算法:(蜃香)
每次把数组扩大到原来的两倍的时候,这O(n)O(n)的复杂度均摊到每个插入操作上qwq
于是每个插入操作的摊还代价为O(1)O(1),扩容操作变为O(0)O(0)
插入操作总复杂度是O(1)O(1),所以最后总复杂度O(n)O(n)

聚合分析:
假设最后数组大小为nn,则扩容了lognlogn
扩容复杂度为O(1)+O(2)+O(4)+...+O(2logn)=O(n)O(1)+O(2)+O(4)+...+O(2^{logn})=O(n)
插入复杂度为O(1)n=O(n)O(1)*n=O(n)
所以总复杂度为O(n)O(n)

势能法:(恶臭)
还没想到什么直观的势能函数……但是想到一个诡异的东西不知道是否正确……口胡一波qwq
令1操作的势能变化为1(意义大概是在扩容的道路上走了多远?),那么2操作的势能变化为-n。
口胡得出1操作总代价为O(1)O(1),2操作总代价为O(0)O(0)
于是总复杂度为O(n)O(n)



//两条分割线以表敬意
令势能函数f(i)=2numsizf(i)=2num-siz,其中numnum表示数组内元素的个数,sizsiz表示数组大小。
易证若一个插入操作没有触发扩容,引起的势能变化是2。摊还代价为O(1)O(1)
若某次插入操作引起了扩容,假设插入前的数组大小是nn,那么里面的元素个数也是nn
扩容前:势能f=2numsiz=2nn=nf=2num-siz=2n-n=n
扩容后:势能f=2numsiz=2(n+1)2n=2f=2num-siz=2(n+1)-2n=2
所以势能变化为2n2-n,那么引起扩容操作的插入操作的摊还代价为n+(2n)=O(1)n+(2-n)=O(1)
综上,插入操作的摊还复杂度为O(1)O(1)
不得不说,势能函数还是很妙的……

3.4 表扩张&删除(push_back,pop_back)

//咕咕咕
问:如果你需要维护一个数据结构,需要资磁在尾部插入/删除数,还要使浪费的空间尽量少,怎么做?
(扩容,收缩复杂度均为O()O(数的个数)
如果模仿3.3,当数组个数小于siz2\frac{siz}{2}时收缩,显然会gg(当数的个数为siz1siz-1时不断插入、删除、插入、删除……)
所以考虑减少收缩次数:
不妨让数的个数少于siz4\frac{siz}{4}时收缩,这样就不会有毒瘤(LXL)来卡掉了qwq
时间复杂度分析:

核算法:
每次扩容的复杂度分摊到至少nn个插入操作上,每个插入操作分摊到O(1)O(1)
收缩时,假设最后一次扩容时数组大小为sizsiz,那么数的个数最多的时候最少也是siz2\frac{siz}{2}个,而收缩时数的个数会变成siz4\frac{siz}{4}个,所以中间最少会有siz4\frac{siz}{4}级别的删除操作。
O(siz)O(siz)的复杂度分摊到siz4\frac{siz}{4}级别的操作上,每个操作分到O(1)O(1)
综上,插入/删除的摊还复杂度均为O(1)O(1)

聚合分析:
假设插入、删除操作共有nn个。那么插入和收缩操作最多都是nn的数量级。
由3.3的聚合分析珂知,扩容操作的总复杂度为O(n)O(n)
假设O(2x)O(2^x)的收缩操作出现了axa_x次,那么收缩操作复杂度F(n)=O(1)a1+O(2)a2+...+O(2n)anF(n)=O(1)*a_1+O(2)*a_2+...+O(2^n)*a_n(忽略一些边界问题qwq)
因为若一个收缩操作是O(2siz)O(2^{siz})的,那么这个收缩操作珂以支配2siz2^{siz}级别的删除操作,
所以axa_xO(2x)O(2^{x})的收缩操作珂以支配2xax2^x*a_x个收缩操作。
因为收缩操作的总数不超过nn,所以1a1+2a2+...+2nann1*a_1+2*a_2+...+2^{n}*a_n\le n
所以F(n)=O(n)F(n)=O(n)
单次插入/删除的复杂度是O(1)O(1)nn次就是O(n)O(n)
综上,总时间复杂度为O(n)O(n)

势能法:
咕咕咕……

暂时就这些qwq

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章