与AVL树一样,伸展树(Splay Tree)也是平衡二叉搜索树的一致,伸展树无需时刻都严格保持整棵树的平衡,也不需要对基本的二叉树结点做任何附加改动,能够保持分摊意义下的高效率。

局部性

通常在任意数据结构的生命期内,执行不同操作的概率往往极不均衡,且各操作之间具有极强的关联性,比如数据局部性,所谓数据局部性包括:

  1. 刚刚被访问到的元素,很可能不久之后就再次被访问
  2. 将被访问的下一元素,很可能就处于不久之前被访问够的某个元素的附近

    故每次都只需将刚被访问到的节点及时地转移到树根(附近),即可加速后续的操作,转移操作与AVL树的旋转操作类似

双层伸展

每次都从当前节点v向上追溯两层,并根据其父亲p和祖父g的相对位置进行相应的旋转

  1. zig-zig/zag-zag

  1. zig-zag/zag-zig

  1. zig/zag(若深度为奇数,需要额外执行一次zig/zag操作)


// 从节点v开始逐层进行伸展
template<typename T>
inline SplayNodePos(T) SplayTree<T>::splay(SplayNodePos(T) & v) {//v为因最近访问而需要伸展的节点
if (!v) return NULL;
SplayNodePos(T) p = NULL;
SplayNodePos(T) g = NULL; //v的父亲以及祖父
while ((p = v->pa) && (g = p->pa) ){// p、g均存在,可以做双层伸展
SplayNodePos(T) gg = g->pa; //gg为v的曾祖父
if (IsLChild(v) && IsLChild(p)) {
attachAsLChild(p, v->rc);
attachAsLChild(g, p->rc);
attachAsRChild(v, p);
attachAsRChild(p, g);
}
else if (IsRChild(v) && IsRChild(p) ){
attachAsRChild(p, v->lc);
attachAsRChild(g, p->lc);
attachAsLChild(v, p);
attachAsLChild(p, g);
}
else if (IsLChild(v) && IsRChild(p)) {
attachAsRChild(g,v->lc);
attachAsLChild(p,v->rc);
attachAsRChild(v,p);
attachAsLChild(v,g);
}
else if (IsRChild(v) && IsLChild(p)) {
attachAsLChild(g, v->rc);
attachAsRChild(g, v->lc);
attachAsLChild(v, p);
attachAsRChild(v, g);
}
if (!gg) v->pa = NULL; //如果v的曾祖父gg不存在,则v现在是root
else //否则将v接到gg上
(g == gg->lc)? attachAsLChild(gg, v) :attachAsRChild(gg, v);
} //双层伸展结束,必有g==NULL,但是p可能非空
if (p = v->pa) { //若p非空,再单独做一次单选
if (IsLChild(v)) {
attachAsLChild(p, v->rc);
attachAsRChild(v,p);
}
else {
attachAsRChild(p, v->lc);
attachAsLChild(v, p);
}
}
v->pa = NULL;
return v;
}

查找操作

不同于其他平衡二叉搜索树的查找操作,伸展树的查找操作是一个动态操作,每次查找后要将最后一个访问的节点通过伸展操作splay到树根

//查找函数,若存在值为key的节点返回相应节点,若不存在则返回相应父节点
template<typename T>
inline SplayNodePos(T) SplayTree<T>::search(const T & key)
{
SplayNodePos(T) hot = NULL;//hot为返回节点的父节点
SplayNodePos(T) x= searchIn(key, root, hot);
if (x)
root = splay(x);
else
root = splay(hot);
return root;
}

插入操作

先利用search操作,注意search此时是一个动态操作,会将最后访问的节点t通过splay操作提升为树根,此时有如上图我们新建节点v并将其作为根接入原树,以t为左子,t的右子为其右子


