高级搜索树-伸展树(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也算是平衡 ...
随机推荐
- 转载一篇关于kafka零拷贝(zero-copy)通俗易懂的好文
原文地址 https://www.cnblogs.com/yizhou35/p/12026263.html 零拷贝就是一种避免CPU 将数据从一块存储拷贝到另外一块存储的技术. DMA技术是Direc ...
- Spring AOP里的静态代理和动态代理,你真的了解嘛?
什么是代理? 为某一个对象创建一个代理对象,程序不直接用原本的对象,而是由创建的代理对象来控制原对象,通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为 ...
- 题解 CF 1372 B
题目 传送门 题意 给出 \(n\),输出 \(a\) ,\(b\) (\(0 < a \leq b < n\)),使\(a+b=n\)且 \(\operatorname{lcm}(a,b ...
- css属性inline-block的应用
1. 让两个块级元素处于同一行 2. 需要元素撑开边框的时候
- vue怎么自定义组件
我们在搭建好的手脚架中 进行使用 一.在src =>components => 创建XXbtn文件夹用来存放你的组件 =>在创建一个vue的文件 . 二.在src => com ...
- Python延迟初始化(lazy property)
转自:https://blog.csdn.net/azsx02/article/details/77649527 Python 对象的延迟初始化是指,当它第一次被创建时才进行初始化,或者保存第一次创建 ...
- Android仿支付宝高顶部功能条伸缩动画
参考:https://blog.csdn.net/aqi00/article/details/72621176
- sql数据管理语句
一.数据管理 1.增加数据 INSERT INTO student VALUES(1,'张三','男',20); -- 插入所有字段.一定依次按顺序插入 -- 注意不能少或多字段值 如只需要插入部分字 ...
- Lua学习入门(基本数据类型)
数据类型 Lua 中有 8 个基本类型分别为:nil.boolean.number.string.userdata.function.thread 和 table. 数据类型 描述 nil 这个最简单 ...
- OpenFeign使用步骤
1. 新建 cloud-consumer-feign-order80 2. pom.xml <?xml version="1.0" encoding="UTF-8& ...