SplayTree 伸展树:自调节的二叉搜索树,能把最近访问的节点移到根节点出。适用于各种实际应用,尤其适合实现cache和garbage collection算法。空间少,性能平摊也有竞争力,编程易实现。
优点:
1、 实现简单,比AVL、红黑树更简单。
2、 性能好,平均性能比得上其他平衡二叉搜索树。可能会比AVL慢点。常数因子的差距。
3、 没有其他的记录数据。
4、 可以适用于相同键的数据,平摊仍然是O(lgn)。类似于稳定排序算法。
缺点:
1、 高度可能会退化到线性,但是平摊仍然是O(lgn)。
2、 多线程的“只读”操作也会改变,需要额外的处理。
支持的操作:
1、访问、动态删除插入,其他平衡树也可以做到。
2、拆分(子树1小于子树2)、合并。
支持的区间操作:
1、砍树:把一个区间[x,y]的所有节点组织成一个子树。
依次访问y+1和x-1,Splay操作需要改动,伸展到root或是root->left或是root->right。这点是下面的基本。
2、对一段区间都增加一个值value
采用线段树的延时策略,必要时才往子节点更新。
3、翻转一个区间(顺序反转)
4、对一个区间向右移动k步(转化成交换两个相邻的区间):实际上就是一段区间向右循环移动:
砍出一个子树,在子树里面继续砍出子树,交换子树位置。
5、插入一段区间
也有Splay操作。
6、删除一段区间
简单,把区间砍成一棵子树。不要忘了Splay子树的父节点操作。
7、返回一段区间的最小值
砍树,得到子树根节点的统计信息。
poj 3580 代码:
#include <iostream>
#include <cstdio>
using namespace std;
#define TREESIZE 2000010
#define ARRAYSIZE 100010
#define INTMAX (1<<30)
struct Node {
//int id;
int value, min, delta, size;
bool rev;
Node * parent, *child[2];
};
Node nodes[TREESIZE];
int curNode=0;
int seq[ARRAYSIZE];
Node *null, *root;
Node* AllocNode( int value )
{
nodes[curNode].child[0] = nodes[curNode].child[1] = null;
nodes[curNode].delta = 0;
nodes[curNode].size = 1;
nodes[curNode].rev = false;
nodes[curNode].min = nodes[curNode].value = value;
++ curNode;
return &nodes[curNode-1];
}
void Update( Node* p )
{
// child一定存在
if ( p == null )
return;
p->min = std::min( p->value, std::min( p->child[0]->min, p->child[1]->min ) );
p->size = p->child[0]->size + p->child[1]->size + 1;
}
void InitTree()
{
//标志不参与计数
null = NULL;
null = AllocNode(INTMAX);
null->size = 0;
//两个哨兵,参与计数
//头结点哨兵
root = AllocNode(INTMAX);
null->child[0] = null->child[1] = root;
root->parent = null;
//尾节点哨兵
root->child[1] = AllocNode(INTMAX);
root->child[1]->parent = root;
Update( root );
}
void Pushdown( Node * p )
{
if ( p == null )
return;
if ( 0 != p->delta )
{
p->value += p->delta;
p->min += p->delta; //good
p->child[0]->delta += p->delta;
p->child[1]->delta += p->delta;
p->delta = 0;
}
if ( p->rev )
{
Node *l = p->child[0];
p->child[0] = p->child[1];
p->child[1] = l;
p->rev = false;
p->child[0]->rev = !p->child[0]->rev;
p->child[1]->rev = !p->child[1]->rev;
}
}
//[): beg-->end-1
Node* MakeTree( int *interval, int beg, int end )
{
if ( beg == end )
return null;
if ( beg+1 == end )
{
Node *p = AllocNode(interval[beg]);
p->min = interval[beg];
return p;
}
int mid = ( beg + end )>>1;
Node *p, *l, *r;
p = AllocNode( interval[mid] );
p->size = end-beg;
l = MakeTree( interval, beg, mid );
r = MakeTree( interval, mid+1, end );
p->child[0] = l;
p->child[1] = r;
l->parent = r->parent = p;
//update
Update( p );
return p;
}
//旋转:注意把推迟的信息压入子女节点中
/*
* Select已经更新了推迟的标示
*/
void Rotation( Node *cur, int direct )
{
Node *y = cur->child[!direct];
Pushdown( y->child[0] );
Pushdown( y->child[1] );
Pushdown( cur->child[direct] );
cur->child[!direct] = y->child[direct];
cur->child[!direct]->parent = cur;
y->parent = cur->parent;
y->child[direct] = cur;
if ( y->parent->child[0] == cur)
y->parent->child[0] = y;
else
y->parent->child[1] = y;
cur->parent = y;
//problem
/**
* 对cur进行更新,cur的子女节点可能有推迟的信息,首先需要对cur原来的左子树的子女节点进行pushdown操作
* 也需要对cur原来的右子女进行Pushdown操作
*/
Update( cur );
Update( y );
if ( root == cur )
root = y;
}
//伸展到fp的子女节点
void Splay( Node *cur, Node * fp )
{
/**
* 保证cur的推迟信息在旋转之前更新到子女节点中
* 这样,root-->cur路径没有任何推迟信息
*/
Pushdown( cur );
while ( cur->parent != fp )
{
if ( fp == cur->parent->parent )
{
if ( cur->parent->child[0] == cur )
Rotation( cur->parent, 1 ); //right-rotation
else
Rotation( cur->parent, 0 );
}
else if ( cur->parent->child[0] == cur && cur->parent->parent->child[0] == cur->parent )
{
Rotation( cur->parent->parent, 1 );
Rotation( cur->parent, 1 );
}
else if ( cur->parent->child[1] == cur && cur->parent->parent->child[1] == cur->parent )
{
Rotation( cur->parent->parent, 0 );
Rotation( cur->parent, 0 );
}
else if ( cur->parent->child[0] == cur )
{
Rotation( cur->parent, 1 );
Rotation( cur->parent, 0 );
}
else {
Rotation( cur->parent, 0 );
Rotation( cur->parent, 1 );
}
}
Update( fp );
}
/**
* 先更新推迟的标志,再查询,保证root到curNode都是最新的(没有推迟标志)
*/
void Select( int rank, Node * fp )
{
//寻找第rank个元素,从1编号
Node * curNode = root;
Pushdown( curNode );
while ( rank != curNode->child[0]->size+1 )
{
if ( rank <= curNode->child[0]->size )
curNode = curNode->child[0];
else {
rank -= curNode->child[0]->size + 1;
curNode = curNode->child[1];
}
Pushdown( curNode );
}
Splay( curNode, fp );
}
//add after pos
void InsertInterval( int pos, int *interval, int cnt )
{
// 第pos个元素称为根节点
Select( pos+1, null );
// 第pos+1个元素成为根节点的右孩子
Select( pos+2, root );
//在root->child[1]的左孩子(为NULL)建立新子树
Node *p = MakeTree( interval, 0, cnt );
root->child[1]->child[0] = p;
p->parent = root->child[1];
//伸展的同时更新整个路径,一定要有
Splay( root->child[1]->child[0], null );
}
void DeleteInterval( int left, int right )
{
Select( left, null );
Select( right+2, root );
root->child[1]->child[0] = null;
Splay( root->child[1], null );
}
void Add( int left, int right, int data )
{
//砍树[left,right],注意有一个头结点哨兵
Select( left, null );
Select( right+2, root );
root->child[1]->child[0]->delta += data;
Splay( root->child[1]->child[0], null );
}
int GetMin( int left, int right )
{
Select( left, null );
Select( right+2, root );
return root->child[1]->child[0]->min;
}
//可以推迟旋转
void Reverse( int left, int right )
{
Select( left, null );
Select( right+2, root );
root->child[1]->child[0]->rev = !root->child[1]->child[0]->rev;
Splay( root->child[1]->child[0], null );
}
void Revolve( int left, int right, int data )
{
++ left;
++ right;
int len;
Node *x, *y;
len = right-left+1;
data = data%len;
if ( 0 == data )
return;
Select( left, null );
x = root;
Select( left-1, null );
Select( right+1, root );
//left->right-data; right-data+1-->right
Select( right-data, root->child[1] );
y = root->child[1]->child[0];
x->child[0] = y->child[1];
x->child[0]->parent = x;
y->child[1] = null;
Splay( x, null );
}
int main()
{
int n, i, m, left, right, data, pos;
char instruct[50];
while ( scanf("%d", &n ) != EOF )
{
for ( i = 0; i < n; ++ i )
scanf("%d", &seq[i] );
InitTree();
//在第一个元素(头结点哨兵)后面加入
InsertInterval( 0, seq, n );
printf("%d\n", root->value );
scanf("%d", &m);
for ( i = 0; i < m; ++ i )
{
scanf("%s", instruct );
if ( 'A' == instruct[0] )
{
scanf("%d%d%d", &left, &right, &data );
Add( left, right, data );
}
else if ( 'I' == instruct[0] )
{
scanf("%d%d", &pos, &data );
InsertInterval( pos, &data, 1 );
}
else if ( 'D' == instruct[0] )
{
scanf("%d", &pos );
DeleteInterval( pos, pos );
}
else if ( 'M' == instruct[0] )
{
scanf("%d%d", &left, &right );
printf("%d\n", GetMin( left, right ) );
}
else if ( 'E' == instruct[3] )
{
scanf("%d%d", &left, &right );
Reverse( left, right );
}
else {
scanf("%d%d%d", &left, &right, &data );
Revolve( left, right, data );
}
//Print();
}
}
return 0;
}