//插入值为key的节点
template<typename T>
SplayNodePos(T) SplayTree<T>::insert(const T & key)
{
if (!root)//原树为空
return root = new SplayNode<T>(key);
if (key == search(key)->key) //存在值为key的节点,无需执行插入操作,且查找时已经执行了splay操作,此时root为值为key的节点
return root;
//目标节点不存在
SplayNodePos(T) t= root;
if (root->key < key) { //插入新根,分别以t和t->rc为左右孩子
t->pa = root = new SplayNode<T>(key, NULL, t, t->rc);
if (HasRChild(t)) {
t->rc->pa = root;
t->rc = NULL;
}
}else {//插入新根,分别以t->lc和t为左右孩子
t->pa = root = new SplayNode<T>(key, NULL, t->lc, t);
if (HasLChild(t)) {
t->lc->pa = root;
t->lc = NULL;
}
}
return root;
} //无论key是否存在于原树中,返回时总有root->key == key

删除操作

先利用search操作,注意search此时是一个动态操作,会将最后访问的节点v通过splay操作伸展为树根,如果要删除的节点存在于树中,则此时通过splay操作伸展为树根,我们不妨先将root的左右子树先分开,利用search和splay操作将右子树的值最小的节点伸展至树根,此时root必定没有左子树,再将原左子树接回新树,得到最终的树


//删除值为key的节点
template<typename T>
inline bool SplayTree<T>::remove(const T & key)
{
if (!root || key != search(key)->key) //若原树为空或目标不存在则不执行删除操作
return false;
SplayNodePos(T) t = root;
//注意上面执行了search操作,即此时已经伸展过原树,有root->key == key
if (!HasLChild(root)) { //若无左子树,直接删除根节点
root = root->rc;
if (root->rc)
root->rc->pa = NULL;
}
else if (!HasRChild(root)) { //若无右子树,直接删除根节点
root = root->lc;
if (root->lc)
root->lc->pa = NULL;
}else { //否则左右子树均存在,此时
SplayNodePos(T) ltree = root->lc;
ltree->pa = NULL; root->lc = NULL; //暂时切除左子树
root = root->rc; root->pa = NULL;//将删除节点和右子树分离
search(key); //再执行一次search操作,此时左侧key最小的节点会伸展至root,且root无左子树
root->lc = ltree; ltree->pa = root; //将原左子树接回整树中
}
delete t; //删除原根节点
return true; //成功删除
}
#endif // ! _SPLAYTREE_DECLARATION_H

性能分析

利用双层伸展,即使是单链的最坏情况,最终也可以将其压缩至长度大致折半,故即使每次都访问最深处节点,最坏情况也不会持续发生。伸展树虽然不能杜绝最坏情况的发生,但是却能有效地控制最坏情况发生的频度,从而保证分摊意义下的整体高效。利用势能分析法可以证明伸展树的到此操作均可在分摊的O(log n)的时间内完成。

完整源码

代码参考《数据结构(c++语言版)》--清华大学邓俊辉

"SplayTree_Define.h"



