【Treap】

【Treap浅析】

  Treap作为二叉排序树处理算法之一,首先得清楚二叉排序树是什么。对于一棵树的任意一节点,若该节点的左子树的所有节点的关键字都小于该节点的关键字,且该节点的右子树的所有节点的关键字都大于该节点的关键字,则这棵树是一棵二叉排序树。

  Treap在每一个节点中有两个最关键的元素——weight和value

  value是在创建这个新节点时赋予该节点的数值大小,Treap利用每个节点的value值将整棵树维持成一个二叉排序树

  weight是在创建这个新节点时赋予该节点的伪随机数,Treap利用每个节点的weight值将整棵树维持成一个最小堆(或最大堆)

  看似不相容的二叉排序树和堆的结合却正是Treap的关键之处。如果用递增(或递减)数列只是对单纯的二叉排序树进行插入操作的话可以想见会变成这种情况:

  此时一棵二叉搜索树就退化成了数链,每次对树上进行查询都会退化成O(N)。而利用随机数的随机性把二叉树维护成一个堆,就可以尽量使一条不严格数链维护成一棵伪平衡树。

  那Treap是如何同时维护堆和二叉搜索树的呢?这就涉及到旋转操作

  Treap涉及两种旋转操作:左旋和右旋。这里用最小堆举例子(懒得用电脑画图(逃:

  利用代码看一下左、右旋代码的实现:

  1.左旋:

 void lt(int &k){
int tmp = t[k].r;
t[k].r = t[tmp].l;
t[tmp].l = k;
t[tmp].size = t[k].size;
t[k].size = t[t[k].l].size + t[t[k].r].size + t[k].cnt;
k = tmp;
}

  2.右旋:

 void rt(int &k){
int tmp = t[k].l;
t[k].l = t[tmp].r;
t[tmp].r = k;
t[tmp].size = t[k].size;
t[k].size = t[t[k].r].size + t[t[k].l].size + t[k].cnt;
k = tmp;
}

  除了旋转之外还有许多其他的操作,结合代码看一下:

  插入节点:

 void Insert(int &k,int num){
if(!k){//如果这是一个没有被创建过的新的节点就创建一个新的节点
k = ++size;
t[k].wt = rand();//为该新节点赋予一个随机值weight
t[k].val = num;//为该节点赋予一个数值大小value
t[k].cnt = t[k].size = ;
return ;
}
++t[k].size;//插入了一个新节点,所以路径上每个结点的子树大小++
if(num == t[k].val){//如果这个数值被添加到树中过,就把那个节点处的cnt++
++t[k].cnt;
return ;
}
if(num < t[k].val){//如果要被插入的数值比当前结点数值小,向左子树寻找
Insert(t[k].l,num);
if(t[t[k].l].wt < t[k].wt)//如果左右孩子其中一个不满足最小堆,即其中一个小于父亲节点,则通过旋转使其满足最小堆
rt(k);
}
else{//如果要被插入的数值比当前结点数值大,向右子树寻找
Insert(t[k].r,num);
if(t[t[k].r].wt < t[k].wt)//维护最小堆
lt(k);
23 }

  删除节点:

  void Delete(int &k,int num){
if(!k)
return ;
if(t[k].val == num){
if(t[k].cnt > ){//如果某个数值被插入2次及以上,在节点处cnt--即可。
--t[k].cnt;
--t[k].size;
return ;
}
else if(!(t[k].l * t[k].r)){//如果那个数值只被插入了一次且左右孩子至少有一个为空,则直接把不为空的孩子接到父亲节点上。
k = t[k].l + t[k].r;
return ;
}
else{//如果两个孩子都不为空,则把两个孩子中weight较小的旋转到父亲节点,把原来的要删除的节点一直旋转成叶子节点或只有一个非空孩子的节点。
if(t[t[k].l].wt <= t[t[k].r].wt){
rt(k);
Delete(k,num);
}
else{
lt(k);
Delete(k,num);
}
}
}
else{
--t[k].size;//经过的路径size全都--
if(num < t[k].val)//判断接下来寻找num位置的走向
Delete(t[k].l,num);
else
Delete(t[k].r,num);
}
}

  求序列中某一数值在数列中的排名:

 int Rank(int &k,int num){
if(!k)
return ;
if(t[k].val == num)
return t[t[k].l].size + ;//若与当前结点相等,左节点size+1就是排名,防止t[k].cnt影响排名
else if(t[k].val > num)
return Rank(t[k].l,num);
else
return Rank(t[k].r,num) + t[t[k].l].size + t[k].cnt;//递归到右子树返回的是在右子树之内的排名,要加上父节点的cnt和左孩子的size
}

  求序列中第k大数值:

 int Search(int &k,int num){
if(!k)
return ;
if(num <= t[t[k].l].size)
return Search(t[k].l,num);
else if(num <= t[t[k].l].size + t[k].cnt)//如果排名属于(左孩子size,左孩子size + 当前结点cnt]区间内,则第k大数就是该节点的数值大小。
return t[k].val;
else
return Search(t[k].r,num - t[t[k].l].size - t[k].cnt);//进行右子树的递归,排名需剪掉左子树的size和当前结点cnt,这样才能保证在右子树找到正确结果。
}

  求前序及后序:

 int Pre(int &k,int num){
if(!k)
return -2147483648;//返回极小值避免对下方max函数产生影响
if(t[k].val >= num)
return Pre(t[k].l,num);
else
return max(t[k].val,Pre(t[k].r,num));//如果当前结点数值小于num,则在该节点右子树寻找比该节点数值更大的节点,后序同理
}
int Nex(int &k,int num){
if(!k)
return 2147483647;
if(t[k].val <= num)
return Nex(t[k].r,num);
else
return min(t[k].val,Nex(t[k].l,num));
}

  这就是比其他平衡树要好写的多的Treap了。Treap功能十分局限,但有个Treap2.0叫作无旋Treap,功能就和其他平衡树相差无几了,学了再更。

【数据结构】【平衡树】浅析树堆Treap的更多相关文章

  1. 【bzoj3196】Tyvj 1730 二逼平衡树 线段树套Treap

    题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:1.查询k在区间内的排名2.查询区间内排名为k的值3.修改某一位值上的数值4.查询k在区间内的前驱(前驱定义 ...

  2. BZOJ - 3196 Tyvj 1730 二逼平衡树 (线段树套treap)

    题目链接 区间线段树套treap,空间复杂度$O(nlogn)$,时间复杂度除了查询区间k大是$O(log^3n)$以外都是$O(log^2n)$的. (据说线段树套线段树.树状数组套线段树也能过?) ...

  3. bzoj 3196 && luogu 3380 JoyOI 1730 二逼平衡树 (线段树套Treap)

    链接:https://www.lydsy.com/JudgeOnline/problem.php?id=3196 题面; 3196: Tyvj 1730 二逼平衡树 Time Limit: 10 Se ...

  4. [bzoj3196][Tyvj 1730][二逼平衡树] (线段树套treap)

    Description 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作: 1.查询k在区间内的排名 2.查询区间内排名为k的值 3.修改某一位值上的数值 4.查询k在 ...

  5. BZOJ3224/LOJ104 普通平衡树 treap(树堆)

    您需要写一种数据结构,来维护一些数,其中需要提供以下操作:1. 插入x2. 删除x(若有多个相同的数,因只删除一个)3. 查询x的排名(若有多个相同的数,因输出最小的排名)4. 查询排名为x的数5. ...

  6. 可旋转Treap(树堆)总结

    树堆,在数据结构中也称Treap,是指有一个随机附加域满足堆的性质的二叉搜索树,其结构相当于以随机数据插入的二叉搜索树.其基本操作的期望时间复杂度为O(logn).相对于其他的平衡二叉搜索树,Trea ...

  7. BZOJ_3196_二逼平衡树_(树套树,线段树+Treap)

    描述 http://www.lydsy.com/JudgeOnline/problem.php?id=3196 可以处理区间问题的平衡树. 3196: Tyvj 1730 二逼平衡树 Time Lim ...

  8. BZOJ3196 Tyvj1730 二逼平衡树 【树套树】 【线段树套treap】

    BZOJ3196 Tyvj1730 二逼平衡树 Description 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作: 1.查询k在区间内的排名 2.查询区间内排名 ...

  9. 树堆(Treap)

    平衡树 简介: 平衡二叉树(Balanced Binary Tree)具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树.平衡二叉树的常用实现方 ...

随机推荐

  1. send,recv,sendto,recvfrom ~转载

     send,recv,sendto,recvfrom send函数 int send( SOCKET s,    const char FAR *buf,    int len,    int fla ...

  2. socket 编程 TCP 实现简单聊天功能【转】

    转自:http://blog.csdn.net/liujia2100/article/details/9006479 版权声明:本文为博主原创文章,未经博主允许不得转载. 各个主要函数的功能: .so ...

  3. 异步网络模块之aiohttp的使用(一)

    异步网络模块之aiohttp的使用(一) 平时我们也许用的更多的是requests模块,或者是requests_hml模块,但是他们都属于阻塞类型的不支持异步,速度很难提高,于是后来出现了异步的gre ...

  4. 调用start()与run()的区别

    1.调用start()方法: 通知“线程规划器”当前线程已经准备就绪,等待调用线程对象的run()方法.这个过程就是让系统安排一个时间来调用Thread中的run()方法,使线程得到运行,启动线程,具 ...

  5. Centos7 配置网络

    /* Centos7 的网络 不可以用ifconfig获取,需要安装包 所以 .*/ //查看ip [root@master ~]# ip a /* Centos7 的网卡名字与 Centos6有区别 ...

  6. java中的构造方法与其作用

    什么是构造方法呢? 方法名和类名相同 没有返回值类型,连void都不能写 没有具体的返回值 构造方法分为无参构造方法与有参构造方法. 先来看一下最简单的无参构造方法: Student.java pac ...

  7. 切面保存web访问记录

    package com.hn.xf.device.api.rest.aspect; import com.hn.xf.device.api.rest.authorization.manager.Tok ...

  8. addeventlistener监听scroll跟touch

    这三个事件只在手机上生效 touchstart,手指开始触屏 touchmove,手指移动 touchend,手指触屏结束   这个事件在手机上跟在pc端都生效 scroll事件     addeve ...

  9. 《java并发编程实战》读书笔记13--Java内存模型,重排序,Happens-Before

    第16章 Java内存模型 终于看到这本书的最后一章了,嘿嘿,以后把这本书的英文版再翻翻.这本书中尽可能回避了java内存模型(JMM)的底层细节,而将重点放在一些高层设计问题,例如安全发布,同步策略 ...

  10. jpa缓存导致无法查询到更新后的数据&android出现ANR的一个解决办法

    1. 向服务器更新记录后查询,始终查询不到更新后的信息 只能查到更新之前的,马上推断出是缓存的问题.网上搜索一番,将问题定位为jpa缓存,我们要设置jpa查询时不从缓存中取,直接从数据库中取,这样便能 ...