6天通吃树结构—— 第四天 伸展树

原文:6天通吃树结构—— 第四天 伸展树

我们知道AVL树为了保持严格的平衡,所以在数据插入上会呈现过多的旋转,影响了插入和删除的性能,此时AVL的一个变种

伸展树(Splay)就应运而生了,我们知道万事万物都遵循一个“八二原则“,也就是说80%的人只会用到20%的数据,比如说我们

的“QQ输入法”,平常打的字也就那么多,或许还没有20%呢。

一:伸展树

1:思想

伸展树的原理就是这样的一个”八二原则”,比如我要查询树中的“节点7”,如果我们是AVL的思路,每次都查询“节点7”,那么当这

棵树中的节点越来越多的情况下就会呈现下旋,所以复杂度只会递增,伸展树的想法就是在第一次查询时树里面会经过一阵痉挛把

“节点7”顶成“根节点”,操作类似AVL的双旋转,比如下图:

当我们再次查询同样的”数字7“时,直接在根节点处O(1)取出,当然这算是一个最理想的情况,有时痉挛过度,会出现糟糕的”链表“,

也就退化了到O(N),所以伸展树讲究的是”摊还时间“,意思就是说在”连续的一系列操作中的平均时间“,当然可以保证是log(N)。

2:伸展方式

不知道大家可否记得,在AVL中的旋转要分4个情况,同样伸展树中的伸展需要考虑6种情况,当然不考虑镜像的话也就是3种情况,

从树的伸展方向上来说有“自下而上”和“自上而下"的两种方式,考虑到代码实现简洁,我还是说下后者。

<1> 自上而下的伸展

这种伸展方式会把树切成三份,L树,M树,R树,考虑的情况有:单旋转,“一字型”旋转,“之字形”旋转。

①: 单旋转

从图中我们可以看到,要将“节点2”插入到根上,需要将接近于“节点2”的数插入到根上,也就是这里的“节点7”,首先树被分成了3份,

初始情况,L和R树是“空节点”,M是整棵树,现在需要我们一步一步拆分,当我们将“节点2”试插入到“节点7”的左孩子时,发现“节点7”

就是父节点,满足“单旋转”情况,然后我们将整棵树放到“R树”中的left节点上,M此时是一个逻辑上的空节点,然后我们将R树追加到

M树中。L树追加到M的左子树中,最后我们将“节点2”插入到根节点上。说这么多有点拗口,伸展树比较难懂,需要大家仔细品味一下。

②: 一字型

一字型旋转方式与我们AVL中的“单旋转”类似,首先同样我们切成了三份,当我们"预插入20时”,发现20的“父节点”是根的右孩子,

而我们要插入的数字又在父节点的右边,此时满足”一字型“旋转,我们将7,10两个节点按照”右右情况”旋转,旋转后“节点10"的

左孩子放入到L树的right节点,"节点10”作为中间树M,最后将20插入根节点。

③: 之字形

之字形有点类似AVL中的“双旋转”,不过人家采取的策略是不一样的,当我们试插入“节点9”,同样发现“父节点”是根的右儿子,并且

“节点9”要插入到父节点的内侧,根据规则,需要将“父节点10”作为M树中的根节点,“节点7”作为L树中的right节点,然后M拼接L和R,

最后将节点9插入到根上。

3:基本操作

①:节点定义

我们还是采用普通二叉树中的节点定义,也就没有了AVL那么烦人的高度信息。

 1     public class BinaryNode<T>
 2     {
 3         // Constructors
 4         public BinaryNode(T theElement) : this(theElement, null, null) { }
 5
 6         public BinaryNode(T theElement, BinaryNode<T> lt, BinaryNode<T> rt)
 7         {
 8             element = theElement;
 9             left = lt;
10             right = rt;
11         }
12
13         public T element;
14
15         public BinaryNode<T> left;
16
17         public BinaryNode<T> right;
18     }

②:伸展