#ifndef _SPLAYTREE_DEFINE_H
#define _SPLAYTREE_DEFINE_H //#include"pch.h"
#include<iostream>
#define SplayNodePos(T) SplayNode<T> *
//宏定义
#define IsRoot(x) ( !((x)->pa) )
#define IsLChild(x) ( !(IsRoot(x) ) && (x)==(x)->pa->lc)
#define IsRChild(x) ( !(IsRoot(x) ) && (x)==(x)->pa->rc)
#define HasLChild(x) ((x)->lc )
#define HasRChild(x) ((x)->rc )
#define HasChild(x) (HasLChild(x) || HasRChild(x))
#define HasBothChild(x) (HasRChild(x) && HasLChild(x) )
#define IsLeaf(x) (! HasChild(x) ) //SplayNode 定义
template<typename T>
struct SplayNode {
public:
T key;
SplayNodePos(T) pa;//pa -- parent
SplayNodePos(T) lc;//lc -- left child
SplayNodePos(T) rc;//rc -- right child
//构造函数
SplayNode() :pa(NULL), lc(NULL), rc(NULL) {}
SplayNode(T elem, SplayNodePos(T) pa = NULL, SplayNodePos(T) lc = NULL, SplayNodePos(T) rc = NULL) :
key(elem), pa(pa), lc(lc), rc(rc) { }
};
//AVLTree 定义
template<typename T>
class SplayTree {
private:
SplayNodePos(T) root; //树根
public:
//构造函数和析构函数
SplayTree() :root(NULL) {}
~SplayTree() {} //只读函数
int height() { return Height(root); }
//遍历函数
void preOrder(); //前序遍历
void inOrder(); //中序遍历
void postOrder(); //后序遍历
//操作函数
SplayNodePos(T) search(const T &key); //查找函数,若存在值为key的节点返回相应节点,若不存在则返回NULL,基于searchIn函数实现
SplayNodePos(T) insert(const T &key); //插入值为key的节点
bool remove(const T &key); //移除值为key的节点 private: void preOrder(SplayNodePos(T) &cur); //以cur节点为root进行前序遍历
void inOrder(SplayNodePos(T) &cur); //以cur节点为root进行中序遍历
void postOrder(SplayNodePos(T) &cur); //以cur节点为root进行后序遍历
SplayNodePos(T) searchIn(const T & key, SplayNodePos(T) cur, SplayNodePos(T)& hot); //以cur节点为root查找值为key的节点,hot为返回节点的父节点
SplayNodePos(T) splay(SplayNodePos(T) &cur); // 从节点cur开始逐层进行伸展
};
template<typename NodePos>
inline void attachAsLChild(NodePos p, NodePos lc);//lc作为p的左子接入,lc可能为空
template<typename NodePos>
inline void attachAsRChild(NodePos p, NodePos rc);//rc作为p的右子子接入,lc可能为空
#endif

"SplayTree_Declaration.h"


#include"pch.h"
#ifndef _SPLAYTREE_DECLARATION_H
#define _SPLAYTREE_DECLARATION_H #include"SplayTree_Declaration.h"
#include<iostream>
template<typename T>
inline void SplayTree<T>::preOrder(SplayNodePos(T) & cur)
{
if (!cur) return;
std::cout << cur->key << " ";
preOrder(cur->lc);
preOrder(cur->rc);
} template<typename T>
inline void SplayTree<T>::inOrder(SplayNodePos(T) & cur)
{
if (!cur) return;
inOrder(cur->lc);
std::cout << cur->key << " ";
inOrder(cur->rc);
} template<typename T>
inline void SplayTree<T>::postOrder(SplayNodePos(T)& cur)
{
if (!cur) return;
postOrder(cur->lc);
postOrder(cur->rc);
std::cout << cur->key << " ";
} template<typename T>
inline void SplayTree<T>::preOrder()
{
preOrder(root);
} template<typename T>
inline void SplayTree<T>::inOrder()
{
inOrder(root);
} template<typename T>
inline void SplayTree<T>::postOrder()
{
postOrder(root);
} //lc作为p的左子接入,lc可能为空
template<typename NodePos>
inline void attachAsLChild(NodePos p, NodePos lc) {
p->lc = lc;
if (lc) lc->pa = p;
} //rc作为p的右子子接入,lc可能为空
template<typename NodePos>
inline void attachAsRChild(NodePos p, NodePos rc) {
p->rc = rc;
if (rc) rc->pa = p;
} //以cur节点为root查找值为key的节点,hot为返回节点的父节点
template<typename T>
inline SplayNodePos(T) SplayTree<T>::searchIn(const T & key, SplayNodePos(T) cur, SplayNodePos(T)& hot)
{
if (!cur || key == cur->key) return cur;
hot = cur;
return searchIn(key, (key < cur->key ? cur->lc : cur->rc), hot);
} //查找函数,若存在值为key的节点返回相应节点,若不存在则返回相应父节点
template<typename T>
inline SplayNodePos(T) SplayTree<T>::search(const T & key)
{
SplayNodePos(T) hot = NULL;//hot为返回节点的父节点
SplayNodePos(T) x= searchIn(key, root, hot);
if (x)
root = splay(x);
else
root = splay(hot);
return root;
} //插入值为key的节点
template<typename T>
SplayNodePos(T) SplayTree<T>::insert(const T & key)
{
if (!root)//原树为空
return root = new SplayNode<T>(key);
if (key == search(key)->key) //存在值为key的节点,无需执行插入操作,且查找时已经执行了splay操作,此时root为值为key的节点
return root;
//目标节点不存在
SplayNodePos(T) t= root;
if (root->key < key) { //插入新根,分别以t和t->rc为左右孩子
t->pa = root = new SplayNode<T>(key, NULL, t, t->rc);
if (HasRChild(t)) {
t->rc->pa = root;
t->rc = NULL;
}
}else {//插入新根,分别以t->lc和t为左右孩子
t->pa = root = new SplayNode<T>(key, NULL, t->lc, t);
if (HasLChild(t)) {
t->lc->pa = root;
t->lc = NULL;
}
}
return root;
} //无论key是否存在于原树中,返回时总有root->key == key //删除值为key的节点
template<typename T>
inline bool SplayTree<T>::remove(const T & key)
{
if (!root || key != search(key)->key) //若原树为空或目标不存在则不执行删除操作
return false;
SplayNodePos(T) t = root;
//注意上面执行了search操作,即此时已经伸展过原树,有root->key == key
if (!HasLChild(root)) { //若无左子树,直接删除根节点
root = root->rc;
if (root->rc)
root->rc->pa = NULL;
}
else if (!HasRChild(root)) { //若无右子树,直接删除根节点
root = root->lc;
if (root->lc)
root->lc->pa = NULL;
}else { //否则左右子树均存在,此时
SplayNodePos(T) ltree = root->lc;
ltree->pa = NULL; root->lc = NULL; //暂时切除左子树
root = root->rc; root->pa = NULL;//将删除节点和右子树分离
search(key); //再执行一次search操作,此时左侧key最小的节点会伸展至root,且root无左子树
root->lc = ltree; ltree->pa = root; //将原左子树接回整树中
}
delete t; //删除原根节点
return true; //成功删除
}
#endif // ! _SPLAYTREE_DECLARATION_H // 从节点v开始逐层进行伸展
template<typename T>
inline SplayNodePos(T) SplayTree<T>::splay(SplayNodePos(T) & v) {//v为因最近访问而需要伸展的节点
if (!v) return NULL;
SplayNodePos(T) p = NULL;
SplayNodePos(T) g = NULL; //v的父亲以及祖父
while ((p = v->pa) && (g = p->pa) ){// p、g均存在,可以做双层伸展
SplayNodePos(T) gg = g->pa; //gg为v的曾祖父
if (IsLChild(v) && IsLChild(p)) {
attachAsLChild(p, v->rc);
attachAsLChild(g, p->rc);
attachAsRChild(v, p);
attachAsRChild(p, g);
}
else if (IsRChild(v) && IsRChild(p) ){
attachAsRChild(p, v->lc);
attachAsRChild(g, p->lc);
attachAsLChild(v, p);
attachAsLChild(p, g);
}
else if (IsLChild(v) && IsRChild(p)) {
attachAsRChild(g,v->lc);
attachAsLChild(p,v->rc);
attachAsRChild(v,p);
attachAsLChild(v,g);
}
else if (IsRChild(v) && IsLChild(p)) {
attachAsLChild(g, v->rc);
attachAsRChild(g, v->lc);
attachAsLChild(v, p);
attachAsRChild(v, g);
}
if (!gg) v->pa = NULL; //如果v的曾祖父gg不存在,则v现在是root
else //否则将v接到gg上
(g == gg->lc)? attachAsLChild(gg, v) :attachAsRChild(gg, v);
} //双层伸展结束,必有g==NULL,但是p可能非空
if (p = v->pa) { //若p非空,再单独做一次单选
if (IsLChild(v)) {
attachAsLChild(p, v->rc);
attachAsRChild(v,p);
}
else {
attachAsRChild(p, v->lc);
attachAsLChild(v, p);
}
}
v->pa = NULL;
return v;
}

"main.cpp"


// SplayTree.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
// #include "pch.h"
#include <iostream>
#include"SplayTree_Declaration.h"
#include"SplayTree_Define.h"
using namespace std;
int main()
{
SplayTree<int> t;
int n;
cout << "insert number:";
cin >> n;
cout << "insert:";
for (int i = 0; i < n; i++) {
int tmp;
cin >> tmp;
t.insert(tmp);
}
cout << "PreOrder:";
t.preOrder();
cout << endl;
cout << "InOrder:";
t.inOrder();
cout << endl;
cout << "PostOrder:";
t.postOrder();
cout << endl;
cout << "remove number:";
cin >> n;
cout << "remove:";
for (int i = 0; i < n; i++) {
int tmp;
cin >> tmp;
t.remove(tmp);
}
cout << "PreOrder:";
t.preOrder();
cout << endl;
cout << "InOrder:";
t.inOrder();
cout << endl;
cout << "PostOrder:";
t.postOrder();
cout << endl;
return 0;
return 0;
}

高级搜索树-伸展树(Splay Tree)的更多相关文章

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

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

  2. K:伸展树(splay tree)

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

  3. 树-伸展树(Splay Tree)

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

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

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

  5. 伸展树 Splay Tree

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

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

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

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

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

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

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

  9. 伸展树 Splay 模板

    学习Splay的时候参考了很多不同的资料,然而参考资料太杂的后果就是模板调出来一直都有问题,尤其是最后发现网上找的各种资料均有不同程度的错误. 好在啃了几天之后终于算是啃下来了. Splay也算是平衡 ...

随机推荐

  1. Tips1:考虑用静态工厂方法代替构造器

    用静态工厂方法来代替构造器为外界提供对象 描述: 静态工厂方法代替构造器来给外界提供对象,创建对象依然是由构造器来完成的 创建对象和提供对象: 创建对象的方式: 构造器 提供对象来哦方式: 构造器 类 ...

  2. JavaScript动画实例:曲线的绘制

    在“JavaScript图形实例:曲线方程”一文中,我们给出了15个曲线方程绘制图形的实例.这些曲线都是根据其曲线方程,在[0,2π]区间取一系列角度值,根据给定角度值计算对应的各点坐标,然后在计算出 ...

  3. win7下建立docker共享文件夹

    前言 建立本机(win7)和VirtualBox中docker虚拟机的共享文件夹,注:下面的命令都是以root身份运行的,使用sudo -i切换到root身份,如无法切换,请自行在命令前加上sudo命 ...

  4. 【揭秘】C语言类型转换时发生了什么?

    ID:技术让梦想更伟大 作者:李肖遥 链接:https://mp.weixin.qq.com/s/ZFf3imVaJgeesuhl1Kn9sQ 在C语言中,数据类型指的是用于声明不同类型的变量或函数的 ...

  5. 设计模式:mediator模式

    目的:解决多组件之间的通信问题,使得组件之间的通信变得简单 核心:提供一个管理类,用来处理组件之间的通信,所有的组件只和管理类通信,组件彼此之间不在单独通信 例子: class Mediator { ...

  6. ISE和modelsim的配合

    modelsim好强呀,我在ISE中在编写时clk不小心把input写成了inout,ISE也没有给我报错:在modelsim中仿真时提示出这个错误了!

  7. 微信小程序 springboot nginx 做图片存储 上传 浏览

    微信小程序前端-springboot后端-nginx图片存储 前言 本人小白一名,这是第一次学习微信小程序,特此做个记录. 首先准备nginx做图片存储 选择一个地址存放图片 #我的地址 [root@ ...

  8. 大数据框架Hive优化方法

    常规调优手段 Fetch抓取 某些情况查询不必用MapReduce计算,比如select*,可以直接读取文件 本地模式 有时数据量比较小,hive可以通过本地模式在单台机器上处理所有任务,对于小数据集 ...

  9. https://blog.csdn.net/yongchaocsdn/article/details/53355296

    https://blog.csdn.net/yongchaocsdn/article/details/53355296

  10. list 和 [ ] 的功能不相同

    对于一个对象: list(对象) 可以进行强制转换 [对象] 不能够进行强制转换,只是在外围加上 [ ] 列表推导式中相同   2020-05-06