[关闭]
@wsndy-xx 2018-05-27T16:42:52.000000Z 字数 4432 阅读 1061

Splay-restudy

算法学习

题记

    之前学习过Spaly,也做过几道题,但是不就就忘干净啦,几天又看了一下,应该有了更深入的理解,整理一下。

前言

Splay基于二叉查找树(这里不再介绍)
满足性质:一个节点的左孩子一定比它小,右孩子一定比它大
考虑向树中插入一个数,期望时间复杂度为
但是较为极端的情况也会被卡成
这样就非常不优秀啦
那么,有一种叫平衡树的数据结构出现了


Splay简介

Splay是平衡树的一种,中文名为伸展树,由丹尼尔·斯立特Daniel Sleator和罗伯特·恩卓·塔扬Robert Endre Tarjan在1985年发明的
它的主要思想是:对于查找频率较高的节点,使其处于离根节点相对较近的节点,这样就会使得整棵树处于较为平衡的状态,极少会出现上述 的插入。
那么怎么查找频率高的点呢
可以认为每次被查找的点查找频率相对较高,就是把每次查找到的点搬到根节点去,维持平衡


变量表示

  1. f[i] 表示 节点 i 的父亲节点
  2. ch[i][0] 表示节点 i 的左儿子
  3. ch[i][1] 表示节点 i 右儿子
  4. key[i] 表示节点 i 的关键字,就是节点 i 表示的那个数字
  5. cnt[i] 表示节点 i 的关键字出现的次数
  6. size[i] 表示以 i 为根的子树的大小
  7. Size 表示整棵树的大小
  8. Root 表示根节点

Splay基本操作

  1. void Clear(int x) {
  2. ch[x][1] = ch[x][0] = cnt[x] = size[x] = fa[x] = key[x] = 0;
  3. }
  1. bool Get(int x) {
  2. return ch[fa[x]][1] == x;
  3. }
  1. void Update(int x) {
  2. if (x) {
  3. size[x]=cnt[x];
  4. if (ch[x][0]) size[x]+=size[ch[x][0]];
  5. if (ch[x][1]) size[x]+=size[ch[x][1]];
  6. }
  7. }

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二者一下

  1. void Rotate(int x) {
  2. int fax = fa[x], grfa = fa[fax], w = Get(x);
  3. ch[fax][w] = ch[x][w ^ 1];
  4. fa[ch[x][w ^ 1]] = fax;
  5. ch[x][w ^ 1] = fax;
  6. fa[fax] = x;
  7. fa[x] = grfa;
  8. ch[grfa][ch[grfa][1] == fax] = x;
  9. Update(fax);
  10. Update(x);
  11. }
  12. <div class="md-section-divider"></div>
  1. void Splay(int x) {
  2. for(int fax; fax = fa[x]; Rotate(x))
  3. if(fa[fax]) Rotate((Get(x) == Get(fax)) ? fax : x);
  4. Root = x;
  5. }
  6. <div class="md-section-divider"></div>
  1. if (Root == 0) do_somethings
  2. <div class="md-section-divider"></div>

else 按照二叉树的性质向下查找,
找到 key[] 相同的节点 cnt[] ++, Update 当前节点 和 当前节点的父亲节点
为什么要 Update 这两个点,因为在接下来将该点Splay到根的过程中
子树大小会发生改变的只有这两点size[], cnt[]信息
而其他点在Splay后的size[]cnt[]信息与不旋转前是一样的
然后 Splay(当前节点),当前节点转到根,(至于为什么,忘记听谁讲过,因为
当前节点出现,增加的当前节点的频率,将当前节点转到根维护树的平衡,当然,也可以随机旋转。)
如果已经找到最底层,说明该点在原先的树中并不存在,直接Insert,更新一些列信息,Splay一下

  1. void Insert(int x) {
  2. if(! Root) {
  3. Size ++;
  4. Root = Size;
  5. ch[Root][10] = ch[Root][0] = fa[Root] = 0;
  6. size[Root] = cnt[Size] = 1;
  7. key[Root] = x;
  8. return ;
  9. }
  10. int now = Root, fanow = 0;
  11. while(1) {
  12. if(x == key[now]) {
  13. cnt[now] ++;
  14. Update(now), Update(fanow);
  15. Splay(now);
  16. return ;
  17. }
  18. fanow = now;
  19. now = ch[now][key[now] < x];
  20. if(!now) {
  21. Size ++;
  22. ch[Size][0] = ch[Size][11] = 0;
  23. fa[Size] = fanow;
  24. ch[fanow][x > key[fanow]] = Size;
  25. size[Size] = cnt[Size] = 1;
  26. key[Size] = x;
  27. Update(fanow);
  28. Splay(Size);
  29. break;
  30. }
  31. }
  32. }
  33. <div class="md-section-divider"></div>

else 说明 x > key[u], 那么Ans +=左子树的大小 + 当前节点的大小,即 cnt[]
如果相等,得到Ans,并且Splay 当前节点

  1. inline int Find(int x) {
  2. int now = Root, Ans = 0;
  3. while(1) {
  4. if(x < key[now]) now = ch[now][0];
  5. else {
  6. Ans += (ch[now][0] ? size[ch[now][0]] : 0);
  7. if(x == key[now]) {
  8. Splay(now);
  9. return Ans + 1;
  10. }
  11. Ans += cnt[now];
  12. now = ch[now][1];
  13. }
  14. }
  15. }
  16. <div class="md-section-divider"></div>

else 向右子树找 || 已经找到
判断:记录左子树大小 size[] 和 当前节点的大小 cnt[], 然后进行判断

  1. inline int Findx(int x) {
  2. int now = Root;
  3. while(1) {
  4. if(ch[now][0] && x <= size[ch[now][0]]) now = ch[now][0];
  5. else {
  6. int imp = (ch[now][0] ? size[ch[now][0]] : 0) + cnt[now];
  7. if(x <= imp) return key[now];
  8. x -= imp;
  9. now = ch[now][13];
  10. }
  11. }
  12. }
  13. <div class="md-section-divider"></div>
  1. int Pre() {
  2. int now = ch[Root][0];
  3. while(ch[now][14]) now = ch[now][15];
  4. return now;
  5. }
  6. int Nxt() {
  7. int now = ch[Root][16];
  8. while(ch[now][0]) now = ch[now][0];
  9. return now;
  10. }
  11. <div class="md-section-divider"></div>
  1. void Delete(int x) {
  2. int my = Find(x);
  3. if(cnt[Root] > 1) cnt[Root] --, Update(Root);
  4. else if(!ch[Root][17] && !ch[Root][0]) Clear(Root), Root = 0;
  5. else if(!ch[Root][0]) {
  6. int befroot = Root;
  7. Root = ch[Root][18];
  8. fa[Root] = 0;
  9. Clear(befroot);
  10. }
  11. else if(!ch[Root][19]) {
  12. int befroot = Root;
  13. Root = ch[Root][0];
  14. fa[Root] = 0;
  15. Clear(befroot);
  16. }
  17. else {
  18. int left = Pre(), befroot = Root;
  19. Splay(left);
  20. ch[Root][20] = ch[befroot][21];
  21. fa[ch[befroot][22]] = Root;
  22. Clear(befroot);
  23. Update(Root);
  24. }
  25. return ;
  26. }

总结

平衡树的本质是二叉搜索树
Splay 的本质是旋转 Rotate

完整代码

Code

参考

史上最详尽的平衡树(splay)讲解与模板
Splay详解(二)

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注