关于树论【伸展树】

搬运:搞了LCT后想来回顾一下伸展树。 
用codevs的2443为例吧。

讲讲结构,函数的意思,写了注释。 
首先他是一棵二叉树,并且是可以动的。 
然后他有个性质,点x左子树(没错是整个子树)的值恒小于x的值,右子树(没错是整个子树)的值恒大于x的值。 
然后他可以。看,这下3变成根了。因为这样,我们就可以找到适合新来的点插入的位置,以及删除点时保持结构。 

#include<cstdio>
#include<cstring>
using namespace std;
int root;
struct trnode
{
    int d,f,c,n,son[2];
    //d为值,f为父亲的编号,c为控制的节点个数,n为同值的节点个数
}tr[110000];int len;
void update(int x)//更新x所控制的节点数
{
    int lc=tr[x].son[0],rc=tr[x].son[1];
    tr[x].c=tr[lc].c+tr[rc].c+tr[x].n;
}
void add(int d,int f)//添加值为d的点,认f为父亲,同时,f也认它为孩子
{
    len++;
    tr[len].d=d;tr[len].n=1;tr[len].c=1;tr[len].f=f;
    if(d<tr[f].d)tr[f].son[0]=len;
    else tr[f].son[1]=len;
    tr[len].son[0]=tr[len].son[1]=0;
}
int findip(int d)//找值为d的节点的地址,如果不存在d,有可能是接近d的(或大或小)
{
    int x=root;
    while(tr[x].d!=d)
    {
        if(d<tr[x].d)
        {
            if(tr[x].son[0]==0)break;
            else x=tr[x].son[0];
        }
        else
        {
            if(tr[x].son[1]==0)break;
            else x=tr[x].son[1];
        }
    }
    return x;
}
void rotate(int x,int w)//左旋(x,0)或者右旋 (x,1)
{
    int f=tr[x].f,ff=tr[f].f;//x在旋转之前,要确定x的父亲f和爷爷ff
    int r,R;//r表示儿辈,R表示父辈
     //有四个角色:我x,我的儿子,我的父亲f,我的爷爷ff
    r=tr[x].son[w];R=f;//我的儿子->当我父亲的儿子
    tr[R].son[1-w]=r;
    if(r!=0)tr[r].f=R;

    r=x;R=ff;//我->当我爷爷的儿子
    if(tr[ff].son[0]==f)tr[R].son[0]=r;
    else tr[R].son[1]=r;
    tr[r].f=R;

    r=f;R=x;//我的父亲->当我儿子
    tr[R].son[w]=r;
    tr[r].f=R;

    update(f);// 先更新处于下层的点f
    // PS:父亲变成我的儿子了,他要统计他的新儿子的数量再告诉我
    update(x);// 再更新上层的x
}
void splay(int x,int rt)//该函数功能是为了让x成为rt的孩子(左或右都行)
{
    while(tr[x].f!=rt)//如果rt是x的父亲,则什么都不用做,否则x就要不断向上旋转
    {
        int f=tr[x].f,ff=tr[f].f;//准备x的父亲和爷爷
        if(ff==rt)//如果x的爷爷是rt,那么x只需要旋转一次(相当于跳一层)
        {
            if(x==tr[f].son[0])rotate(x,1);//如果是左儿子就向右转
            else rotate(x,0);
        }
        else
        {
            if(tr[f].son[0]==x&&tr[ff].son[0]==f){rotate(f,1);rotate(x,1);}
            else if(tr[f].son[1]==x&&tr[ff].son[0]==f){rotate(x,0);rotate(x,1);}
            else if(tr[f].son[0]==x&&tr[ff].son[1]==f){rotate(x,1);rotate(x,0);}
            else if(tr[f].son[1]==x&&tr[ff].son[1]==f){rotate(f,0);rotate(x,0);}
        }
    }
    if(rt==0)root=x;
}
void ins(int d)//插入数值为d的一个节点
{
    if(root==0)
    {
        add(d,len);root=len;
        return ;
    }
    int x=findip(d);
    if(tr[x].d==d)
    {
        tr[x].n++;
        update(x);
        splay(x,0);
    }
    else
    {
        add(d,x);
        update(x);
        splay(len,0);
    }
}
void del(int d)//删除数值为d的一个节点
{
    int x=findip(d);splay(x,0);
    if(tr[x].d!=d){return ;}
    if(tr[x].n>1){tr[x].n--;return ;}//有多个,就不用删点
    if(tr[x].son[0]==0&&tr[x].son[1]==0){len=0;root=0;}//没儿子..整棵树绝后了
    else if(tr[x].son[0]!=0&&tr[x].son[1]==0){root=tr[x].son[0];tr[root].f=0;}
    //有左儿子,他就是老大了
    else if(tr[x].son[0]==0&&tr[x].son[1]!=0){root=tr[x].son[1];tr[root].f=0;}
    //有右儿子,他就是老大了
    else//左右都有,找到左儿子最大的子孙(ta就没有右儿子了),让右儿子做他的右儿子
    {
        int p=tr[x].son[0];
        while(tr[p].son[1]!=0)p=tr[p].son[1];
        splay(p,x);

        int r=tr[x].son[1],R=p;
        tr[R].son[1]=r;
        tr[r].f=R;

        root=R;tr[root].f=0;
        update(R);
    }
}
int findpaiming(int d)//找排名
{
    int x=findip(d);splay(x,0);
    return tr[tr[x].son[0]].c+1;
}
int findshuzi(int k)//找排名为k的值
{
    int x=root;
    while(1)
    {
        int lc=tr[x].son[0],rc=tr[x].son[1];
        if(k<=tr[lc].c)x=lc;
        else if(k>tr[lc].c+tr[x].n){k-=tr[lc].c+tr[x].n;x=rc;}
        else break;
    }
    splay(x,0);
    return tr[x].d;
}
int findqianqu(int d)//找前驱
{
    int x=findip(d);splay(x,0);
    if(d<=tr[x].d&&tr[x].son[0]!=0)
    //如果是if(d<tr[x].d&&tr[x].son[0]!=0)则找到的是:小于等于d的前驱
    {
        x=tr[x].son[0];
        while(tr[x].son[1]!=0)x=tr[x].son[1];
    }
    if(d<=tr[x].d)x=0;//如果是if(tr[x].d>d)则找到的是:小于等于d的前驱
    return x;
}
int findhouji(int d)//找后继..和找前驱差不多
{
    int x=findip(d);splay(x,0);
    if(d>=tr[x].d&&tr[x].son[1]!=0)
    {
        x=tr[x].son[1];
        while(tr[x].son[0]!=0)x=tr[x].son[0];
    }
    if(d>=tr[x].d)x=0;
    return x;
}
int main()
{
    int n;scanf("%d",&n);
    root=0;len=0;
    for(int i=1;i<=n;i++)
    {
        int cz,x;scanf("%d%d",&cz,&x);
        if(cz==1)ins(x);
        else if(cz==2)del(x);
        else if(cz==3)printf("%d\n",findpaiming(x));
        else if(cz==4)printf("%d\n",findshuzi(x));
        else if(cz==5)printf("%d\n",tr[findqianqu(x)].d);
        else if(cz==6)printf("%d\n",tr[findhouji(x)].d);
    }
    return 0;
}
时间: 2024-10-20 08:27:50