这里为了编写代码方便,我采用的是逻辑nullNode节点,具体伸展逻辑大家可以看上面的图。

 1         #region 伸展
 2         /// <summary>
 3         /// 伸展
 4         /// </summary>
 5         /// <param name="Key"></param>
 6         /// <param name="tree"></param>
 7         /// <returns></returns>
 8         public BinaryNode<T> Splay(T Key, BinaryNode<T> tree)
 9         {
10             BinaryNode<T> leftTreeMax, rightTreeMin;
11
12             header.left = header.right = nullNode;
13
14             leftTreeMax = rightTreeMin = header;
15
16             nullNode.element = Key;
17
18             while (true)
19             {
20                 int compareResult = Key.CompareTo(tree.element);
21
22                 if (compareResult < 0)
23                 {
24                     //如果成立,说明是”一字型“旋转
25                     if (Key.CompareTo(tree.left.element) < 0)
26                         tree = rotateWithLeftChild(tree);
27
28                     if (tree.left == nullNode)
29                         break;
30
31                     //动态的将中间树的”当前节点“追加到 R 树中,同时备份在header中
32                     rightTreeMin.left = tree;
33
34                     rightTreeMin = tree;
35
36                     tree = tree.left;
37                 }
38                 else if (compareResult > 0)
39                 {
40                     //如果成立,说明是”一字型“旋转
41                     if (Key.CompareTo(tree.right.element) > 0)
42                         tree = rotateWithRightChild(tree);
43
44                     if (tree.right == nullNode)
45                         break;
46
47                     //动态的将中间树的”当前节点“追加到 L 树中,同时备份在header中
48                     leftTreeMax.right = tree;
49
50                     leftTreeMax = tree;
51
52                     tree = tree.right;
53                 }
54                 else
55                 {
56                     break;
57                 }
58             }
59
60             /* 剥到最后一层,来最后一次切分 */
61             //把中间树的左孩子给“左树”
62             leftTreeMax.right = tree.left;
63
64             //把中间树的右孩子给“右树”
65             rightTreeMin.left = tree.right;
66
67             /* 合并操作 */
68             //将头节点的左树作为中间树的左孩子
69             tree.left = header.right;
70
71             //将头结点的右树作为中间树的右孩子
72             tree.right = header.left;
73
74             return tree;
75         }
76         #endregion

③:插入

插入操作关键在于我们要找到接近于”要插入点“的节点,然后顶成“根节点”,也就是上面三分图中的最后一分。

 1 #region 插入
 2         /// <summary>
 3         /// 插入
 4         /// </summary>
 5         /// <param name="Key"></param>
 6         public void Insert(T Key)
 7         {
 8             if (newNode == null)
 9                 newNode = new BinaryNode<T>(default(T));
10
11             newNode.element = Key;
12
13             if (root == nullNode)
14             {
15                 newNode.left = newNode.right = nullNode;
16
17                 root = newNode;
18             }
19             else
20             {
21                 root = Splay(Key, root);
22
23                 int compareResult = Key.CompareTo(root.element);
24
25                 if (compareResult < 0)
26                 {
27                     newNode.left = root.left;
28
29                     newNode.right = root;
30
31                     root.left = nullNode;
32
33                     root = newNode;
34                 }
35                 else
36                     if (compareResult > 0)
37                     {
38                         newNode.right = root.right;
39
40                         newNode.left = root;
41
42                         root.right = nullNode;
43
44                         root = newNode;
45                     }
46                     else
47                         return;
48             }
49
50             newNode = null;
51         }
52         #endregion

④:删除

删除操作也要将节点伸展到根上,然后进行删除,逻辑很简单。

 1  #region 删除
 2         /// <summary>
 3         /// 删除
 4         /// </summary>
 5         /// <param name="Key"></param>
 6         public void Remove(T Key)
 7         {
 8             BinaryNode<T> newTree;
 9
10             //将删除结点顶到根节点
11             root = Splay(Key, root);
12
13             //不等于说明没有找到
14             if (root.element.CompareTo(Key) != 0)
15                 return;
16
17             //如果左边为空,则直接用root的右孩子接上去
18             if (root.left == nullNode)
19             {
20                 newTree = root.right;
21             }
22             else
23             {
24                 newTree = root.left;
25
26                 newTree = Splay(Key, newTree);
27
28                 newTree.right = root.right;
29             }
30             root = newTree;
31         }
32         #endregion

