文章图片和代码来自邓俊辉老师课件

概述

伸展树(Splay Tree),也叫分裂树,是一种二叉排序树,它能在O(log n)内完成插入、查找和删除操作。它由丹尼尔·斯立特Daniel Sleator 和 罗伯特·恩卓·塔扬Robert Endre Tarjan 在1985年发明的。(出处百度百科)

它的操作就是将访问到的元素放在根节点处。主要的操作就是 zip 和 zag

下面是空间/时间复杂度(出处

算法分析

双层伸展

双层伸展的作用是提升了树平均的访问性能。构思的精髓 : 向上追溯两层,而非一层。 右下角是伸展树需要处理的四种情况。具体的处理是怎么样的呢?

双层伸展主要在 zig-zip 和 zag-zag 的情况下发挥作用,例如要使v 升到根节点,双层伸展要求我们使用先对祖父节点zig,然后再做一次zip ,即是下部分三幅图演示的那样。下面我们看一下使用这种方法真的可以提升性能吗。

分摊性能

我们可以看到左边是逐层调整的方法,而右边是双层调整的方法,右边的子树的高度很明显比左边的矮了一半,当下一次又遇到最坏节点时,由于高度矮了一半了,那么性能自然就提升了。所以平均分摊时间可以达到 logn .

算法实现

代码是根绝邓老师的提供的代码用java改写的,添加了部分注释

主要处理的四种情况可以详见下面的代码

  1 package Splay;
2
3
4 public class Node {
5 Node left;
6 Node right;
7 Node parent;
8 int value;
9
10 public Node(int value) {
11 this.value = value;
12 }
13
14 public boolean isLeftChild() {
15 return parent != null && (parent.left == this);
16 //根节点,我们直接返回false
17 }
18
19 public boolean isRightChild() {
20 return parent != null && (parent.right == this);
21 //根节点,我们直接返回false
22 }
23
24 @Override
25 public String toString() {
26 return "该节点的值为:" + value + " 左节点:" + ((left!=null)? left.value:"无") + " 右节点:" + ((right!=null)? right.value:"无") + " 父节点:" + ((parent!=null)? parent.value:"无");
27 }
28 }
29
  1 package Splay;
2
3
4 import com.sun.org.apache.bcel.internal.generic.IF_ACMPEQ;
5
6 import javax.management.modelmbean.ModelMBean;
7
8 /**
9 * Splay Tree (伸展树)
10 *
11 *
12 */
13 public class SplayTree {
14
15 public Node root;
16
17 /**
18 * @param val 插入节点的值
19 */
20 public Node insert(int val) throws Exception {
21 if (root == null) {
22 root = new Node(val);
23 return null;
24 }
25 Node node = search(val);
26 //search操作查找是否存在该节点
27 if (node.value == val) {
28 return node;
29 } else { //search操作找不到该节点,查找返回的是hot节点,插入的节点再和hot重新装配
30 Node newRoot = new Node(val);
31 if (val > node.value) {
32 node.parent = newRoot;
33 newRoot.left = node;
34 newRoot.right = node.right;
35 if (node.right != null) {
36 node.right.parent = newRoot;
37 node.right = null;
38 }
39 } else {
40 node.parent = newRoot;
41 newRoot.right = node;
42 newRoot.left = node.left;
43 if (node.left != null) {
44 node.left.parent = newRoot;
45 node.left = null;
46 }
47 }
48 root = newRoot;
49 return newRoot;
50 }
51 }
52
53 /**
54 * 删除某个节点
55 * @param val 删除的节点
56 * @return true 成功删除,反之
57 */
58 public boolean delete(int val)throws Exception{
59 Node node = search(val);
60 if (node.value == val) { //找到该节点
61 if (node.left == null) { //没有左子树
62 root = node.right;
63 node.right.parent = null;
64 node.right = null;
65 } else if (node.right == null) { //没有右子树
66 root = node.left;
67 node.left.parent = null;
68 node.left = null;
69 }else { //节点存在左右子树
70 //暂时切除左子树,然后右子树中最小的节点,在连接起来
71 Node lTree = node.left;
72 lTree.parent = null;
73 root.left = null;
74 search(node.value);
75 //最小的节点必然上升到了根节点,此时的root应该是右子树中最小的节点
76 root.left = lTree;
77 lTree.parent = root;
78 node = null; //原来的节点置null
79 }
80 return true;
81 }else {
82 return false;
83 }
84 }
85
86 /**
87 * 计算整个树的高度
88 *
89 * @return
90 */
91 public int height() {
92 Node node;
93 int lh = 1, rh = 1;
94 node = root.left;
95 while (node != null) {
96 lh++;
97 node = node.left;
98 }
99
100 node = root.right;
101 while (node != null) {
102 rh++;
103 node = node.right;
104 }
105
106 return Math.max(lh, rh);
107 }
108
109 /**
110 * 中序递归打印
111 * @param node
112 */
113 public void printMidNum(Node node) {
114 if (node != null) {
115 printMidNum(node.left);
116 System.out.print(node.value + " ");
117 // System.out.println(node.toString());
118 printMidNum(node.right);
119 }
120 }
121
122
123 /**
124 * @param value 使用伸展策略搜寻的某个值,
125 * @return 返回查找到的node, 树中没有该元素返回null
126 */
127 public Node search(int value) throws Exception {
128 /*
129 搜索某个节点是不是存在,调用splay方法
130 */
131 Node compare;
132 Node target; //目标节点
133 Node hot = null; //目标附近节点
134 compare = root;
135
136 if (compare == null) {
137 throw new Exception("该树为空树");
138 }
139 //查找某个节点
140 while (true) {
141 if (compare == null) {
142 target = null;
143 break;
144 } else if (compare.value == value) {
145 target = compare;
146 break;
147 }
148 if (compare.value > value) {
149 hot = compare;
150 compare = compare.left;
151 } else {
152 hot = compare;
153 compare = compare.right;
154 }
155 }
156
157 if (target == null) {
158 root = splay(hot);
159 return null;
160 }
161
162 return (root = splay(target));
163 }
164
165
166 public void attachAsLChild(Node parent, Node lChild) {
167 parent.left = lChild;
168 if (lChild != null)
169 lChild.parent = parent;
170 }
171
172 void attachAsRChild(Node parent, Node rChild) {
173 parent.right = rChild;
174 if (rChild != null)
175 rChild.parent = parent;
176 }
177
178 /**
179 * 传入的 v 必须存在于树中,由调用的方法保证
180 * splay 方法针对情况进行转换,转换的思路是重新对各个节点的位置装配,装配的意思指的是
181 * 根据初始的位置和最终的位置拆解-重连的操作
182 *
183 * @param v v为因最近访问而需伸展的节点位置
184 * @return 调整之后新树根应为被伸展的节点,故返回该节点的位置以便上层函数更新树根
185 */
186 public Node splay(Node v) {
187 if (v == null) return null;
188 //*v的父亲与祖父
189 Node p;
190 Node g;
191 while ((p = v.parent) != null && (g = p.parent) != null) { //自下而上,反复对*v做双层伸展
192 Node gg = g.parent; //每轮之后*v都以原曾祖父(great-grand parent)为父
193 if (v.isLeftChild()) {
194 if (p.isLeftChild()) { //zig-zig
195 attachAsLChild(g, p.right);
196 attachAsLChild(p, v.right);
197 attachAsRChild(p, g);
198 attachAsRChild(v, p);
199 } else { //zig-zag
200 attachAsLChild(p, v.right);
201 attachAsRChild(g, v.left);
202 attachAsLChild(v, g);
203 attachAsRChild(v, p);
204 }
205 } else if (p.isRightChild()) { //zag-zag
206 attachAsRChild(g, p.left);
207 attachAsRChild(p, v.left);
208 attachAsLChild(p, g);
209 attachAsLChild(v, p);
210 } else { //zag-zig
211 attachAsRChild(p, v.left);
212 attachAsLChild(g, v.right);
213 attachAsRChild(v, g);
214 attachAsLChild(v, p);
215 }
216
217 //重连子树,并判断是否loop是否结束
218 if (gg == null)
219 v.parent = null; //若*v原先的曾祖父*gg不存在,则*v现在应为树根
220 else //否则,*gg此后应该以*v作为左或右孩子
221 if (g == gg.left) {
222 attachAsLChild(gg, v);
223 } else {
224 attachAsRChild(gg, v);
225 }
226
227 }
228 //双层伸展结束时,必有g == NULL,但p可能非空,即是目标到根节点之间还有一个节点,需要一次单旋解决
229 if ((p = v.parent) != null) {
230 if (v.isLeftChild()) {
231 attachAsLChild(p, v.right);
232 attachAsRChild(v, p);
233 } else {
234 attachAsRChild(p, v.left);
235 attachAsLChild(v, p);
236 }
237 }
238 v.parent = null;
239 return v;
240 }
241
242
243 }
244
245
246

代码中的注释已经说明了各个操作的流程,需要注意的是insert 和 delete方法,由于这两个方法在实现的时候都会先调用search方法,我们使用了一个hot 节点,表示离目标节点最近的节点,让hot 节点上升到根节点,方便我们在insert和delete后续的使用。

综合评价

不能保证单次最坏情况的出现的原因是,假如我们一开始要找的那个点就在最底下,那么就可能达到了最坏的情况。

参考资料

  • 邓俊辉老师的数据结构课程

数据结构(二) --- 伸展树(Splay Tree)的更多相关文章

  1. 纸上谈兵: 伸展树 (splay tree)[转]

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢!  我们讨论过,树的搜索效率与树的深度有关.二叉搜索树的深度可能为n,这种情况下,每 ...

  2. K:伸展树(splay tree)

      伸展树(Splay Tree),也叫分裂树,是一种二叉排序树,它能在O(lgN)内完成插入.查找和删除操作.在伸展树上的一般操作都基于伸展操作:假设想要对一个二叉查找树执行一系列的查找操作,为了使 ...

  3. 高级搜索树-伸展树(Splay Tree)

    目录 局部性 双层伸展 查找操作 插入操作 删除操作 性能分析 完整源码 与AVL树一样,伸展树(Splay Tree)也是平衡二叉搜索树的一致,伸展树无需时刻都严格保持整棵树的平衡,也不需要对基本的 ...

  4. 树-伸展树(Splay Tree)

    伸展树概念 伸展树(Splay Tree)是一种二叉排序树,它能在O(log n)内完成插入.查找和删除操作.它由Daniel Sleator和Robert Tarjan创造. (01) 伸展树属于二 ...

  5. 伸展树(Splay tree)的基本操作与应用

    伸展树的基本操作与应用 [伸展树的基本操作] 伸展树是二叉查找树的一种改进,与二叉查找树一样,伸展树也具有有序性.即伸展树中的每一个节点 x 都满足:该节点左子树中的每一个元素都小于 x,而其右子树中 ...

  6. 【BBST 之伸展树 (Splay Tree)】

    最近“hiho一下”出了平衡树专题,这周的Splay一直出现RE,应该删除操作指针没处理好,还没找出原因. 不过其他操作运行正常,尝试用它写了一道之前用set做的平衡树的题http://codefor ...

  7. 伸展树 Splay Tree

    Splay Tree 是二叉查找树的一种,它与平衡二叉树.红黑树不同的是,Splay Tree从不强制地保持自身的平衡,每当查找到某个节点n的时候,在返回节点n的同时,Splay Tree会将节点n旋 ...

  8. HDU 4453 Looploop (伸展树splay tree)

    Looploop Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Su ...

  9. hdu 2871 Memory Control(伸展树splay tree)

    hdu 2871 Memory Control 题意:就是对一个区间的四种操作,NEW x,占据最左边的连续的x个单元,Free x 把x单元所占的连续区间清空 , Get x 把第x次占据的区间输出 ...

  10. [数据结构]伸展树(Splay)

    #0.0 写在前面 Splay(伸展树)是较为重要的一种平衡树,理解起来也依旧很容易,但是细节是真的多QnQ,学一次忘一次,还是得用博客加深一下理解( #1.0 Splay! #1.1 基本构架 Sp ...

随机推荐

  1. 279. 完全平方数 leetcode JAVA

    题目: 给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n.你需要让组成和的完全平方数的个数最少. 示例 1: 输入: n = 12 输出: 3 解释: ...

  2. Ionic2:创建App启动页滑动欢迎界面

    来自:https://my.oschina.net/qinphil/blog/777787 著作权归原创作者所有,如有再转,请自觉标明原创出处,以示尊重! 摘要: 每个有逼格的App在第一次启动时都有 ...

  3. [译文]casperjs使用说明-选择器

    casperjs的选择器可以在dom下工作,他既支持css也支持xpath. 下面所有的例子都基于这段html代码: <!doctype html> <html> <he ...

  4. I01-通过查询资料库方式来监控Informatica调度情况

    --登陆INFA资料库,运行下面的SQL --想要更加个性化查询的话注意看SQL倒数第二第三行的备注 SELECT RUN_DATE, START_TIME , END_TIME, FOLIDER , ...

  5. Nginx反向代理与负载简单实现

    反向代理 1.proxy_pass 通过反向代理把请求转发到百度 2.proxy_pass 既可以是ip地址,也可以是域名,同时还可以指定端口 3.proxy_pass 指定的地址携带了URI,如果前 ...

  6. [Ruby]Unzipping a file using rubyzip

    link: http://www.markhneedham.com/blog/2008/10/02/ruby-unzipping-a-file-using-rubyzip/ require 'ruby ...

  7. 导出excel设置样式(Aspose.Cells)

    Aspose.Cells.Style style = xlBook.Styles[xlBook.Styles.Add()];style1.Pattern = Aspose.Cells.Backgrou ...

  8. java HelloWorld时报错:"找不到或无法加载主类"问题的解决办法

    学习java的第一天: 当我在做Java入门的时候,根据教程写的第一个Java程序是: public class Hello{ public static void main(String args[ ...

  9. window.onresize事件在vue项目中的应用

    //vue页面<template> <div id='echart'> 报表 </div> </template> <script> exp ...

  10. Ubuntu18.04安装thunderbird并设置中文

    Ubuntu18.04安装thunderbird并设置中文 安装thunderbird sudo apt-get install thunderbird 安装中文包 sudo apt-get inst ...