1、均摊分析简介,by delayyy,湖南省长沙市长郡中学 陈胤伯,前(che)言(dan),一天正水着 有一位少年提问:我抱着“QAQ 同求!”的心态等了很久还是木有人回答,前(che)言(dan),当今世界 Spaly横行 随机提根的单旋党猖獗相信很多同学当初学习Splay的过程是这样的: 看了会做法 w 这很简单嘛! 复杂度咋证?一看“留给读者自行证明”、“可以证明是O(log n)”、“Tarjan证明了是” 算了不管辣!不止是Splay,更为基础的并查集,复杂度也不知道咋证明。,前(che)言(dan),不会复杂度分析,还是可以闷声做大题。可是作为一名OIER应该反对迷信,崇尚科学。_
2、 进入正题均摊分析。,均摊分析,引用黑书上的一个例子进行说明:,初始值为0的一个k位二进制计数器,支持+1操作,时间复杂度如何?,累计分析,显然每次操作O(k),会计分析,均摊分析,“初始值为0的一个k位二进制计数器,只支持加一操作。” 每次确实是O(k)的,但均摊分析可以得到更好的上界。累计分析 考虑n个操作的总时间T(n) 考虑第i位,每2i次操作变一次,所以T(n)=n+n/2+n/4+.=O(n),T(n)/n=O(1)会计分析 把时间消耗看做金钱的消耗 把一个0变成1时投资2元,其中1元当时就用掉,另1元在1变成0的时候用掉 每次操作投资2元,n次操作投资O(n),所以均摊时间复杂度
3、O(1),均摊分析,下面介绍一种更加通用的方法势能分析。把“势能”看做整个数据结构的一个状态函数定义 i 表示 i 次操作后整个结构的势能定义第 i 次操作的均摊时间花费 ai=ci+i-i-1(其中 ci 表示第 i 次操作的实际消耗时间)如果我们能设计出恰到好处的势函数,得到 a 和 0-n 上界就得到了T(n)的上界。,均摊分析,以“二进制计数器”为例,我们尝试一下势能分析。 定义势能 =(二进制串中1的个数)。 设第 i 个操作有x个0-1、y个1-0,则此操作均摊复杂度 ai=ci+=(x+y)+(x-y)=2x=2 。 T(n) = a+0-n a 2n 所以是O(n)我们发现,势
4、能分析的关键是设计势函数。 在一个很快的操作时稍微增加一点 在一个耗时的操作时急剧减少把势函数想象成一种“存储”,在不怎么耗时的时候存下,在非常耗时的时候取出来。 类似于钱,只要“自己掏的钱”和“挪用的存款”不超过某个界,那么花的钱一定不超过那个界。,Splay,先来回顾一下 Splay rotate操作 如果父亲是根,单旋一次 如果父亲和爷爷方向一致,先转父亲后转自己 如果父亲和爷爷方向不同,转两次自己定义 v 的势能 R(v)=log(sizev),势函数 =R(v) 。 显然任意时刻 0n log n,这意味着 0-n = O(n log n) 。 两个不怎么重要的发现 一棵很平衡的树,
5、不怎么耗时,势函数值较小。 一棵很畸形的树(比如一条链),容易耗时,势函数值较大。我们来看看,在这种势函数下,三种操作的均摊复杂度分别是什么。,Splay - Zig,Splay - ZigZig,Splay - ZigZag,Splay,综上所述,我们得到了三种情况下的均摊复杂度: 如果父亲是根,单旋一次 1+ R(x)-R(x) 如果父亲和爷爷方向一致,先转父亲后转自己 3(R(x)-R(x)) 如果父亲和爷爷方向不同,转两次自己 2(R(x)-R(x))由于每次旋转后 x 的结束位置是下一次旋转开始时 x 的位置 我们把三种全放缩成 3(R(x)-R(x))那么执行 Splay(v) 的
6、均摊复杂度 a = 1 + 3(R(root)-R(v)) = O(log(n/sizev)至此,我们得到了 n 个点、m 次 Splay 操作的时间复杂度为:O(n log n + m log n),LCT,分析完 Splay,再看看 LCT。LCT 可以看作一群 Splay 拼起来的结构。LCT 的主要操作是 access(v),因此我们来分析 access 的时间复杂度。1虚实边的切换2Splay 中的复杂度,LCT,1虚实边的切换若v是u的儿子且满足sizevsizeu/2,称v是u的大儿子,否则为小儿子。对应的父边称大(小)边。定义整个结构的势能 p=(大虚边的个数),一次操作的均摊
7、复杂度 a=c+p。当经过一条小边时,c+=1;p可能+=1; # 考虑size的变化,可知路过的小边条数是O(log n)的当经过一条大边时,c+=1, p-=1。所以,n 个点 m 次 access,虚实切换的均摊复杂度 = a+p0-pm = O(m log n) + O(n),LCT,2Splay中的复杂度考虑这群小 Splay,根再连到 path_parent ,形成一棵辅助树。我们把 size 定义成辅助树的子树大小,之前的证明依然成立。其实转化一下就是在一个大Splay里搞。,LCT,综上所述,n 个点 m 次 access 的 LCT 复杂度是 O(n log n + m lo
8、g n) 的。,并查集,用树来维护不相交的集合,支持 FIND 和 LINK。按秩合并 # 每个集合有个 rank,LINK 时 rank 相同就给一个+1,把 rank 小的往大的上并。路径压缩 # 即 FIND 之后顺便把路径上所有点连到根去,并查集 - 按秩合并,结论1. 一个点到根的 rank 是严格递增的结论2. 一个根节点 rank 为 r 的树,size 2r。 证明2. 考虑 rank=k 的点是如何产生的由两个根 rank=k-1 的树合并而成,归纳证明即可。结论3. n 个点的树根节点 rank 至多为 log2 n于是,由结论1、3可知:只按秩合并, FIND 复杂度 O
9、(log n),LINK 复杂度 O(1)。,并查集 - 路径压缩,大多数选手写冰茶几一般都只写路径压缩优化。 我们先来证明复杂度的 upper_bound。类似 Splay 的定义 R(v) 和 。 考虑路径压缩一发,均摊复杂度为 k+。 减少量为:注意到 ai 比后缀和还大时后半部分才会小于1,也就是 log 出 来不足 1。然而这样的 i 只有至多 log n 个。所以,这一坨 k - log n。所以均摊复杂度 O(log n),并查集 - 路径压缩,这里再给出一个 lower_bound 的证明,也就是把这种做法卡到O(m log n)。定义树 B(i) B(0) 只有一个点 B(i
10、) = merge B(i-1) , B(i-1)先用若干操作构造B(log n)。 不断执行以下操作 加入一个点,把根连过去 FIND 最深的那个点,并查集 - 完全体,并查集完全体的复杂度是 O(m (n) 的。(n) 是阿克曼函数的反函数,为此,我们先介绍一下阿克曼函数。稍有常识的人都能看出,这是一个增长十分恐怖函数。 (n) 是使得函数 Ak(0) 超过 n 的最低级别 k,一般不会超过 4。,并查集 - 完全体,一些约定px:x的父亲rankx:x的秩level(x):最大的k满足 ,显然有 0 level(x)(n)iter(x):最大的i满足 ,显然有 1 iter(x) ran
11、kx点x的势能 R(x): 如果 x 是根或 rankx=0:R(x)=(n)*rankx 否则:R(x)= (n) - level(x)*rankx - iter(x)定义整个结构的势函数 = R(x)。并查集的操作有:LINK、FIND。我们下面尝试分析每个操作的均摊复杂度。,并查集 - 完全体,先讲一些性质以便之后的分析。对于所有的非根节点 x:R(x) 不会增加 因为其父亲的 rank 单增如果 rankx 非 0 且 level(x) 或者 iter(x) 发生了变化,则 R(x) 至少要减 1。 rankx=0 的 R(x) 一直为 0。 rankx1 的 ,如果 level 不变
12、,那么 iter 至少+1;如果 level +1了,根据计算式有 R(x) -= rankx,由于 iter1,rankx,iter 的减少最多使 R(x)+= rankx-1。,并查集 - 完全体,1LINKLINK 本身是 O(1) 的,所以我们只需要考虑 。设执行的操作是 px:=y,那么只有 x 和 y 以及 y 的儿子节点的 R 可能会变。y 的儿子节点是非根节点,它们的 R 不会增加。x 本来是享受高级待遇的根,现在沦为儿子了,R 显然不增加。y 还是根,ranky 至多 +1,根据计算式 R(y) (n)因此 a = 1+(n) = O(n),并查集 - 完全体,2FIND假设
13、查找路径上一共 s 个点,则 a = s+,我们考虑 会减少多少。设 x 是路径上满足 rankx0 的一个点且 x 之后有一个 y 满足 level(x)=level(y)。注意不考虑路径两端点后,这样的 x 一定至少有 s-2-(n) 个,即除了路径上 level=k (k=0(n)-1)的最后一个点。路径压缩后 rankpx 还更大 ,因此 x 的 level 或 iter 会变化。所以 x 的势能至少会减少 1。这意味着 -(s-2-(n))所以 s+ = O( (n) ),并查集 - 完全体,综上所述,n 个点 m 次操作的并查集复杂度为:O(m (n),感谢,感谢党和国家,感谢CCF提供了这次交流的机会。感谢罗雨屏学长的帮助和指导。感谢吕凯风同学的帮助和指导。感谢彭雨翔毒瘤强行要求被加入感谢名单让我突然想起来还有感谢这个环节。,Thanks for listening,