伸展树的节点的size域的应用

原创 2016年05月30日 23:55:14

伸展树不是平衡二叉树,但是它的操作均摊是O(logN)的,只需要将增、删、查、改的节点都加以伸展即可。因此可以用来解决相关问题。

在伸展树的节点上附加一个size域,用来保存以该节点为根的子树的节点总数。这个size域可以用来解决很多问题。例如可以解决区间问题,例如SBT就是用这个size域来计算平衡条件。size域能够解决的最简单、最直接的问题就是kth问题——求第k小的数。

在伸展树上加上size域用来解决POJ2371

二叉树节点的结构体定义如下:

int const SIZE = 1111111;
int const LEFT = 0;
int const RIGHT = 1;
struct node_t{
    int parent;   //父节点
    int child[2]; //子节点
    int sn;       //指示本节点是其父亲左儿子还是右儿子
    key_t key;    //键
    value_t value;//值,这道题里实际上不需要
    int size;     //size域
}Node[SIZE];      //Node[0]不使用,用来充当NULL
int toUsed = 0;

这道题中用来充当键的就是一个整数,而且存在可能相等的情况。因此,将其出现的先后顺序考虑进去,从而确保任意两个节点具有不同的键。因此,键不是一个整数,而是一个结构体。同时为这个结构体重载相应的关系运算符。

struct key_t{
    int key;   //真正的键
    int order; //出现的顺序
    key_t(int a=0,int b=0):key(a),order(b){}
};
bool operator < (key_t const&l,key_t const&r){
    return l.key < r.key || ( l.key == r.key && l.order < r.order );
}
bool operator == (key_t const&l,key_t const&r){
    return l.key == r.key && l.order == r.order;
}
bool operator != (key_t const&l,key_t const&r){
    return !( l == r );
}

计算size域所用的函数

inline void _pushUp(int t){
    Node[t].size = 1;
    int son = Node[t].child[LEFT];
    if (son) Node[t].size += Node[son].size;
    son = Node[t].child[RIGHT];
    if (son) Node[t].size += Node[son].size;
}

伸展操作所用到的函数,在《伸展树的旋转和伸展操作》中有介绍。这个地方需要注意的是:要在合适的地方调用_pushUp(int)函数来维持size域

//设置p、t的父子关系,这里不调用_pushUp(int)
inline void _link(int p,int sn,int t){
    Node[p].child[sn] = t;
    Node[t].parent = p;
    Node[t].sn = sn;
}
//旋转就是重新确定三对父子关系,这里只维持节点p的size
inline void _rotate(int t){
    int p = Node[t].parent;
    int sn = Node[t].sn;
    int osn = sn ^ 1;

    _link(p,sn,Node[t].child[osn]);
    _link(Node[p].parent,Node[p].sn,t);
    _link(t,osn,p);

    _pushUp(p);
}
//伸展,只需在伸展的最后维持节点t的size
void _splay(int t,int p,int&root){
    while( Node[t].parent != p ){
        int pp = Node[t].parent;
        if ( Node[pp].parent != p ){
            Node[pp].sn == Node[t].sn ?
                _rotate(pp) : _rotate(t);
        }
        _rotate(t);
    }
    _pushUp(t);
    if ( 0 == p ) root = t;
    return;
}

再为伸展树提供3个辅助函数。

//获取一个新的可用节点
inline int _newNode(){
    ++toUsed;
    memset(Node+toUsed,0,sizeof(node_t));
    return toUsed;
}
//在root树上查找键为key的节点,parent为其父节点
int _advance(int root,int&parent,key_t key){
    if ( 0 == root ) return parent = 0;

    int t = root;
    parent = Node[t].parent;
    while( t && key != Node[t].key ){
        parent = t;
        t = key < Node[t].key ? Node[t].child[LEFT] : Node[t].child[RIGHT];
    }
    return t;
}
//在root树上递归查找第kth个数,编号从1开始。这是size应用的关键,但其实很好理解。
int _kth(int root,int kth){
    int son = Node[root].child[LEFT];
    int sz = son ? Node[son].size + 1 : 1;

    if ( kth < sz ) return _kth(son,kth);
    if ( sz < kth ) return _kth(Node[root].child[RIGHT],kth-sz);
    return root;
}

接下来为伸展数提供对外接口,根据这道题的要求,只需提供3个函数即可。

//初始化
inline void init(){
    toUsed = 0;
    memset(Node,0,sizeof(node_t));
}
//在root树上插入一个节点
void insert(int&root,key_t key,value_t value=0){
    int t = _newNode();
    Node[t].key = key;
    Node[t].value = value;
    Node[t].size = 1;

    if ( 0 == root ){
        root = t;
        return;
    }

    int p;
    _advance(root,p,key);

    int sn = key < Node[p].key ? LEFT : RIGHT;
    _link(p,sn,t);
    _splay(t,0,root);
}
//在root树上查找第kth数,编号从1开始
int select(int& root,int kth){
    int t = _kth(root,kth);
    _splay(t,0,root);
    return t;
}

接下来只要完成主函数即可。完整的代码如下。

#include <cstdio>
#include <cstring>
using namespace std;

