高级搜索树-伸展树(Splay Tree)
与AVL树一样,伸展树(Splay Tree)也是平衡二叉搜索树的一致,伸展树无需时刻都严格保持整棵树的平衡,也不需要对基本的二叉树结点做任何附加改动,能够保持分摊意义下的高效率。
局部性
通常在任意数据结构的生命期内,执行不同操作的概率往往极不均衡,且各操作之间具有极强的关联性,比如数据局部性,所谓数据局部性包括:
- 刚刚被访问到的元素,很可能不久之后就再次被访问
- 将被访问的下一元素,很可能就处于不久之前被访问够的某个元素的附近
故每次都只需将刚被访问到的节点及时地转移到树根(附近),即可加速后续的操作,转移操作与AVL树的旋转操作类似
双层伸展
每次都从当前节点v向上追溯两层,并根据其父亲p和祖父g的相对位置进行相应的旋转
- zig-zig/zag-zag

- zig-zag/zag-zig

- 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)的更多相关文章
- 纸上谈兵: 伸展树 (splay tree)[转]
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 我们讨论过,树的搜索效率与树的深度有关.二叉搜索树的深度可能为n,这种情况下,每 ...
- K:伸展树(splay tree)
伸展树(Splay Tree),也叫分裂树,是一种二叉排序树,它能在O(lgN)内完成插入.查找和删除操作.在伸展树上的一般操作都基于伸展操作:假设想要对一个二叉查找树执行一系列的查找操作,为了使 ...
- 树-伸展树(Splay Tree)
伸展树概念 伸展树(Splay Tree)是一种二叉排序树,它能在O(log n)内完成插入.查找和删除操作.它由Daniel Sleator和Robert Tarjan创造. (01) 伸展树属于二 ...
- 【BBST 之伸展树 (Splay Tree)】
最近“hiho一下”出了平衡树专题,这周的Splay一直出现RE,应该删除操作指针没处理好,还没找出原因. 不过其他操作运行正常,尝试用它写了一道之前用set做的平衡树的题http://codefor ...
- 伸展树 Splay Tree
Splay Tree 是二叉查找树的一种,它与平衡二叉树.红黑树不同的是,Splay Tree从不强制地保持自身的平衡,每当查找到某个节点n的时候,在返回节点n的同时,Splay Tree会将节点n旋 ...
- 伸展树(Splay tree)的基本操作与应用
伸展树的基本操作与应用 [伸展树的基本操作] 伸展树是二叉查找树的一种改进,与二叉查找树一样,伸展树也具有有序性.即伸展树中的每一个节点 x 都满足:该节点左子树中的每一个元素都小于 x,而其右子树中 ...
- HDU 4453 Looploop (伸展树splay tree)
Looploop Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Su ...
- hdu 2871 Memory Control(伸展树splay tree)
hdu 2871 Memory Control 题意:就是对一个区间的四种操作,NEW x,占据最左边的连续的x个单元,Free x 把x单元所占的连续区间清空 , Get x 把第x次占据的区间输出 ...
- 伸展树 Splay 模板
学习Splay的时候参考了很多不同的资料,然而参考资料太杂的后果就是模板调出来一直都有问题,尤其是最后发现网上找的各种资料均有不同程度的错误. 好在啃了几天之后终于算是啃下来了. Splay也算是平衡 ...
随机推荐
- oracle 12c数据库在Windows环境下的安装
因为菜鸟小白之前做着一些数据库审计产品的测试,接下来我会分享一些关于数据库安装和通过python的访问数据库的知识 安装 首先我们需要下载一个oracle 12c的安装程序,解压后右键点击“ ...
- “我放弃了年薪20W的offer......”
我的职业生涯开始和大多数测试人一样,开始接触都是纯功能界面测试.那时候在一家电商公司做测试,做了有一段时间,熟悉产品的业务流程以及熟练测试工作流程规范之后,效率提高了,工作比较轻松,也得到了更好的机会 ...
- koa中是如何封装获取客户端IP的?
案例 var koa = require('koa') var app = new koa() app.use(function (ctx,next) { ctx.body = ctx.ip }) a ...
- 集训作业 洛谷P1866 编号
这个题是个数学题啊. 总体思路不是很难,每个兔子有一个编号,只要不停的看下一个兔子有多少可选编号,再乘上之前的所有可能性就可以算出一共的编号方法. #include<iostream> # ...
- Azure Traffic Manager(二) 基于权重与基于优先级的路由策略为我们的Web项目提供负载均衡
一,引言 上一片文章我们使用 Azure Traffic Manager 分发用户请求,同时演示了两种路由策略,“Performance”,“Geographic”的两种方式,今天我们继续讲解 Tra ...
- 【几何+模拟】二次元变换 计蒜客 - T3213
题目 aslky 有一个 n×n 的矩形,每个位置上都有一个数,有 q 次操作,每次他会让你上下翻转 (UD),左右反转 (LR),顺时针旋转 90∘(SZ),逆时针旋转 90∘(NZ),请你输出最后 ...
- 干货分享丨玩转物联网IoTDA服务系列四-智能网关
摘要:该场景主要描述的是设备可以通过MQTT协议与物联网平台进行交互,用户可以在控制台产品详情中自定义Topic,通过应用侧接口或控制台创建数据转发规则,把设备上报的消息转发给其他华为云服务,供应用侧 ...
- Java Web(3)-XML
一.XML简介 1. 什么是xml? xml 是可扩展的标记性语言 2. xml的作用? 用来保存数据,而且这些数据具有自我描述性 它还可以做为项目或者模块的配置文件 还可以做为网络传输数据的格式(现 ...
- 多个activity的博客参考,用mainactivity 调用 明天阅读一下
https://blog.csdn.net/hbwxy521/article/details/53101019
- Java SE基础知识
Java SE面试题 目录 Java SE基础 基本语法 数据类型 关键字 面向对象 集合 集合类概述 Collection接口 List Set Map Java SE基础 基本语法 数据类型 Ja ...