总的运行代码如下:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5
  6 namespace DataStructSplay
  7 {
  8     public class BinaryNode<T>
  9     {
 10         public BinaryNode(T theElement) : this(theElement, null, null) { }
 11
 12         public BinaryNode(T theElement, BinaryNode<T> lt, BinaryNode<T> rt)
 13         {
 14             element = theElement;
 15             left = lt;
 16             right = rt;
 17         }
 18
 19         public T element;
 20
 21         public BinaryNode<T> left;
 22
 23         public BinaryNode<T> right;
 24     }
 25
 26     public class SplayTree<T> where T : IComparable
 27     {
 28         public BinaryNode<T> root;
 29
 30         public BinaryNode<T> nullNode;
 31
 32         public BinaryNode<T> header = new BinaryNode<T>(default(T));
 33
 34         public BinaryNode<T> newNode;
 35
 36         public SplayTree()
 37         {
 38             nullNode = new BinaryNode<T>(default(T));
 39
 40             nullNode.left = nullNode.right = nullNode;
 41
 42             root = nullNode;
 43         }
 44
 45         #region 插入
 46         /// <summary>
 47         /// 插入
 48         /// </summary>
 49         /// <param name="Key"></param>
 50         public void Insert(T Key)
 51         {
 52             if (newNode == null)
 53                 newNode = new BinaryNode<T>(default(T));
 54
 55             newNode.element = Key;
 56
 57             if (root == nullNode)
 58             {
 59                 newNode.left = newNode.right = nullNode;
 60
 61                 root = newNode;
 62             }
 63             else
 64             {
 65                 root = Splay(Key, root);
 66
 67                 int compareResult = Key.CompareTo(root.element);
 68
 69                 if (compareResult < 0)
 70                 {
 71                     newNode.left = root.left;
 72
 73                     newNode.right = root;
 74
 75                     root.left = nullNode;
 76
 77                     root = newNode;
 78                 }
 79                 else
 80                     if (compareResult > 0)
 81                     {
 82                         newNode.right = root.right;
 83
 84                         newNode.left = root;
 85
 86                         root.right = nullNode;
 87
 88                         root = newNode;
 89                     }
 90                     else
 91                         return;
 92             }
 93
 94             newNode = null;
 95         }
 96         #endregion
 97
 98         #region 是否包含
 99         /// <summary>
100         /// 是否包含
101         /// </summary>
102         /// <param name="Key"></param>
103         /// <returns></returns>
104         public bool Contains(T Key)
105         {
106             if (isEmpty())
107                 return false;
108
109             root = Splay(Key, root);
110
111             return root.element.CompareTo(Key) == 0;
112         }
113         #endregion
114
115         #region 判断是否为空
116         /// <summary>
117         /// 判断是否为空
118         /// </summary>
119         /// <returns></returns>
120         public bool isEmpty()
121         {
122             return root == nullNode;
123         }
124         #endregion
125
126         #region 伸展
127         /// <summary>
128         /// 伸展
129         /// </summary>
130         /// <param name="Key"></param>
131         /// <param name="tree"></param>
132         /// <returns></returns>
133         public BinaryNode<T> Splay(T Key, BinaryNode<T> tree)
134         {
135             BinaryNode<T> leftTreeMax, rightTreeMin;
136
137             header.left = header.right = nullNode;
138
139             leftTreeMax = rightTreeMin = header;
140
141             nullNode.element = Key;
142
143             while (true)
144             {
145                 int compareResult = Key.CompareTo(tree.element);
146
147                 if (compareResult < 0)
148                 {
149                     //如果成立,说明是”一字型“旋转
150                     if (Key.CompareTo(tree.left.element) < 0)
151                         tree = rotateWithLeftChild(tree);
152
153                     if (tree.left == nullNode)
154                         break;
155
156                     //动态的将中间树的”当前节点“追加到 R 树中,同时备份在header中
157                     rightTreeMin.left = tree;
158
159                     rightTreeMin = tree;
160
161                     tree = tree.left;
162                 }
163                 else if (compareResult > 0)
164                 {
165                     //如果成立,说明是”一字型“旋转
166                     if (Key.CompareTo(tree.right.element) > 0)
167                         tree = rotateWithRightChild(tree);
168
169                     if (tree.right == nullNode)
170                         break;
171
172                     //动态的将中间树的”当前节点“追加到 L 树中,同时备份在header中
173                     leftTreeMax.right = tree;
174
175                     leftTreeMax = tree;
176
177                     tree = tree.right;
178                 }
179                 else
180                 {
181                     break;
182                 }
183             }
184
185             /* 剥到最后一层,来最后一次切分 */
186             //把中间树的左孩子给“左树”
187             leftTreeMax.right = tree.left;
188
189             //把中间树的右孩子给“右树”
190             rightTreeMin.left = tree.right;
191
192             /* 合并操作 */
193             //将头节点的左树作为中间树的左孩子
194             tree.left = header.right;
195
196             //将头结点的右树作为中间树的右孩子
197             tree.right = header.left;
198
199             return tree;
200         }
201         #endregion
202
203         #region 删除
204         /// <summary>
205         /// 删除
206         /// </summary>
207         /// <param name="Key"></param>
208         public void Remove(T Key)
209         {
210             BinaryNode<T> newTree;
211
212             //将删除结点顶到根节点
213             root = Splay(Key, root);
214
215             //不等于说明没有找到
216             if (root.element.CompareTo(Key) != 0)
217                 return;
218
219             //如果左边为空,则直接用root的右孩子接上去
220             if (root.left == nullNode)
221             {
222                 newTree = root.right;
223             }
224             else
225             {
226                 newTree = root.left;
227
228                 newTree = Splay(Key, newTree);
229
230                 newTree.right = root.right;
231             }
232             root = newTree;
233         }
234         #endregion
235
236         #region 右旋转
237         /// <summary>
238         /// 右旋转
239         /// </summary>
240         /// <param name="k1"></param>
241         /// <returns></returns>
242         public BinaryNode<T> rotateWithRightChild(BinaryNode<T> k1)
243         {
244             BinaryNode<T> k2 = k1.right;
245             k1.right = k2.left;
246             k2.left = k1;
247             return k2;
248         }
249         #endregion
250
251         #region 左旋转
252         /// <summary>
253         /// 左旋转
254         /// </summary>
255         /// <param name="k2"></param>
256         /// <returns></returns>
257         public BinaryNode<T> rotateWithLeftChild(BinaryNode<T> k2)
258         {
259             BinaryNode<T> k1 = k2.left;
260             k2.left = k1.right;
261             k1.right = k2;
262             return k1;
263         }
264         #endregion
265     }
266 }

