伸展树的节点的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原创文章,转载请注明出处。

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

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

伸展树(Splay tree)图解与实现

一、伸展树  本文介绍了二叉查找树的一种改进数据结构–伸展树(Splay Tree)。它的主要特点是不会保证树一直是平衡的,但各种操作的平摊时间复杂度是O(log n),因而,从平摊复杂度上看,二叉...
  • u014634338
  • u014634338
  • 2015年11月02日 16:20
  • 3160

AVL树、splay树(伸展树)和红黑树比较

一、AVL树: 优点:查找、插入和删除,最坏复杂度均为O(logN)。实现操作简单     如过是随机插入或者删除,其理论上可以得到O(logN)的复杂度,但是实际情况大多不是随机的。如果是随机的,则...
  • u010585135
  • u010585135
  • 2014年12月10日 21:58
  • 2516

【伸展树篇】我仍旧不记得那天参考的代码---存父节点的Splay树

这货也是参考的某一份PASCAL代码,但是在学校敲得,网页早就忘了,但是Splay的文档就那么几份,反正是OI国家集训队论文里的那份源码的修改版,来源说清了... 这货才是正统的Splay吧,所有...
  • zhenaodingpao
  • zhenaodingpao
  • 2013年10月15日 02:36
  • 437

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

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

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

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

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

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

节点大小平衡树((Size Balanced Tree, c++和python源码)

  • 2014年02月26日 15:31
  • 5KB
  • 下载

郁闷的出纳员(伸展树) C语言

  • 2018年01月03日 08:02
  • 17KB
  • 下载

Splay(伸展树)模板

  • 2015年01月10日 15:42
  • 4KB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:伸展树的节点的size域的应用
举报原因:
原因补充:

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