int const SIZE = 1111111;
int const LEFT = 0;
int const RIGHT = 1;
struct key_t{
    int key;   
    int order;
    key_t(int a=0,int b=0):key(a),order(b){}
};
bool operator < (key_t const&l,key_t const&r){
    return l.key < r.key || (l.key == r.key && l.order < r.order);
}
bool operator == (key_t const&l,key_t const&r){
    return l.key == r.key && l.order == r.order;
}
bool operator != (key_t const&l,key_t const&r){
    return !(l == r);
}
typedef int value_t;
struct node_t{
    int parent;
    int child[2];
    int sn;
    key_t key;
    value_t value;
    int size;
}Node[SIZE];
int toUsed = 0;

inline void _pushUp(int t){
    Node[t].size = 1;
    int son = Node[t].child[LEFT];
    if (son) Node[t].size += Node[son].size;
    son = Node[t].child[RIGHT];
    if (son) Node[t].size += Node[son].size;
}
inline void _link(int p,int sn,int t){
    Node[p].child[sn] = t;
    Node[t].parent = p;
    Node[t].sn = sn;
}
inline void _rotate(int t){
    int p = Node[t].parent;
    int sn = Node[t].sn;
    int osn = sn ^ 1;

    _link(p,sn,Node[t].child[osn]);
    _link(Node[p].parent,Node[p].sn,t);
    _link(t,osn,p);

    _pushUp(p);
}
void _splay(int t,int p,int&root){
    while( Node[t].parent != p ){
        int pp = Node[t].parent;
        if ( Node[pp].parent != p ){
            Node[pp].sn == Node[t].sn ?
                _rotate(pp) : _rotate(t);
        }
        _rotate(t);
    }
    _pushUp(t);
    if ( 0 == p ) root = t;
    return;
}
inline void init(){
    toUsed = 0;
    memset(Node,0,sizeof(node_t));
}
inline int _newNode(){
    ++toUsed;
    memset(Node+toUsed,0,sizeof(node_t));
    return toUsed;
}
int _advance(int root,int&parent,key_t key){
    if ( 0 == root ) return parent = 0;

    int t = root;
    parent = Node[t].parent;
    while( t && key != Node[t].key ){
        parent = t;
        t = key < Node[t].key ? Node[t].child[LEFT] : Node[t].child[RIGHT];
    }
    return t;
}
void insert(int&root,key_t key,value_t value=0){
    int t = _newNode();
    Node[t].key = key;
    Node[t].value = value;
    Node[t].size = 1;

    if ( 0 == root ){
        root = t;
        return;
    }

    int p;
    _advance(root,p,key);

    int sn = key < Node[p].key ? LEFT : RIGHT;
    _link(p,sn,t);
    _splay(t,0,root);
}
int _kth(int root,int kth){
    int son = Node[root].child[LEFT];
    int sz = son ? Node[son].size + 1 : 1;

    if ( kth < sz ) return _kth(son,kth);
    if ( sz < kth ) return _kth(Node[root].child[RIGHT],kth-sz);
    return root;
}

int select(int& root,int kth){
    int t = _kth(root,kth);
    _splay(t,0,root);
    return t;
}

int main(){
    init();
    int root = 0;
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;++i){
        int x;
        scanf("%d",&x);
        insert(root,key_t(x,i));
    }
    char tmp[10];
    scanf("%s%d",tmp,&n);
    for(int i=0;i<n;++i){
        int k;
        scanf("%d",&k);
        int t = select(root,k);
        printf("%d\n",Node[t].key.key);
    }
    return 0;
}
版权声明:本文为湖南师范大学RBS原创文章,转载请注明出处。

相关文章推荐

POJ 3481 Double Queue 伸展树splay + 删除节点

题目:http://poj.org/problem?id=3481题意:有一组操作,有如下三种: 0. 0,结束操作 1. 1 k p,把一个客户k加入到队列中,优先级为p 2. 2, 把队列...

伸展树应用初步——解决区间问题

伸展树的基本操作就是伸展,也就是将指定节点旋转至树根(同时不改变排序二叉树的性质)。在这个操作的基础上,配合节点中保存额外的数据域,伸展树可以完成多种任务,包括各种区间问题。     伸展树的节点除了...

伸展树的基本操作与应用 IOI2004 国家集训队论文 杨思雨

伸展树的基本操作与应用 安徽省芜湖一中 杨思雨 【关键字】 伸展树 基本操作 应用 【摘要】 本文主要介绍了伸展树的基本操作以及其在解题中的应用。全文可以分为以下四个部分。 第一部分引言,主要说明了...

伸展树的基本实现和区间操作

  • 2015年11月20日 15:52
  • 31KB
  • 下载

Splay(伸展树)模板

  • 2015年01月10日 15:42
  • 4KB
  • 下载

Splay Tree(伸展树)[NOI2005]维修数列

伸展树         概述:不同于线段树的以空间换取时间,用多余的节点去存储多余的信息,来达到降低时间复杂度。SplayTree基于一种更简单的思想,为了使整个查找时间更小,被查频率高的那些条目就应...

伸展树C++类实现<二>自顶向下设计

上一篇主要基于父节点形式的自底向上的设计实现伸展树,这一篇将实现基于左右辅助树的自顶向下的设计。 这种方式不需要节点存储其父节点的指针。当我们沿着树向下搜索某个节点x时,将搜索路径上的节点及其子树移...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:伸展树的节点的size域的应用
举报原因:
原因补充:

(最多只允许输入30个字)