伸展树可以总结成一幅图:

时间: 2024-11-08 10:09:11

6天通吃树结构—— 第四天 伸展树的相关文章

6天通吃树结构—— 第五天 Trie树

原文:6天通吃树结构-- 第五天 Trie树 很有段时间没写此系列了,今天我们来说Trie树,Trie树的名字有很多,比如字典树,前缀树等等. 一:概念 下面我们有and,as,at,cn,com这些关键词,那么如何构建trie树呢? 从上面的图中,我们或多或少的可以发现一些好玩的特性. 第一:根节点不包含字符,除根节点外的每一个子节点都包含一个字符. 第二:从根节点到某一节点,路径上经过的字符连接起来,就是该节点对应的字符串. 第三:每个单词的公共前缀作为一个字符节点保存. 二:使用范围 既然

6天通吃树结构—— 第三天 Treap树

原文:6天通吃树结构-- 第三天 Treap树 我们知道,二叉查找树相对来说比较容易形成最坏的链表情况,所以前辈们想尽了各种优化策略,包括AVL,红黑,以及今天 要讲的Treap树. Treap树算是一种简单的优化策略,这名字大家也能猜到,树和堆的合体,其实原理比较简单,在树中维护一个"优先级“,”优先级“ 采用随机数的方法,但是”优先级“必须满足根堆的性质,当然是“大根堆”或者“小根堆”都无所谓,比如下面的一棵树: 从树中我们可以看到: ①:节点中的key满足“二叉查找树”. ②:节点中的“优

6天通吃树结构—— 第二天 平衡二叉树

原文:6天通吃树结构-- 第二天 平衡二叉树 上一篇我们聊过,二叉查找树不是严格的O(logN),导致了在真实场景中没有用武之地,谁也不愿意有O(N)的情况发生, 作为一名码农,肯定会希望能把“范围查找”做到地球人都不能优化的地步. 当有很多数据灌到我的树中时,我肯定会希望最好是以“完全二叉树”的形式展现,这样我才能做到“查找”是严格的O(logN), 比如把这种”树“调正到如下结构. 这里就涉及到了“树节点”的旋转,也是我们今天要聊到的内容. 一:平衡二叉树(AVL) 1:定义 父节点的左子树

