高级搜索树-伸展树(Splay Tree)

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

局部性

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

  1. 刚刚被访问到的元素,很可能不久之后就再次被访问
  2. 将被访问的下一元素,很可能就处于不久之前被访问够的某个元素的附近
    故每次都只需将刚被访问到的节点及时地转移到树根(附近),即可加速后续的操作,转移操作与AVL树的旋转操作类似

双层伸展

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

  1. zig-zig/zag-zag

zig-zig操作

  1. zig-zag/zag-zig

zig-zag操作

  1. zig/zag(若深度为奇数,需要额外执行一次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;
}

posted @ 2020-06-06 17:27  海物chinono  阅读(306)  评论(0)    收藏  举报