浅谈splay的双旋

昨晚终于明白了splay双旋中的一些细节,今日整理如下

注:题目用的2002HNOI营业额统计,测试结果均来及codevs 网站的评测结果 http://codevs.cn/problem/1296/

本题完整代码请见http://www.cnblogs.com/TheRoadToTheGold/p/6372009.html

1、这是旋转部分的代码

 1 inline void splay(int x,int goal) {
 2     while(pre[x]!=goal) {
 3         int y=pre[x];
 4         int kind=ch[y][0]==x;
 5         if(pre[y]==goal) rot(x,kind); //父节点是目标节点,单旋 
 6         else {
 7             kind=ch[pre[y]][0]==y;
 8             if(ch[y][!kind]==x) { //一字型 
 9                 rot(y,kind);
10                 rot(x,kind);
11             } else { //之字型 
12                 rot(x,!kind);
13                 rot(x,kind);
14             }
15         }
16     }
17     root=x;
18 }

 

理解较浅,目前认为之字形旋转的本质就是单旋,一字型才算双旋

2、双旋与单旋的区别

先看看运行结果

左边是单旋,右边是双旋,空间几乎一样,时间差距很大

why?    

 图1:

图2的单旋结果

         

 图2:

双旋结果                                                                                                                        

           

可以发现,双旋之后的层数要比单旋之后的层数少1

这只是5层(本图中第6层对5不干扰),如果层数更多呢

所以,双旋层数比单旋要更少

手推一下,上图中第1次旋转结果的树的形态与单旋结果的树的形态是一样的,只是4和3换了个位

所以双旋的优越性在一字型层数>4时才会体现

为什么双旋层数要少?

继续观察图2的第2个和第3个,发现少的那一层是因为4和1处在了同一层

即双旋 使 待旋转点的旋转方向的孩子 与待旋转点的爷爷节点  处在同一层

换用字母表示:

设a的旋转方向的孩子是b,a的父节点是c,c的父节点是d

那么双旋使b、d处在同一层,因为b、d都成为了c的孩子

为什么?分析双旋的两次旋转

先旋转父节点,使爷爷节点和自己都成为父节点的孩子,此时父节点和自己处在同一层,原来的爷爷节点成为自己的父节点

然后旋转自己,自己旋转方向的孩子节点成为自己现在父节点(原先爷爷节点)的孩子,也就是和自己原先父节点处在了同一层

这也是我认为之字型旋转也是单旋的原因,因为之字型旋转是让自己旋转2次,与单旋并无不同

3、旋转顺序

A、代码中一字型旋转顺序是先转父节点,再转自己

①不能改成先转自己,再转父节点

先转自己让自己到父节点的位置,父节点到自己的孩子位置,再转父节点,又转回去了,跟没转一个样

②可以改成旋转自己两次,但那样就相当于单旋

B、之字型旋转的旋转顺序是连续转自己2次

看图,

可以发现连续转自己两次,每次使自己上移一层

而如果先转父节点,可以发现自己原来在哪儿还在那儿,只是父节点和爷爷节点换了而已,相当于这一次白转了

当然你也可以先转父节点,再转自己(两次旋转方向相同),因为的正确性是有保证的

对比一下,上边是先转父节点,再转自己,下边是连续转自己两次(一字型旋转均是先转父节点,再转自己)

上边空间明显大,时间也多点儿

 

 

以上内容转自这里QWQ

 

---------------------------------------------------我-是-分-割-线-----------------------------------------------------------

 

下面说一点我自己的思考QWQ

我们假设有这样一棵二叉搜索树

现在要把4节点转到根节点

按照一般的方法,会一直旋转4节点直到旋转到根

转完后是酱紫的

(中间的过程我太懒省略了,有兴趣的可以自己动手画画)

但是我们通过观察发现,可以先对4节点进行一次单旋

 

然后就可以愉快地双旋辣QWQ

转完后是酱紫的

结构一下子变得高效了起来

这只是一条不太长的链,所以层数只减少了一层。如果数据很大,这一优化就会更明显

代码只需要做很小的修改

1 //一般的双旋
2 inline void splay(int x, int goal) { //将 x 旋转到 goal 的子节点
3     for (int i; (i = t[x].f) != goal; rotate(x))
4         if (t[i].f != goal)
5             rotate(get(x) == get(i) ? i : x);
6     if (!goal)
7         rt = x;
8 }
1 //单双混用
2 inline void splay(int x, int goal) {
3     for (int i; (i = t[x].f) != goal; rotate(x))
4         if (t[i].f != goal && get(x) == get(i))
5             rotate(i);
6     if (!goal)
7         rt = x;
8 }

按照以上分析,这样写程序会变得更加高效,然而在实际评测时单双混用却要稍微慢一点,空间使用也略大

【例1】洛谷P3391 【模板】文艺平衡树(Splay)

上面是一般的双旋,下面是单双混用

【例2】洛谷P3369 【模板】普通平衡树

 

上面是一般的双旋, 下面是单双混用

测试了几次都是这样,十分玄学

posted @ 2019-07-27 11:49  rp++  阅读(309)  评论(0编辑  收藏  举报