关于树论【伸展树】的相关文章

《树》之伸展树

本文介绍什么? 使用伸展树有什么样的效果: 伸展树的定义: 伸展树ADT具体实现过程的描述: 代码实现. 一.使用伸展树(splay tree)的效果: 使用伸展树时,对伸展树上任意一次操作的最坏运行时间为 O( N ):但是,它保证了连续M次操作花费的最多时间为O(M ㏒N),从而可以推算出对伸展树的每一次操作的摊还时间为O( ㏒N). 二.伸展树的定义: 对于一颗二查查找树进行操作时,每访问一个节点,该节点都通过旋转操作被放到根上.这样的一颗二查查找树称为伸展树. 三.伸展树ADT的描述:

poj_3580 伸展树

自己伸展树做的第一个题 poj 3580 supermemo. 题目大意 对一个数组进行维护,包含如下几个操作: ADD x, y, d 在 A[x]--A[y] 中的每个数都增加d REVERSE x, y 将 A[x]--A[y] 中的数进行反转,变为 A[y],A[y-1]....A[x+1],A[x] REVOLVE x, y, T 将 A[x]--A[y]中的数连续右移T次 INSERT x, P 在A后添加数P DELETE x 删除A[x] MIN x, y 查询A[x]--A[y

树-伸展树(Splay Tree)

伸展树概念 伸展树(Splay Tree)是一种二叉排序树,它能在O(log n)内完成插入.查找和删除操作.它由Daniel Sleator和Robert Tarjan创造. (01) 伸展树属于二叉查找树,即它具有和二叉查找树一样的性质:假设x为树中的任意一个结点,x节点包含关键字key,节点x的key值记为key[x].如果y是x的左子树中的一个结点,则key[y] <= key[x]:如果y是x的右子树的一个结点,则key[y] >= key[x]. (02) 除了拥有二叉查找树的性质

Splay Tree(伸展树)

参考:<数据结构(C++语言版)>邓俊辉著 (好书 一. 伸展树(由 D. D. Sleator 和 R. E. Tarjan 于 1985 年发明)也是平衡二叉搜索树的一种形式.相对于 AVL 树,伸展树的实现更为简洁 伸展树无需时刻都严格地保持全树的平衡,但却能够在任何足够长的真实操作序列中,保持分摊意义上的高效率 伸展树也不需要对基本的二叉树节点结构做任何附加的要求或改动,更不需要记录平衡因子或高度之类的额外信息,故适用范围更广 二.局部性 信息处理的典型模式是,将所有的数据项视作一个集

伸展树、B树与B+树

B树 如果数据装不下主存,那么这就意味着必须把数据结构放在磁盘上,此时,因为大O模型不再适应,所以导致规则发生了变化. 不平衡二叉树的最坏情形下它具有线性的深度,由于典型的AVL树接近到最优的高度,但二叉查找树不能进到低于LogN.一棵完全二叉树的高度大约为与log2N,而一棵完全M叉树的高度大约是logmN. B树能保证少数的磁盘访问(B-树) 阶为M的B树是一棵具有下列特性的树 有关b树的一些特性,注意与后面的b+树区分: 1 关键字集合分布在整颗树中: 2 非叶节点存储直到M-1个关键字以

poj_3468 伸展树

题目大意 一个数列,每次操作可以是将某区间数字都加上一个相同的整数,也可以是询问一个区间中所有数字的和.(这里区间指的是数列中连续的若干个数)对每次询问给出结果. 思路 1. 伸展树的一般规律 对于区间的查找更新操作,可以考虑使用伸展树.线段树等数据结构.这里使用伸展树来解决.     伸展树对数组进行维护的核心思想是,将需要维护的一组数单独提取出来,形成一棵子树(一般为整棵树的根节点的右子节点的左孩子节点 为根),然后再这个子树上进行操作.此时进行某些操作(如 ADD, SUM 等),只需要在

HYSBZ 1503 郁闷的出纳员 伸展树

题目链接: https://vjudge.net/problem/26193/origin 题目描述: 中文题面....... 解题思路: 伸展树, 需要伸展树的模板, 突然发现自己昨天看到的模板不是太好, 现在又新找了一个,  很简练, 自己将模板的实现从头到尾看了一遍, 觉得数组实现的实在是非常的巧妙, 然后自己照着敲了一遍, 边敲边看, 崩掉了.....肯定是哪里手残了, 没有必要浪费时间去改了, 有那时间不如看点别的 代码: #include <iostream> #include &

伸展树整理

伸展树 1.在伸展树上的一般操作都基于伸展操作:假设想要对一个二叉查找树执行一系列的查找操作,为了使整个查找时间更小,被查频率高的那些条目就应当经常处于靠近树根的位置.因此,在每次查找之后对树进行重构,把被查找的条目搬移到离树根近一些的地方.伸展树应运而生.伸展树是一种自调整形式的二叉查找树,它会沿着从某个节点到树根之间的路径,通过一系列的旋转把这个节点搬移到树根去. 2.操作 (1)伸展操作 伸展操作Splay(x,S)是在保持伸展树有序性的前提下,通过一系列旋转将伸展树S中的元素x调整至树的

Splay伸展树

伸展树,感觉对我这种菜鸟还是有些吃力,主要也是旋转的时候吧,把要查询的节点旋转到根节点,看网上是有两种方法,一是从上到下,一是从下到上.从上到下,是把树拆分成中树,左树和右树,始终保证目标节点在中树,不断向下把中树的节点添到右树或者左树,直到目标节点为中树的根,再将三树合并起来.另外一种从下到上,旋转操作类似AVL树中的旋转,但是判断好像不是很好写,我写的是从上到下的,另外删除也很巧妙,先把目标节点旋转到根,若此时左子树为空直接删除,否则找到左子树最右的节点当头,利用伸展树的特殊旋转就可以一步删

HNOI2002(伸展树)

营业额统计 Time Limit:5000MS     Memory Limit:165888KB     64bit IO Format:%lld & %llu Submit Status Description 营业额统计 Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况. Tiger拿出了公司的账本,账本上记录了公司成立以来每天的营业额.分析营业情况是一项相当复杂的工作.由于节假日,大减价或者是其他情况的时候,营业额会出现一定的波动