伸展树(Splay Tree)是一种二叉搜索树,它能在O(log n)内完成插入、查找和删除操作。它由Daniel Sleator和Robert Tarjan创造。它的优势在于不需要记录用于平衡树的冗余信息。在伸展树上的一般操作都基于伸展操作。
为什么需要伸展树(Splay Tree)
各种二叉搜索树存在不足。比如:对于一个有n个节点的平衡 二叉搜索树 ,虽然最坏情况下每次查找的时间复杂度不会超过O(logn),但是如果访问模式不均匀,平衡树的效率就会受到影响。此外,它们还需要额外的空间来存储平衡信息(结点的高度)。
这些 二叉搜索 树的设计目标都是减少最坏情况下单次操作时间,但是 二叉搜索 树 的典型应用经常需要执行一系列的查找操作,此时更关心的性能指标是所有这些操作总共需要多少时间。对于此类应用,更好的目标就是降低操作的摊平时间,此处的摊平时间是指在一系列最坏情况的操作序列中单次操作的平均时间。获得摊平效率的一种方法就是使用“自调整”的数据结构。
和平衡的或是其它对结构有明确限制的数据结构比起来,自调整数据结构有以下几个优点:
1、从摊平角度而言,它们忽略常量因子,因此绝对不会比有明确限制的数据结构差。而且由于它们可以根据使用情况进行调整,于是在使用模式不均匀的情况下更加有效。
2、由于无需存储平衡或者其它的限制信息,它们所需的空间更小。
3、它们的查找和更新算法概念简单,易于实现。
当然,自调整结构也有潜在的缺点:
1、它们需要更多的局部调整,尤其是在查找期间。(那些有明确限制的数据结构仅需在更新期间进行调整,查找期间则不用)
2、一系列查找操作中的某一个可能会耗时较长,这在实时应用程序中可能是个不足之处。
什么是伸展树(Splay Tree)
假设想要对一个二叉搜索树执行一系列的查找操作。为了使整个查找时间更小,被查频率高的那些条目就应当经常处于靠近树根的位置。于是想到设计一个简单方法,在每次查找之后对树进行重构,把被查找的条目搬移到离树根近一些的地方。splay tree应运而生。splay tree是一种自调整形式的二叉搜索树,它会沿着从某个节点到树根之间的路径,通过一系列的旋转把这个节点搬移到树根去。
两种重构方法:
1、单旋:在查找完位于节点x中的条目i之后,旋转链接x和其父节点的边。(除非x就是树根)
2、搬移至树根:在查找完位于节点x中的条目i之后,旋转链接x和其父节点的边,然后重复这个操作直至x成为树根。
旋转示意图:
![](http://img2.tuicool.com/In2Mbe.jpg!web)
伸展树的自底向上伸展(bottom-up splay)
splay tree的重构方法和搬移至树根的方法相似,它也会沿着查找路径做自底向上的旋转,将被查找条目移至树根。但不同的是,它的旋转是成对进行的,顺序取决于查找路径的结构。为了在节点x处对树进行splay操作,我们需要重复下面的步骤,直至x成为树根为止:
注:图中 R-当前节点 Q-父亲节点 P-祖父节点
1、第一种情况:如果x的父节点p(x)是树根,则旋转连接x和p(x)的边。(这种情况是最后一步)
![](http://img1.tuicool.com/ZBZ7be.jpg!web)
2、第二种情况:如果p(x)不是树根,而且x和p(x)本身都是左孩子或者都是右孩子,则先旋转连接p(x)和x的祖父节点g(x)的边,然后再旋转连接x和p(x)的边。
![](http://img2.tuicool.com/AB7VFjV.jpg!web)
3、第三种情况:如果p(x)不是树根,而且x是左孩子,p(x)是右孩子,或者相反,则先旋转连接x和p(x)的边,再旋转连接x和新的p(x)的边。
![](http://img0.tuicool.com/mYneEn.jpg!web)
在节点x处进行splay操作的时间是和查找x所需的时间成比例的。splay操作不单是把x搬移到了树根,而且还把查找路径上的每个节点的深度都大致减掉了一半。
伸展树的C++实现
1. 基本定义
1.1 节点
template <class T>
class SplayTreeNode{
public:
T key; // 关键字(键值)
SplayTreeNode *left; // 左孩子
SplayTreeNode *right; // 右孩子
<span class="hljs-type">SplayTreeNode</span>():left(<span class="hljs-type">NULL</span>),right(<span class="hljs-type">NULL</span>) {}
<span class="hljs-type">SplayTreeNode</span>(<span class="hljs-type">T</span> <span class="hljs-title">value</span>, <span class="hljs-type">SplayTreeNode</span> *<span class="hljs-title">l</span>, <span class="hljs-type">SplayTreeNode</span> *<span class="hljs-title">r</span>):
key(<span class="hljs-title">value</span>), left(<span class="hljs-title">l</span>),right(<span class="hljs-title">r</span>) {}
};
SplayTreeNode是伸展树节点对应的类。它包括的几个组成元素:
(01) key -- 是关键字,是用来对伸展树的节点进行排序的。
(02) left -- 是左孩子。
(03) right -- 是右孩子。
1.2 伸展树
template <class T>
class SplayTree {
private:
SplayTreeNode<T> *mRoot; // 根结点
public:
<span class="hljs-type">SplayTree</span>();
~<span class="hljs-type">SplayTree</span>();
// 前序遍历"伸展树"
void preOrder();
// 中序遍历"伸展树"
void inOrder();
// 后序遍历"伸展树"
void postOrder();
// (递归实现)查找"伸展树"中键值为key的节点
<span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* search(<span class="hljs-type">T</span> <span class="hljs-title">key</span>);
// (非递归实现)查找"伸展树"中键值为key的节点
<span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* iterativeSearch(<span class="hljs-type">T</span> <span class="hljs-title">key</span>);
// 查找最小结点:返回最小结点的键值。
<span class="hljs-type">T</span> minimum();
// 查找最大结点:返回最大结点的键值。
<span class="hljs-type">T</span> maximum();
// 旋转key对应的节点为根节点,并返回值为根节点。
void splay(<span class="hljs-type">T</span> <span class="hljs-title">key</span>);
// 将结点(<span class="hljs-title">key</span>为节点键值)插入到伸展树中
void insert(<span class="hljs-type">T</span> <span class="hljs-title">key</span>);
// 删除结点(<span class="hljs-title">key</span>为节点键值)
void remove(<span class="hljs-type">T</span> <span class="hljs-title">key</span>);
// 销毁伸展树
void destroy();
// 打印伸展树
void print();
private:
// 前序遍历"伸展树"
void preOrder(<span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* <span class="hljs-title">tree</span>) const;
// 中序遍历"伸展树"
void inOrder(<span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* <span class="hljs-title">tree</span>) const;
// 后序遍历"伸展树"
void postOrder(<span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* <span class="hljs-title">tree</span>) const;
// (递归实现)查找"伸展树x"中键值为key的节点
<span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* search(<span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* <span class="hljs-title">x</span>, <span class="hljs-type">T</span> <span class="hljs-title">key</span>) const;
// (非递归实现)查找"伸展树x"中键值为key的节点
<span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* iterativeSearch(<span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* <span class="hljs-title">x</span>, <span class="hljs-type">T</span> <span class="hljs-title">key</span>) const;
// 查找最小结点:返回tree为根结点的伸展树的最小结点。
<span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* minimum(<span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* <span class="hljs-title">tree</span>);
// 查找最大结点:返回tree为根结点的伸展树的最大结点。
<span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* maximum(<span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* <span class="hljs-title">tree</span>);
// 旋转key对应的节点为根节点,并返回值为根节点。
<span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* splay(<span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* <span class="hljs-title">tree</span>, <span class="hljs-type">T</span> <span class="hljs-title">key</span>);
// 将结点(<span class="hljs-title">z</span>)插入到伸展树(<span class="hljs-title">tree</span>)中
<span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* insert(<span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* &<span class="hljs-title">tree</span>, <span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* <span class="hljs-title">z</span>);
// 删除伸展树(<span class="hljs-title">tree</span>)中的结点(键值为<span class="hljs-title">key</span>),并返回被删除的结点
<span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* remove(<span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* &<span class="hljs-title">tree</span>, <span class="hljs-type">T</span> <span class="hljs-title">key</span>);
// 销毁伸展树
void destroy(<span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* &<span class="hljs-title">tree</span>);
// 打印伸展树
void print(<span class="hljs-type">SplayTreeNode</span><<span class="hljs-type">T</span>>* <span class="hljs-title">tree</span>, <span class="hljs-type">T</span> <span class="hljs-title">key</span>, <span class="hljs-title">int</span> <span class="hljs-title">direction</span>);
};
SplayTree是伸展树对应的类。它包括根节点mRoot和伸展树的函数接口。
2. 旋转
旋转是伸展树中需要重点关注的,它的代码如下:
/*
* 旋转key对应的节点为根节点,并返回值为根节点。
*
* 注意:
* (a):伸展树中存在"键值为key的节点"。
* 将"键值为key的节点"旋转为根节点。
* (b):伸展树中不存在"键值为key的节点",并且key < tree->key。
* b-1 "键值为key的节点"的前驱节点存在的话,将"键值为key的节点"的前驱节点旋转为根节点。
* b-2 "键值为key的节点"的前驱节点存在的话,则意味着,key比树中任何键值都小,那么此时,将最小节点旋转为根节点。
* (c):伸展树中不存在"键值为key的节点",并且key > tree->key。
* c-1 "键值为key的节点"的后继节点存在的话,将"键值为key的节点"的后继节点旋转为根节点。
* c-2 "键值为key的节点"的后继节点不存在的话,则意味着,key比树中任何键值都大,那么此时,将最大节点旋转为根节点。
*/
template <class T>
SplayTreeNode<T>* SplayTree<T>::splay(SplayTreeNode<T>* tree, T key)
{
SplayTreeNode<T> N, *l, *r, *c;
<span class="hljs-keyword">if</span> (tree == <span class="hljs-type">NULL</span>)
<span class="hljs-keyword">return</span> tree;
<span class="hljs-type">N</span>.<span class="hljs-keyword">left</span> = <span class="hljs-type">N</span>.<span class="hljs-keyword">right</span> = <span class="hljs-type">NULL</span>;
l = r = &<span class="hljs-type">N</span>;
<span class="hljs-keyword">for</span> (;;)
{
<span class="hljs-keyword">if</span> (key < tree->key)
{
<span class="hljs-keyword">if</span> (tree-><span class="hljs-keyword">left</span> == <span class="hljs-type">NULL</span>)
<span class="hljs-keyword">break</span>;
<span class="hljs-keyword">if</span> (key < tree-><span class="hljs-keyword">left</span>->key)
{
<span class="hljs-built_in">c</span> = tree-><span class="hljs-keyword">left</span>; <span class="hljs-comment">/* rotate right */</span>
tree-><span class="hljs-keyword">left</span> = <span class="hljs-built_in">c</span>-><span class="hljs-keyword">right</span>;
<span class="hljs-built_in">c</span>-><span class="hljs-keyword">right</span> = tree;
tree = <span class="hljs-built_in">c</span>;
<span class="hljs-keyword">if</span> (tree-><span class="hljs-keyword">left</span> == <span class="hljs-type">NULL</span>)
<span class="hljs-keyword">break</span>;
}
r-><span class="hljs-keyword">left</span> = tree; <span class="hljs-comment">/* link right */</span>
r = tree;
tree = tree-><span class="hljs-keyword">left</span>;
}
<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (key > tree->key)
{
<span class="hljs-keyword">if</span> (tree-><span class="hljs-keyword">right</span> == <span class="hljs-type">NULL</span>)
<span class="hljs-keyword">break</span>;
<span class="hljs-keyword">if</span> (key > tree-><span class="hljs-keyword">right</span>->key)
{
<span class="hljs-built_in">c</span> = tree-><span class="hljs-keyword">right</span>; <span class="hljs-comment">/* rotate left */</span>
tree-><span class="hljs-keyword">right</span> = <span class="hljs-built_in">c</span>-><span class="hljs-keyword">left</span>;
<span class="hljs-built_in">c</span>-><span class="hljs-keyword">left</span> = tree;
tree = <span class="hljs-built_in">c</span>;
<span class="hljs-keyword">if</span> (tree-><span class="hljs-keyword">right</span> == <span class="hljs-type">NULL</span>)
<span class="hljs-keyword">break</span>;
}
l-><span class="hljs-keyword">right</span> = tree; <span class="hljs-comment">/* link left */</span>
l = tree;
tree = tree-><span class="hljs-keyword">right</span>;
}
<span class="hljs-keyword">else</span>
{
<span class="hljs-keyword">break</span>;
}
}
l-><span class="hljs-keyword">right</span> = tree-><span class="hljs-keyword">left</span>; <span class="hljs-comment">/* assemble */</span>
r-><span class="hljs-keyword">left</span> = tree-><span class="hljs-keyword">right</span>;
tree-><span class="hljs-keyword">left</span> = <span class="hljs-type">N</span>.<span class="hljs-keyword">right</span>;
tree-><span class="hljs-keyword">right</span> = <span class="hljs-type">N</span>.<span class="hljs-keyword">left</span>;
<span class="hljs-keyword">return</span> tree;
}
template <class T>
void SplayTree<T>::splay(T key)
{
mRoot = splay(mRoot, key);
}
上面的代码的作用:将"键值为key的节点"旋转为根节点,并返回根节点。它的处理情况共包括:
(a):伸展树中存在"键值为key的节点"。
将"键值为key的节点"旋转为根节点。
(b):伸展树中不存在"键值为key的节点",并且key < tree->key。
b-1) "键值为key的节点"的前驱节点存在的话,将"键值为key的节点"的前驱节点旋转为根节点。
b-2) "键值为key的节点"的前驱节点存在的话,则意味着,key比树中任何键值都小,那么此时,将最小节点旋转为根节点。
(c):伸展树中不存在"键值为key的节点",并且key > tree->key。
c-1) "键值为key的节点"的后继节点存在的话,将"键值为key的节点"的后继节点旋转为根节点。
c-2) "键值为key的节点"的后继节点不存在的话,则意味着,key比树中任何键值都大,那么此时,将最大节点旋转为根节点。
下面列举个例子分别对a进行说明。
在下面的伸展树中查找10,共包括"右旋" --> "右链接" --> "组合"这3步。
(01) 右旋
对应代码中的"rotate right"部分
(02) 右链接
对应代码中的"link right"部分
(03) 组合
对应代码中的"assemble"部分
提示:如果在上面的伸展树中查找"70",则正好与"示例1"对称,而对应的操作则分别是"rotate left", "link left"和"assemble"。
其它的情况,例如"查找15是b-1的情况,查找5是b-2的情况"等等,这些都比较简单,大家可以自己分析。
3. 插入
插入代码
/*
* 将结点插入到伸展树中,并返回根节点
*
* 参数说明:
* tree 伸展树的根结点
* key 插入的结点的键值
* 返回值:
* 根节点
*/
template <class T>
SplayTreeNode<T>* SplayTree<T>::insert(SplayTreeNode<T>* &tree, SplayTreeNode<T>* z)
{
SplayTreeNode<T> *y = NULL;
SplayTreeNode<T> *x = tree;
<span class="hljs-comment">// 查找z的插入位置</span>
<span class="hljs-keyword">while</span> (x != <span class="hljs-literal">NULL</span>)
{
y = x;
<span class="hljs-keyword">if</span> (z->key < x->key)
x = x->left;
<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (z->key > x->key)
x = x->right;
<span class="hljs-keyword">else</span>
{
<span class="hljs-built_in">cout</span> << <span class="hljs-string">"不允许插入相同节点("</span> << z->key << <span class="hljs-string">")!"</span> << <span class="hljs-built_in">endl</span>;
<span class="hljs-keyword">delete</span> z;
<span class="hljs-keyword">return</span> tree;
}
}
<span class="hljs-keyword">if</span> (y==<span class="hljs-literal">NULL</span>)
tree = z;
<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (z->key < y->key)
y->left = z;
<span class="hljs-keyword">else</span>
y->right = z;
<span class="hljs-keyword">return</span> tree;
}
template <class T>
void SplayTree<T>::insert(T key)
{
SplayTreeNode<T> *z=NULL;
<span class="hljs-comment">// 如果新建结点失败,则返回。</span>
<span class="hljs-keyword">if</span> ((z=<span class="hljs-keyword">new</span> SplayTreeNode<T>(key,<span class="hljs-literal">NULL</span>,<span class="hljs-literal">NULL</span>)) == <span class="hljs-literal">NULL</span>)
<span class="hljs-keyword">return</span> ;
<span class="hljs-comment">// 插入节点</span>
mRoot = insert(mRoot, z);
<span class="hljs-comment">// 将节点(key)旋转为根节点</span>
mRoot = splay(mRoot, key);
}
insert(key)是提供给外部的接口,它的作用是新建节点(节点的键值为key),并将节点插入到伸展树中;然后,将该节点旋转为根节点。
insert(tree, z)是内部接口,它的作用是将节点z插入到tree中。insert(tree, z)在将z插入到tree中时,仅仅只将tree当作是一棵二叉查找树,而且不允许插入相同节点。
4. 删除
删除代码
/*
* 删除结点(节点的键值为key),返回根节点
*
* 参数说明:
* tree 伸展树的根结点
* key 待删除结点的键值
* 返回值:
* 根节点
*/
template <class T>
SplayTreeNode<T>* SplayTree<T>::remove(SplayTreeNode<T>* &tree, T key)
{
SplayTreeNode<T> *x;
<span class="hljs-keyword">if</span> (tree == <span class="hljs-literal">NULL</span>)
<span class="hljs-keyword">return</span> <span class="hljs-literal">NULL</span>;
<span class="hljs-comment">// 查找键值为key的节点,找不到的话直接返回。</span>
<span class="hljs-keyword">if</span> (search(tree, key) == <span class="hljs-literal">NULL</span>)
<span class="hljs-keyword">return</span> tree;
<span class="hljs-comment">// 将key对应的节点旋转为根节点。</span>
tree = splay(tree, key);
<span class="hljs-keyword">if</span> (tree->left != <span class="hljs-literal">NULL</span>)
{
<span class="hljs-comment">// 将"tree的前驱节点"旋转为根节点</span>
x = splay(tree->left, key);
<span class="hljs-comment">// 移除tree节点</span>
x->right = tree->right;
}
<span class="hljs-keyword">else</span>
x = tree->right;
<span class="hljs-keyword">delete</span> tree;
<span class="hljs-keyword">return</span> x;
}
template <class T>
void SplayTree<T>::remove(T key)
{
mRoot = remove(mRoot, key);
}
remove(key)是外部接口,remove(tree, key)是内部接口。
remove(tree, key)的作用是:删除伸展树中键值为key的节点。
它会先在伸展树中查找键值为key的节点。若没有找到的话,则直接返回。若找到的话,则将该节点旋转为根节点,然后再删除该节点。
伸展树的C++实现完整源代码:
(1)实现程序
#ifndef _SPLAY_TREE_HPP_
#define _SPLAY_TREE_HPP_
using namespace std;
template <class T>
class SplayTreeNode{
public:
T key; // 关键字(键值)
SplayTreeNode *left; // 左孩子
SplayTreeNode *right; // 右孩子
SplayTreeNode():left(<span class="hljs-literal">NULL</span>),right(<span class="hljs-literal">NULL</span>) {}
SplayTreeNode(T value, SplayTreeNode *l, SplayTreeNode *r):
key(value), left(l),right(r) {}
};
template <class T>
class SplayTree {
private:
SplayTreeNode<T> *mRoot; // 根结点
<span class="hljs-keyword">public</span>:
SplayTree();
~SplayTree();
<span class="hljs-comment">// 前序遍历"伸展树"</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">preOrder</span><span class="hljs-params">()</span></span>;
<span class="hljs-comment">// 中序遍历"伸展树"</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">inOrder</span><span class="hljs-params">()</span></span>;
<span class="hljs-comment">// 后序遍历"伸展树"</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">postOrder</span><span class="hljs-params">()</span></span>;
<span class="hljs-comment">// (递归实现)查找"伸展树"中键值为key的节点</span>
SplayTreeNode<T>* search(T key);
<span class="hljs-comment">// (非递归实现)查找"伸展树"中键值为key的节点</span>
SplayTreeNode<T>* iterativeSearch(T key);
<span class="hljs-comment">// 查找最小结点:返回最小结点的键值。</span>
<span class="hljs-function">T <span class="hljs-title">minimum</span><span class="hljs-params">()</span></span>;
<span class="hljs-comment">// 查找最大结点:返回最大结点的键值。</span>
<span class="hljs-function">T <span class="hljs-title">maximum</span><span class="hljs-params">()</span></span>;
<span class="hljs-comment">// 旋转key对应的节点为根节点,并返回值为根节点。</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">splay</span><span class="hljs-params">(T key)</span></span>;
<span class="hljs-comment">// 将结点(key为节点键值)插入到伸展树中</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">insert</span><span class="hljs-params">(T key)</span></span>;
<span class="hljs-comment">// 删除结点(key为节点键值)</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">remove</span><span class="hljs-params">(T key)</span></span>;
<span class="hljs-comment">// 销毁伸展树</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">destroy</span><span class="hljs-params">()</span></span>;
<span class="hljs-comment">// 打印伸展树</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">print</span><span class="hljs-params">()</span></span>;
<span class="hljs-keyword">private</span>:
<span class="hljs-comment">// 前序遍历"伸展树"</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">preOrder</span><span class="hljs-params">(SplayTreeNode<T>* tree)</span> <span class="hljs-keyword">const</span></span>;
<span class="hljs-comment">// 中序遍历"伸展树"</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">inOrder</span><span class="hljs-params">(SplayTreeNode<T>* tree)</span> <span class="hljs-keyword">const</span></span>;
<span class="hljs-comment">// 后序遍历"伸展树"</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">postOrder</span><span class="hljs-params">(SplayTreeNode<T>* tree)</span> <span class="hljs-keyword">const</span></span>;
<span class="hljs-comment">// (递归实现)查找"伸展树x"中键值为key的节点</span>
SplayTreeNode<T>* search(SplayTreeNode<T>* x, T key) <span class="hljs-keyword">const</span>;
<span class="hljs-comment">// (非递归实现)查找"伸展树x"中键值为key的节点</span>
SplayTreeNode<T>* iterativeSearch(SplayTreeNode<T>* x, T key) <span class="hljs-keyword">const</span>;
<span class="hljs-comment">// 查找最小结点:返回tree为根结点的伸展树的最小结点。</span>
SplayTreeNode<T>* minimum(SplayTreeNode<T>* tree);
<span class="hljs-comment">// 查找最大结点:返回tree为根结点的伸展树的最大结点。</span>
SplayTreeNode<T>* maximum(SplayTreeNode<T>* tree);
<span class="hljs-comment">// 旋转key对应的节点为根节点,并返回值为根节点。</span>
SplayTreeNode<T>* splay(SplayTreeNode<T>* tree, T key);
<span class="hljs-comment">// 将结点(z)插入到伸展树(tree)中</span>
SplayTreeNode<T>* insert(SplayTreeNode<T>* &tree, SplayTreeNode<T>* z);
<span class="hljs-comment">// 删除伸展树(tree)中的结点(键值为key),并返回被删除的结点</span>
SplayTreeNode<T>* remove(SplayTreeNode<T>* &tree, T key);
<span class="hljs-comment">// 销毁伸展树</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">destroy</span><span class="hljs-params">(SplayTreeNode<T>* &tree)</span></span>;
<span class="hljs-comment">// 打印伸展树</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">print</span><span class="hljs-params">(SplayTreeNode<T>* tree, T key, <span class="hljs-keyword">int</span> direction)</span></span>;
};
/*
- 构造函数
*/
template <class T>
SplayTree<T>::SplayTree():mRoot(NULL)
{
}
/*
- 析构函数
*/
template <class T>
SplayTree<T>::~SplayTree()
{
destroy(mRoot);
}
/*
- 前序遍历"伸展树"
/
template <class T>
void SplayTree<T>::preOrder(SplayTreeNode<T> tree) const
{
if(tree != NULL)
{
cout<< tree->key << " " ;
preOrder(tree->left);
preOrder(tree->right);
}
}
template <class T>
void SplayTree<T>::preOrder()
{
preOrder(mRoot);
}
/*
- 中序遍历"伸展树"
/
template <class T>
void SplayTree<T>::inOrder(SplayTreeNode<T> tree) const
{
if(tree != NULL)
{
inOrder(tree->left);
cout<< tree->key << " " ;
inOrder(tree->right);
}
}
template <class T>
void SplayTree<T>::inOrder()
{
inOrder(mRoot);
}
/*
- 后序遍历"伸展树"
/
template <class T>
void SplayTree<T>::postOrder(SplayTreeNode<T> tree) const
{
if(tree != NULL)
{
postOrder(tree->left);
postOrder(tree->right);
cout<< tree->key << " " ;
}
}
template <class T>
void SplayTree<T>::postOrder()
{
postOrder(mRoot);
}
/*
-
(递归实现)查找"伸展树x"中键值为key的节点
/
template <class T>
SplayTreeNode<T> SplayTree<T>::search(SplayTreeNode<T>* x, T key) const
{
if (x==NULL || x->key==key)
return x;if (key < x->key)
return search(x->left, key);
else
return search(x->right, key);
}
template <class T>
SplayTreeNode<T>* SplayTree<T>::search(T key)
{
return search(mRoot, key);
}
/*
-
(非递归实现)查找"伸展树x"中键值为key的节点
/
template <class T>
SplayTreeNode<T> SplayTree<T>::iterativeSearch(SplayTreeNode<T>* x, T key) const
{
while ((x!=NULL) && (x->key!=key))
{
if (key < x->key)
x = x->left;
else
x = x->right;
}return x;
}
template <class T>
SplayTreeNode<T>* SplayTree<T>::iterativeSearch(T key)
{
return iterativeSearch(mRoot, key);
}
/*
-
查找最小结点:返回tree为根结点的伸展树的最小结点。
/
template <class T>
SplayTreeNode<T> SplayTree<T>::minimum(SplayTreeNode<T>* tree)
{
if (tree == NULL)
return NULL;while(tree->left != NULL)
tree = tree->left;
return tree;
}
template <class T>
T SplayTree<T>::minimum()
{
SplayTreeNode<T> *p = minimum(mRoot);
if (p != NULL)
return p->key;
<span class="hljs-keyword">return</span> (T)<span class="hljs-literal">NULL</span>;
}
/*
-
查找最大结点:返回tree为根结点的伸展树的最大结点。
/
template <class T>
SplayTreeNode<T> SplayTree<T>::maximum(SplayTreeNode<T>* tree)
{
if (tree == NULL)
return NULL;while(tree->right != NULL)
tree = tree->right;
return tree;
}
template <class T>
T SplayTree<T>::maximum()
{
SplayTreeNode<T> *p = maximum(mRoot);
if (p != NULL)
return p->key;
<span class="hljs-keyword">return</span> (T)<span class="hljs-literal">NULL</span>;
}
/*
- 旋转key对应的节点为根节点,并返回值为根节点。
- 注意:
- (a):伸展树中存在"键值为key的节点"。
-
将"键值为key的节点"旋转为根节点。
- (b):伸展树中不存在"键值为key的节点",并且key < tree->key。
- b-1 "键值为key的节点"的前驱节点存在的话,将"键值为key的节点"的前驱节点旋转为根节点。
- b-2 "键值为key的节点"的前驱节点存在的话,则意味着,key比树中任何键值都小,那么此时,将最小节点旋转为根节点。
- ©:伸展树中不存在"键值为key的节点",并且key > tree->key。
- c-1 "键值为key的节点"的后继节点存在的话,将"键值为key的节点"的后继节点旋转为根节点。
- c-2 "键值为key的节点"的后继节点不存在的话,则意味着,key比树中任何键值都大,那么此时,将最大节点旋转为根节点。
/
template <class T>
SplayTreeNode<T> SplayTree<T>::splay(SplayTreeNode<T>* tree, T key)
{
SplayTreeNode<T> N, *l, *r, *c;
<span class="hljs-keyword">if</span> (tree == <span class="hljs-literal">NULL</span>)
<span class="hljs-keyword">return</span> tree;
N.left = N.right = <span class="hljs-literal">NULL</span>;
l = r = &N;
<span class="hljs-keyword">for</span> (;;)
{
<span class="hljs-keyword">if</span> (key < tree->key)
{
<span class="hljs-keyword">if</span> (tree->left == <span class="hljs-literal">NULL</span>)
<span class="hljs-keyword">break</span>;
<span class="hljs-keyword">if</span> (key < tree->left->key)
{
c = tree->left; <span class="hljs-comment">/* rotate right */</span>
tree->left = c->right;
c->right = tree;
tree = c;
<span class="hljs-keyword">if</span> (tree->left == <span class="hljs-literal">NULL</span>)
<span class="hljs-keyword">break</span>;
}
r->left = tree; <span class="hljs-comment">/* link right */</span>
r = tree;
tree = tree->left;
}
<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (key > tree->key)
{
<span class="hljs-keyword">if</span> (tree->right == <span class="hljs-literal">NULL</span>)
<span class="hljs-keyword">break</span>;
<span class="hljs-keyword">if</span> (key > tree->right->key)
{
c = tree->right; <span class="hljs-comment">/* rotate left */</span>
tree->right = c->left;
c->left = tree;
tree = c;
<span class="hljs-keyword">if</span> (tree->right == <span class="hljs-literal">NULL</span>)
<span class="hljs-keyword">break</span>;
}
l->right = tree; <span class="hljs-comment">/* link left */</span>
l = tree;
tree = tree->right;
}
<span class="hljs-keyword">else</span>
{
<span class="hljs-keyword">break</span>;
}
}
l->right = tree->left; <span class="hljs-comment">/* assemble */</span>
r->left = tree->right;
tree->left = N.right;
tree->right = N.left;
<span class="hljs-keyword">return</span> tree;
}
template <class T>
void SplayTree<T>::splay(T key)
{
mRoot = splay(mRoot, key);
}
/*
- 将结点插入到伸展树中,并返回根节点
- 参数说明:
- tree 伸展树的根结点
- key 插入的结点的键值
- 返回值:
- 根节点
/
template <class T>
SplayTreeNode<T> SplayTree<T>::insert(SplayTreeNode<T>* &tree, SplayTreeNode<T>* z)
{
SplayTreeNode<T> *y = NULL;
SplayTreeNode<T> *x = tree;
<span class="hljs-comment">// 查找z的插入位置</span>
<span class="hljs-keyword">while</span> (x != <span class="hljs-literal">NULL</span>)
{
y = x;
<span class="hljs-keyword">if</span> (z->key < x->key)
x = x->left;
<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (z->key > x->key)
x = x->right;
<span class="hljs-keyword">else</span>
{
<span class="hljs-built_in">cout</span> << <span class="hljs-string">"不允许插入相同节点("</span> << z->key << <span class="hljs-string">")!"</span> << <span class="hljs-built_in">endl</span>;
<span class="hljs-keyword">delete</span> z;
<span class="hljs-keyword">return</span> tree;
}
}
<span class="hljs-keyword">if</span> (y==<span class="hljs-literal">NULL</span>)
tree = z;
<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (z->key < y->key)
y->left = z;
<span class="hljs-keyword">else</span>
y->right = z;
<span class="hljs-keyword">return</span> tree;
}
template <class T>
void SplayTree<T>::insert(T key)
{
SplayTreeNode<T> *z=NULL;
<span class="hljs-comment">// 如果新建结点失败,则返回。</span>
<span class="hljs-keyword">if</span> ((z=<span class="hljs-keyword">new</span> SplayTreeNode<T>(key,<span class="hljs-literal">NULL</span>,<span class="hljs-literal">NULL</span>)) == <span class="hljs-literal">NULL</span>)
<span class="hljs-keyword">return</span> ;
<span class="hljs-comment">// 插入节点</span>
mRoot = insert(mRoot, z);
<span class="hljs-comment">// 将节点(key)旋转为根节点</span>
mRoot = splay(mRoot, key);
}
/*
- 删除结点(节点的键值为key),返回根节点
- 参数说明:
- tree 伸展树的根结点
- key 待删除结点的键值
- 返回值:
- 根节点
/
template <class T>
SplayTreeNode<T> SplayTree<T>::remove(SplayTreeNode<T>* &tree, T key)
{
SplayTreeNode<T> *x;
<span class="hljs-keyword">if</span> (tree == <span class="hljs-literal">NULL</span>)
<span class="hljs-keyword">return</span> <span class="hljs-literal">NULL</span>;
<span class="hljs-comment">// 查找键值为key的节点,找不到的话直接返回。</span>
<span class="hljs-keyword">if</span> (search(tree, key) == <span class="hljs-literal">NULL</span>)
<span class="hljs-keyword">return</span> tree;
<span class="hljs-comment">// 将key对应的节点旋转为根节点。</span>
tree = splay(tree, key);
<span class="hljs-keyword">if</span> (tree->left != <span class="hljs-literal">NULL</span>)
{
<span class="hljs-comment">// 将"tree的前驱节点"旋转为根节点</span>
x = splay(tree->left, key);
<span class="hljs-comment">// 移除tree节点</span>
x->right = tree->right;
}
<span class="hljs-keyword">else</span>
x = tree->right;
<span class="hljs-keyword">delete</span> tree;
<span class="hljs-keyword">return</span> x;
}
template <class T>
void SplayTree<T>::remove(T key)
{
mRoot = remove(mRoot, key);
}
/*
-
销毁伸展树
/
template <class T>
void SplayTree<T>::destroy(SplayTreeNode<T> &tree)
{
if (tree==NULL)
return ;if (tree->left != NULL)
destroy(tree->left);
if (tree->right != NULL)
destroy(tree->right);delete tree;
}
template <class T>
void SplayTree<T>::destroy()
{
destroy(mRoot);
}
/*
- 打印"伸展树"
- key – 节点的键值
- direction – 0,表示该节点是根节点;
-
-1,表示该节点是它的父结点的左孩子;
-
1,表示该节点是它的父结点的右孩子。
/
template <class T>
void SplayTree<T>::print(SplayTreeNode<T> tree, T key, int direction)
{
if(tree != NULL)
{
if(direction==0) // tree是根节点
cout << setw(2) << tree->key << " is root" << endl;
else // tree是分支节点
cout << setw(2) << tree->key << " is " << setw(2) << key << "'s " << setw(12) << (direction==1?“right child” : “left child”) << endl;
print(tree->left, tree->key, <span class="hljs-number">-1</span>);
print(tree->right,tree->key, <span class="hljs-number">1</span>);
}
}
template <class T>
void SplayTree<T>::print()
{
if (mRoot != NULL)
print(mRoot, mRoot->key, 0);
}
(2)测试程序
#include <iostream>
#include "SplayTree.h"
using namespace std;
static int arr[]= {10,50,40,30,20,60};
int main()
{
int i,ilen;
SplayTree<int>* tree=new SplayTree<int>();
<span class="hljs-built_in">cout</span> << <span class="hljs-string">"== 依次添加: "</span>;
ilen = TBL_SIZE(arr);
<span class="hljs-keyword">for</span>(i=<span class="hljs-number">0</span>; i<ilen; i++)
{
<span class="hljs-built_in">cout</span> << arr[i] <<<span class="hljs-string">" "</span>;
tree->insert(arr[i]);
}
<span class="hljs-built_in">cout</span> << <span class="hljs-string">"\n== 前序遍历: "</span>;
tree->preOrder();
<span class="hljs-built_in">cout</span> << <span class="hljs-string">"\n== 中序遍历: "</span>;
tree->inOrder();
<span class="hljs-built_in">cout</span> << <span class="hljs-string">"\n== 后序遍历: "</span>;
tree->postOrder();
<span class="hljs-built_in">cout</span> << <span class="hljs-built_in">endl</span>;
<span class="hljs-built_in">cout</span> << <span class="hljs-string">"== 最小值: "</span> << tree->minimum() << <span class="hljs-built_in">endl</span>;
<span class="hljs-built_in">cout</span> << <span class="hljs-string">"== 最大值: "</span> << tree->maximum() << <span class="hljs-built_in">endl</span>;
<span class="hljs-built_in">cout</span> << <span class="hljs-string">"== 树的详细信息: "</span> << <span class="hljs-built_in">endl</span>;
tree->print();
i = <span class="hljs-number">30</span>;
<span class="hljs-built_in">cout</span> << <span class="hljs-string">"\n== 旋转节点("</span> << i << <span class="hljs-string">")为根节点"</span>;
tree->splay(i);
<span class="hljs-built_in">cout</span> << <span class="hljs-string">"\n== 树的详细信息: "</span> << <span class="hljs-built_in">endl</span>;
tree->print();
<span class="hljs-comment">// 销毁二叉树</span>
tree->destroy();
<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
伸展树的测试程序运行结果分析:
![复制代码](http://img2.tuicool.com/jYN3ey.gif)
== 依次添加: 10 50 40 30 20 60
== 前序遍历: 60 30 20 10 50 40
== 中序遍历: 10 20 30 40 50 60
== 后序遍历: 10 20 40 50 30 60
== 最小值: 10
== 最大值: 60
== 树的详细信息:
60 is root
30 is 60's left child
20 is 30's left child
10 is 20's left child
50 is 30's right child
40 is 50's left child
== 旋转节点(30)为根节点
== 树的详细信息:
30 is root
20 is 30’s left child
10 is 20’s left child
60 is 30’s right child
50 is 60’s left child
40 is 50’s left child
![复制代码](http://img2.tuicool.com/jYN3ey.gif)
测试程序的主要流程是:新建伸展树,然后向伸展树中依次插入10,50,40,30,20,60。插入完毕这些数据之后,伸展树的节点是60;此时,再旋转节点,使得30成为根节点。
依次插入10,50,40,30,20,60示意图如下:
将30旋转为根节点的示意图如下: