与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. 那些非cmake生成的VTK工程存在的让人崩溃的坑

    由于cmake和IDE的一些编译选项不同,所以导致我们使用者需要多做一些事情.而且vtk官方也表示: If you are not using CMake to compile your code, ...

  2. opencv毛孔识别(python实现)

    毛孔识别 本文仅仅描述如何用opencv完成一个入门级别的毛孔识别,基于python3.7和 opencv 4.3 原图以及识别生成的效果图 一.首先引入需要的包,然后读取需要识别的图片 import ...

  3. 深入浅出ReentrantLock源码解析

    ReentrantLock不但是可重入锁,而且还是公平或非公平锁,在工作中会经常使用到,将自己对这两种锁的理解记录下来,希望对大家有帮助. 前提条件 在理解ReentrantLock时需要具备一些基本 ...

  4. Python Ethical Hacking - The Lab and Needed Software

    The Lab and Needed Software Attacker Machine - Kali Linux https://www.kali.org/ 1. Install the softw ...

  5. OSCP Learning Notes - WebApp Exploitation(5)

    Remote File Inclusion[RFI] Prepare: Download the DVWA from the following website and deploy it on yo ...

  6. Ubuntu构建Docker私有仓库(Repository) 配置过程笔记

    一.准备: 1.服务器(或者虚拟机2台,我的服务环境[  阿里云服务器-Ubuntu 1804 +百度云-Ubuntu 1604]) 2.有效镜像(我这里以上一篇随笔镜像作为有效镜像https://w ...

  7. two types of friend

    两类朋友 第一类,普通朋友,并不能分享一些隐私的感情,只能说一些事情,有一些只是认识的人或者虽然认识很多年但是也只能是这样的! 第二类,关心你,可以交流感清,明显更加亲密一点. 不要对第一类朋友说第二 ...

  8. python txt装换成excel

    工作中,我们需要经常吧一些导出的数据文件,例如sql查出来的结果装换成excel,用文件发送.这次为大家带上python装换excel的脚本 记得先安装wlwt模块,适用版本,python2-3 #c ...

  9. Redis服务之常用配置(一)

    上一篇博客聊了下redis的简介以及redis的yum安装和源码编译安装需要注意到问题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/13378138.html ...

  10. springboot整合邮件发送(163邮箱发送为例)

    先登录163邮箱获取授权  勾选后安装提示会叫你设置授权密码之类的:记住授权的密码 1.引入maven依赖 <dependency> <groupId>org.springfr ...