@wsndy-xx
2018-05-27T08:42:52.000000Z
字数 4432
阅读 1284
算法学习 之前学习过Spaly,也做过几道题,但是不就就忘干净啦,几天又看了一下,应该有了更深入的理解,整理一下。
Splay基于二叉查找树(这里不再介绍)
满足性质:一个节点的左孩子一定比它小,右孩子一定比它大
考虑向树中插入一个数,期望时间复杂度为
但是较为极端的情况也会被卡成
这样就非常不优秀啦
那么,有一种叫平衡树的数据结构出现了
Splay是平衡树的一种,中文名为伸展树,由丹尼尔·斯立特Daniel Sleator和罗伯特·恩卓·塔扬Robert Endre Tarjan在1985年发明的
它的主要思想是:对于查找频率较高的节点,使其处于离根节点相对较近的节点,这样就会使得整棵树处于较为平衡的状态,极少会出现上述 的插入。
那么怎么查找频率高的点呢
可以认为每次被查找的点查找频率相对较高,就是把每次查找到的点搬到根节点去,维持平衡
f[i] 表示 节点 i 的父亲节点ch[i][0] 表示节点 i 的左儿子ch[i][1] 表示节点 i 右儿子key[i] 表示节点 i 的关键字,就是节点 i 表示的那个数字cnt[i] 表示节点 i 的关键字出现的次数size[i] 表示以 i 为根的子树的大小Size 表示整棵树的大小Root 表示根节点
void Clear(int x) {ch[x][1] = ch[x][0] = cnt[x] = size[x] = fa[x] = key[x] = 0;}
bool Get(int x) {return ch[fa[x]][1] == x;}
void Update(int x) {if (x) {size[x]=cnt[x];if (ch[x][0]) size[x]+=size[ch[x][0]];if (ch[x][1]) size[x]+=size[ch[x][1]];}}
Rotate(D) (就是把 旋转到它的父亲节点) 流程 fa[D] = B, 找到 fa[B] = A, 判断 是 的左儿子还是右儿子,记为 ; ch[B][W] = G; fa[G] = B;
3.把 转到 嘛,代替了 的位置,当然也要替人家当儿子,
fa[D] = A ; 会成为 与 相同关系的儿子
小技巧 ch[A][ch[A][1]== B] = D
如果 是 的右儿子 ,那么 为 的右儿子, 反之 为 的左儿子
4.注意,我们记 Root 节点的 fa[]为 0
5.可以看出 size[]发生变化的只有D,B,Update二者一下
void Rotate(int x) {int fax = fa[x], grfa = fa[fax], w = Get(x);ch[fax][w] = ch[x][w ^ 1];fa[ch[x][w ^ 1]] = fax;ch[x][w ^ 1] = fax;fa[fax] = x;fa[x] = grfa;ch[grfa][ch[grfa][1] == fax] = x;Update(fax);Update(x);}<div class="md-section-divider"></div>
Rotate,可以将某点旋转到某一目标状态 Rotate(B), Rotate(D)
void Splay(int x) {for(int fax; fax = fa[x]; Rotate(x))if(fa[fax]) Rotate((Get(x) == Get(fax)) ? fax : x);Root = x;}<div class="md-section-divider"></div>
if (Root == 0) do_somethings<div class="md-section-divider"></div>
else 按照二叉树的性质向下查找,
找到 key[] 相同的节点 cnt[] ++, Update 当前节点 和 当前节点的父亲节点
为什么要 Update 这两个点,因为在接下来将该点Splay到根的过程中
子树大小会发生改变的只有这两点size[], cnt[]信息
而其他点在Splay后的size[]与cnt[]信息与不旋转前是一样的
然后 Splay(当前节点),当前节点转到根,(至于为什么,忘记听谁讲过,因为
当前节点出现,增加的当前节点的频率,将当前节点转到根维护树的平衡,当然,也可以随机旋转。)
如果已经找到最底层,说明该点在原先的树中并不存在,直接Insert,更新一些列信息,Splay一下
void Insert(int x) {if(! Root) {Size ++;Root = Size;ch[Root][10] = ch[Root][0] = fa[Root] = 0;size[Root] = cnt[Size] = 1;key[Root] = x;return ;}int now = Root, fanow = 0;while(1) {if(x == key[now]) {cnt[now] ++;Update(now), Update(fanow);Splay(now);return ;}fanow = now;now = ch[now][key[now] < x];if(!now) {Size ++;ch[Size][0] = ch[Size][11] = 0;fa[Size] = fanow;ch[fanow][x > key[fanow]] = Size;size[Size] = cnt[Size] = 1;key[Size] = x;Update(fanow);Splay(Size);break;}}}<div class="md-section-divider"></div>
Root, Ans 为该数的排名 if x < key[u] 应该向左子树找else 说明 x > key[u], 那么Ans +=左子树的大小 + 当前节点的大小,即 cnt[]
如果相等,得到Ans,并且Splay 当前节点
inline int Find(int x) {int now = Root, Ans = 0;while(1) {if(x < key[now]) now = ch[now][0];else {Ans += (ch[now][0] ? size[ch[now][0]] : 0);if(x == key[now]) {Splay(now);return Ans + 1;}Ans += cnt[now];now = ch[now][1];}}}<div class="md-section-divider"></div>
Find ,从 Root 开始找,初始化当前点 u = Root; if 当前点有左子树 && x < size[左子树],就向左子树找else 向右子树找 || 已经找到
判断:记录左子树大小 size[] 和 当前节点的大小 cnt[], 然后进行判断
inline int Findx(int x) {int now = Root;while(1) {if(ch[now][0] && x <= size[ch[now][0]]) now = ch[now][0];else {int imp = (ch[now][0] ? size[ch[now][0]] : 0) + cnt[now];if(x <= imp) return key[now];x -= imp;now = ch[now][13];}}}<div class="md-section-divider"></div>
x 的前驱(< x 且最大的数),后继(> x 且最小的数) x Insert,注意此时 Root = x, 那么前驱就是从左子树一直找右儿子 key[] == x 的一个点
int Pre() {int now = ch[Root][0];while(ch[now][14]) now = ch[now][15];return now;}int Nxt() {int now = ch[Root][16];while(ch[now][0]) now = ch[now][0];return now;}<div class="md-section-divider"></div>
x Splay(x), 使得 Root == x; Root 没有孩子 -> Clear() x 的前驱,Splay,此时 Root = 该前驱, x 的右子树接到新根的右子树上,这样就相当于把x 删除了 x 的左儿子呢?思考,此时 x 已经没有左儿子了 Update 新根
void Delete(int x) {int my = Find(x);if(cnt[Root] > 1) cnt[Root] --, Update(Root);else if(!ch[Root][17] && !ch[Root][0]) Clear(Root), Root = 0;else if(!ch[Root][0]) {int befroot = Root;Root = ch[Root][18];fa[Root] = 0;Clear(befroot);}else if(!ch[Root][19]) {int befroot = Root;Root = ch[Root][0];fa[Root] = 0;Clear(befroot);}else {int left = Pre(), befroot = Root;Splay(left);ch[Root][20] = ch[befroot][21];fa[ch[befroot][22]] = Root;Clear(befroot);Update(Root);}return ;}
平衡树的本质是二叉搜索树
Splay 的本质是旋转 Rotate