主页 > IT业界  > 

splay

splay

s p l a y splay splay 是一种平衡树,但是并不完全平衡。

它凭借一系列操作使得树趋近于平衡,所以与 A V L AVL AVL 比起来,时间可能没那么快,但是 s p l a y splay splay 可以应用到的地方很多,比如说区间翻转,这是 A V L AVL AVL 无法做到的。

并且LCT要用到splay。

规定 t r e e [ x ] . s o n [ 0 / 1 ] tree[x].son[0/1] tree[x].son[0/1] 表示 x x x 的左右儿子是谁。 t r e e [ x ] . s i z e tree[x].size tree[x].size 表示以 x x x 为根的子树大小。 t r e e [ x ] . f a tree[x].fa tree[x].fa 表示 x x x 的父亲节点。 原理 旋转

s p l a y splay splay 也是利用旋转来维护的,它旋转时传的是儿子节点,然后将儿子节点转到父亲节点。

旋转时有两种情况,一种是我是你的左儿子,一种是我是你的右儿子,但其实两种情况分析起来本质上都差不多,可以打在一起。(不用像 A V L AVL AVL 一样分开)

代码 int pdd(int x) { return tree[tree[x].fa].son[1] == x; }//x是它父亲的哪个儿子 void rotato(int x) { int y = tree[x].fa, z = tree[y].fa, p = pdd(x); tree[z].son[pdd(y)] = x; tree[x].fa = z, tree[y].fa = x; tree[y].son[p] = tree[x].son[p ^ 1], tree[tree[x].son[p ^ 1]].fa = y; tree[x].son[p ^ 1] = y; updata(y), updata(x);//更新信息 } splay

s p l a y splay splay 肯定有 s p l a y splay splay 操作。

s p l a y ( x , y ) splay(x, y) splay(x,y) 就是用来将 x x x 转成 y y y 的儿子。

用到了旋转操作。

代码 bool pd(int x, int y) { return tree[x].fa != y; } void splay(int x, int goal) { push(x);//因为有时候要区间翻转,这个用来用来下传翻转标记,如果题目没有翻转可以不用 for (int y; pd(x, goal); rotato(x)) { y = tree[x].fa; if (pd(y, goal))//用来加快程序速度,删除对程序正确性不会有任何影响 rotato(pdd(y) == pdd(x) ? y : x); } if (!goal) root = x; }

查找,删除的操作都可以参照AVL来做,所以不再叙述。(可以看看我的另一篇博客)

翻转

我们来看看翻转操作如何实现。

我们假设要将区间 l , r l, r l,r 翻转,我们设 l ′ , r ′ l',r' l′,r′ 分别为 l l l 位置的左边, r r r 位置的右边。

则我们可以先将 l l l 节点 s p l a y splay splay 到根节点,再将 r r r 节点 s p l a y splay splay 到根节点的右儿子。

此时 r r r 节点的左子树就是我们要翻转的区间。

由于平衡树的性质,我们只需要将该子树的每个节点的左右儿子交换,便可翻转整个区间。

此时我们前面的 s p l a y splay splay 中的 p u s h push push 就是用来将翻转标记翻转下来。

代码 void push(int x) { if (tree[x].fa) push(tree[x].fa); pushdown(x); } void reverse(int l, int r) { //此时的l,r就是l',r' splay(l, 0), splay(r, l), tree[tree[r].son[0]].flag ^= 1; }

此时 s p l a y splay splay 的基本操作就讲完了。

标签:

splay由讯客互联IT业界栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“splay