【算法杂谈】算法艺术:Treap的“优美”写法

Treap有很多写法,今天研究了一下,觉得这种写法比较好(主要是代码比原来短多了):
如果是直接编的话,会比较烦,因为很多代码是对称的(对于旋转,插入,删除这些操作来说).
所以我们可以用0表示左,1表示右.那么son[0]是左儿子,son[1]是右儿子;Rotate(p,0)表示;左旋Rotate(p,1)表示右旋.
这样的话,很多对称的操作,都可以通过传递一个bool参数统一起来,就不用写两个对称的代码了.
定义:

struct Node
{
       Node *son[2];  //用指针写会比较方便
       int Key,w,Size;   //Key是关键字,w的随机生成的修正值,Size是子树大小
       Node(int a,int b,int c,Node *d):Key(a),w(b),Size(c)
       {
           son[0]=son[1]=d;
       }
}*__root,*null;

其中,__root是根结点,null是哨兵,空节点都指向哨兵.当然哨兵要初始化.
认真分析旋转,插入,删除操作之后会发现,哨兵的作用很大,很多判断都简化了.
其实可以加个优化,就是把重复的结点合并(在大多数题目上都可以这样做).
初始化:

void init()
{
     null=new Node(-oo,oo,0,0);  //这里维护的是小根堆
     __root=null=null->son[0]=null->son[1]=null;
 }

核心操作–旋转:
先来看看原始的左旋和右旋:

void Rotate_left(Node *&p)
{
     Node *u=p->son[1];
     p->son[1]=u->son[0];
     u->son[0]=p;
     u->Size=p->Size;
     p->Size=p->son[0]->Size+p->son[1]->Size+1;
     p=u;
 }
void Rotate_right(Node *&p)
{
     Node *u=p->son[0];
     p->son[0]=u->son[1];
     u->son[1]=p;
     u->Size=p->Size;
     p->Size=p->son[0]->Size+p->son[1]->Size+1;
     p=u;
 }

然后发现,它们的差异,只不过是前三句代码的下标而已.所以,果断用bool替代.
写inline会比较快:

inline void Rotate(Node *&p,bool b)  //Rotate(p,0)表示;左旋Rotate(p,1)表示右旋
{
       Node *u=p->son[!b];
       p->son[!b]=u->son[b];
       u->son[b]=p;
       u->Size=p->Size;
       p->Size=p->son[0]->Size+p->son[1]->Size+1;
       p=u;
 }

插入:

void Insert(Node *&p,const int &x) //注意引用
{
     if (p==null)   //如果找到了位置就插入
     {
        p=new Node(x,rand(),1,null);
        return;
     }
     bool b=(x>p->Key);  //判断是往左子树还是右子树插入
     Insert(p->son[b],x);
     p->Size++;  //维护Size
     if (p->son[b]->w<p->w) Rotate(p,!b);  //在左子树就右旋,在右子树就左旋
 }

删除:

void Delete(Node *&p,const int &x)
{
     if (p->son[0]==null && p->son[1]==null)
     {
        p=null;//这里是等到目标结点变为叶子才删除,仅比常规的删除多做了一次旋转,但代码短多了
        return;
     }
     bool b;  //这里的b为if语句后的操作提供了便利
     if (p->Key==x)
     {
        b=(p->son[1]->w>p->son[0]->w);
        Rotate(p,b);
     }  else b=(x>p->Key);
     p->Size--;
     Delete(p->son[b],x);
 }

认真观察之后,可以发现这种删除可以写成非递归的,但由于引用p比较麻烦,所以就干脆写递归了.
下面的是次要的操作,基本上就和原来的一样了,不过也贴贴代码吧.
因为这些操作都可以写成非递归的,所以就非递归了(非递归比较快).

int Find(int x)  //返回第x小的元素
{
    Node *p=__root;
    for (; p->son[0]->Size+1!=x; )
     if (p->son[0]->Size<x)
     {
        x-=p->son[0]->Size+1;
        p=p->son[1];
     }  else p=p->son[0];
    return p->Key;
}
int Rank(int x)  //返回x的排名(第几小)
{
    Node *p=__root;
    int Count=0;
    for (; p!=null; )
     if (p->Key<x)
     {
        Count+=p->son[0]->Size+1;
        p=p->son[1];
     }  else p=p->son[0];
    return Count+1;
}
int Pred(int x)  //返回x的前驱(这里默认x有前驱)
{
    Node *p=__root;
    int ans;
    for (; p!=null; )
     if (p->Key<x)
     {
        ans=p->Key;
        p=p->son[1];
     }  else p=p->son[0];
    return ans;
}
int Succ(int x)  //返回x的后继(这里默认x有后继)
{
    Node *p=__root;
    int ans;
    for (; p!=null; )
     if (p->Key>x)
     {
        ans=p->Key;
        p=p->son[0];
     }  else p=p->son[1];
    return ans;
}

转载自:http://blog.sina.com.cn/s/blog_70811e1a01014nf5.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值