6天通吃树结构—— 第一天 二叉查找树

原文:6天通吃树结构-- 第一天 二叉查找树 一直很想写一个关于树结构的专题,再一个就是很多初级点的码农会认为树结构无用论,其实归根到底还是不清楚树的实际用途. 一:场景: 1:现状 前几天我的一个大学同学负责的网站出现了严重的性能瓶颈,由于业务是写入和读取都是密集型,如果做缓存,时间间隔也只能在30s左 右,否则就会引起客户纠纷,所以同学也就没有做缓存,通过测试发现慢就慢在数据读取上面,总共需要10s,天啊...原来首页的加载关联 到了4张表,而且表数据中最多的在10w条以上,可以想象4张巨大

PHP算法 《树形结构》 之 伸展树(1) - 基本概念

伸展树的介绍 1.出处:http://www.cnblogs.com/skywang12345/p/3604238.html 伸展树(Splay Tree)是一种二叉排序树,它能在O(log n)内完成插入.查找和删除操作.它由Daniel Sleator和Robert Tarjan创造.(01) 伸展树属于二叉查找树,即它具有和二叉查找树一样的性质:假设x为树中的任意一个结点,x节点包含关键字key,节点x的key值记为key[x].如果y是x的左子树中的一个结点,则key[y] <= key

结构之美——优先队列基本结构(四)——二叉堆、d堆、左式堆、斜堆

实现优先队列结构主要是通过堆完成,主要有:二叉堆.d堆.左式堆.斜堆.二项堆.斐波那契堆.pairing 堆等. 1. 二叉堆 1.1. 定义 完全二叉树,根最小. 存储时使用层序. 1.2. 操作 (1). insert(上滤) 插入末尾 26,不断向上比较,大于26则交换位置,小于则停止. (2). deleteMin(下滤) 提取末尾元素,放在堆顶,不断下滤: (3). 其他操作: 都是基于insert(上滤)与deleteMin(下滤)的操作. 减小元素:减小节点的值,上滤调整堆. 增大

Splay Tree(伸展树)

参考:<数据结构(C++语言版)>邓俊辉著 (好书 一. 伸展树(由 D. D. Sleator 和 R. E. Tarjan 于 1985 年发明)也是平衡二叉搜索树的一种形式.相对于 AVL 树,伸展树的实现更为简洁 伸展树无需时刻都严格地保持全树的平衡,但却能够在任何足够长的真实操作序列中,保持分摊意义上的高效率 伸展树也不需要对基本的二叉树节点结构做任何附加的要求或改动,更不需要记录平衡因子或高度之类的额外信息,故适用范围更广 二.局部性 信息处理的典型模式是,将所有的数据项视作一个集

关于树论【伸展树】

搬运:搞了LCT后想来回顾一下伸展树. 用codevs的2443为例吧. 讲讲结构,函数的意思,写了注释. 首先他是一棵二叉树,并且是可以动的. 然后他有个性质,点x左子树(没错是整个子树)的值恒小于x的值,右子树(没错是整个子树)的值恒大于x的值. 然后他可以转.看,这下3变成根了.因为这样,我们就可以找到适合新来的点插入的位置,以及删除点时保持结构.  #include<cstdio> #include<cstring> using namespace std; int roo

Splay(伸展树、分裂树):平衡二叉搜索树中功能最丰富的树

这是我第一篇对高级数据结构的描述,如有不准确的地方还请指出,谢谢~ 调这颗树的代码调的头皮发麻,和线段树根本不是一个难度的. 首先简单地介绍一下这棵平衡BST中的另类 这是一棵没有任何平衡因子的BST,它依靠均摊来达到O(logn)的插入查询和删除复杂度,常数比较大 而且,它的具有其他BST所不具备的,对于子树的任意分裂和合并的功能 下面我从定义讲起,剖析这棵树实现过程中的每一个细节 const int INF=1000000000; const int maxn=1000005; int n,