前言
整理了一些长相清奇的时间复杂度分析……
约定:
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