treap模版_bartender模板

treap模版_bartender模板#include#include#include#include#include#include#include#include#include#include#include#include#include#include#includeusingnamespacestd;typedefintll;typedefunsignedlonglongu

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定

之前我们讲到二叉搜索树,从二叉搜索树到2-3树到红黑树到B-树。
二叉搜索树的主要问题就是其结构与数据相关,树的深度可能会很大,Treap树就是一种解决二叉搜索树可能深度过大的另一种数据结构

Treap

Treap=Tree+Heap。Treap本身是一棵二叉搜索树,它的左子树和右子树也分别是一个Treap,和一般的二叉搜索树不同的是,
Treap纪录一个额外的数据,就是优先级。Treap在以关键码构成二叉搜索树的同时,还满足堆的性质。
这些优先级是是在结点插入时,随机赋予的,Treap根据这些优先级满足堆的性质。这样的话,Treap是有一个随机附加域满足堆的性质的二叉搜索树,其结构
相当于以随机数据插入的二叉搜索树。其基本操作的期望时间复杂度为O(logn)。相对于其他的平衡二叉搜索树,Treap的特点是实现简单,且能基本实现随机平衡的结构。

Treap维护堆性质的方法
只用到了旋转,只需要两种旋转,编程复杂度比Splay要小一些。

插入 

给节点随机分配一个优先级,先和二叉搜索树的插入一样,先把要插入的点插入到一个叶子上,然后
跟维护堆一样,如果当前节点的优先级比根大就旋转,如果
当前节点是根的左儿子就右旋,如果当前节点是根的右儿子就左旋

下面图解旋转操作:
treap模版_bartender模板


由于旋转是O(1)的,最多进行h次(h是树的高度),插入的复杂度是O(h)的,在期望情况下h=O(logn),所以它的期望复杂度是O(logn)。 

 

下面我们以一个
实例来图解Treap的插入过程,看完之后你一定对Treap的插入了然于胸。


treap模版_bartender模板


删除

删除一个节点有两种方式,可以像删除二叉树节点那样删除一个节点,也可以像删除堆中节点那样删除。

1、用二叉搜索树的方式删除

先在二叉查找树中找到要删除的节点的位置,然后根据节点分以下情况:


情况一:
该节点是
叶节点(没有非空子节 点的节点),直接把节点删除即可。

情况二:

该节点是
链节点(只有一个非空子节点的节点),为了删除这个节点而不影响它的子树,需要把它的子节点代替它的位置,然后把它删除。

treap模版_bartender模板


情况三:

该节点
有两个非空子节点。由于情况比较复杂,一般的策略是
用它右子树的最小值来代替它,然后把它删除。如图所示,删除节点2时,在它的右子树中找到最小的节点3, 该节点一定为待删除节点的后继。删除节点3(它可能是叶节点或者链节点),然后把节点2的值改为3。

(当然,我们也可以使用结点的左子树的最大值来替代它,
为了不使Treap向一边偏沉,我们可以随机地选取是用后继还是前驱代替它, 并保证两种选择的概率均等。)

treap模版_bartender模板

关于查找最小值:
基本方法就是从子树的根节点开始, 如果左子节点不为空,那么就访问左子节点,直到左子节点为空,当前节点就是该子树的最 小值节点。删除它只需用它的右子节点代替它本身。

2、用堆的方式删除

因为Treap满足堆性质,所以只需要把要删除的节点旋转到叶节点上,然后直接删除就可以了。
具体的方法:
如果该节点的左子节点的优先级小于右子节点的优先级,右旋该节点,使该节点降为右子树的根节点,然后访问右子树的根节点,继续操作;

反之,左旋该节点,使该节点降为左子树的根节点,然后访问左子树的根节点,继续操作,直到变成可以直接删除的节点。
(即:让小优先级的结点旋到上面,满足堆的性质)

treap模版_bartender模板

删除最多进行O(h)次旋转,期望复杂度是O(logn)。

查找

和一般的二叉搜索树一样,但是由于Treap的随机化结构,Treap中查找的期望复杂度是O(logn)。 

对比 

与 Splay树 相比:

Splay 和 BST 一样,不需要维护任何附加域,比 Treap 在空间上有节约。但 Splay 在查找时也会调整结构,这使得 Splay 灵活性稍有欠缺。
Splay 的查找插入删除等基本操作的时间复杂度为均摊O(logN)而非期望。可以故意构造出使 Splay 变得很慢的数据。



与AVL 红黑树相比:


AVL 和红黑树在调整的过程中,旋转都是均摊 O(1)的,而 Treap 要 O(logN)。与 Treap 的随机优先级不同,它们维护的附加域要动态的调整,而 Treap 的随机修正值一经生成不再改变,这一点使得灵活性不如 Treap。


AVL 和红黑树都是时间效率很高的经典算法,在许多专业的应用领域(如 STL)有着十分重要的地位。然而AVL和红黑树的编程实现的难度要比Treap大得多。

维护子树的大小

Treap 是一种排序的数据结构,如果我们想
查找第k小的元素
或者
询问某个元素在Treap中从小到大的排名
时,我们就必须知道每个子树中节点的个数。


由于插入、删除、旋转等操作,会使每个子树的大小改变,所 以我们必须对子树的大小进行动态的维护。




对于
旋转
,我们要在旋转后对子节点和根节点分别重新计算其子树的大小。 


对于
插入
,在寻找插入的位置时,每经过一个节点,都要先使以它为根的子树的大小增加 1,再递归进入子树查找。 


对于
删除
,在寻找待删除节点,递归返回时要把所有的经过的节点的子树的大小减少 1。要注意的是,删除之前一定要保证待删除节点存在于 Treap 中。


 


(维护了子树的大小,我们就可以求“
排名第k的元素
”这样的问题了。快排也能求“第k大”问题,但是快排适合静态的数据,对于经常变动的数据,我们用树结构来维护更灵活。)


(我们还可以求“
某个元素的排名
”,我们的基本思想是查找目标元素在 Treap 中的位置,且在查找路径中统计出小于目标元素的节点的总个数,目标元素的排名就是总个数+1。即:在查找的路径中统计小于目标元素的个数,当找到目标元素后加上其左子树的个数即可。)

举个栗子

Treap 是一种高效的动态的数据容器,据此我们可以用它处理一些数据的动态统计问题。

一、一个应用实例
[问题描述] 有一个游戏排名系统,通常要应付三种请求:上传一条新的得分记录、查询某个玩家的当前 排名以及返回某个区段内的排名记录。当某个玩家上传自己最新的得分记录时,他原有的得 分记录会被删除。为了减轻服务器负担,在返回某个区段内的排名记录时,最多返回 10 条 记录。
[求]
(1)更新玩家的得分记录
(2)查询玩家排名(如果两个玩家的得分相同, 则先得到该得分的玩家排在前面。)
(3)查询第 Index 名开始的最多 10 名玩家名字

[解]
因为作为字符串的姓名是不便于处理的,我们给每个玩家都制定一个ID,首先要建立一个由姓名到玩家ID的映射数据结构。为了查找快速,可以用Trie树。之后我们建立一个双关键字的Treap,关键字1为得分从小到大,关键字2为时间戳从大到小,这种排列方式的逆序,恰好是我们要的顺序(也可以直接就是逆序)。

对于问题(1),先查询玩家是否已经存在,如果已经存在,在Treap中更新对应已经存在的记录。
对于问题(2),就是基本的求排名操作。
对于问题(3),就是分别查找第(总记录数 + 1 – k)小的记录。

二、双端优先队列的实现
优先队列(Priority Queue)是一种按优先级维护进出顺序的数据容器结构,可以选择维护实现取出最小值或最大值。我们通常用堆实现优先队列,通常取出最值的时间复杂度为 O(logN)。
用最小堆可以实现最小优先队列,用最大堆可以实现最大优先队列。但是如果我们要求一种 “双端优先队列”,即要求同时支持插入、取出最大值、取出最小值的操作,用一个单纯的堆就不能高效地实现了。
(可以用两个堆来实现,两堆中的元素都互指,但维护两个堆比较复杂。)

我们可以方便地使用Treap实现双端优先队列,只需建立一个 Treap,分别写出取最大值和最小值的功能代码就可以了, 无需做任何修改。由于Treap平衡性不如堆完美,但期望时间仍是 O(logN)。更重要的是在 实现的复杂程度上大大下降,而且便于其他操作的推广。所以,用 Treap 实现优先队列不失为一种便捷而又灵活的方法。

其它:

平衡树并不适合作为所有数据类型的数据的有序存储容器,因为可能有些类型的两个元素直接相互比较大小是十分 耗时的,这个常数时间的消耗是无法忍受的。例如字符串,作为检索字符串的容器,我们更推荐Trie树,而不是平衡树。平衡树仅适合做元素间相互比较时间很少的类型的有序存储容器。

关于查找最小值:

基本方法就是从子树的根节点开始, 如果左子节点不为空,那么就访问左子节点,直到左子节点为空,当前节点就是该子树的最 小值节点。删除它只需用它的右子节点代替它本身。


【参考】

中文维基百科http://zh.wikipedia.org/wiki/%E6%A0%91%E5%A0%86


《随机平衡二叉查找树Treap的分析与应用》 清华大学计算机系 郭家宝


#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<queue>
#include<vector>
#include<climits>
#include<string>
#include<cstdlib>
#include<set>
#include<stack>
#include<map>
#include<bitset>
#include<ctime>
using namespace std;
typedef int ll;
typedef unsigned long long ull;
inline ll read()
{
    char k=0;char ls;ls=getchar();for(;ls<'0'||ls>'9';k=ls,ls=getchar());
    ll x=0;for(;ls>='0'&&ls<='9';ls=getchar())x=x*10+ls-'0';
    if(k=='-')x=0-x;return x;
}
struct E{
    ll lc,rc;//左右孩子
    ll s,sum;//权值和随机数
    ll fa;//父亲
    ll size;//子节点大小
    ll js;//该节点元素计数
}t[1000050];
ll w;
ll size;
ll root;

inline ll find(ll s)//查找
{
    if(size==0)
        return 0;
    ll now=root;
    while(now)
    {
        if(t[now].s==s)
            return now;
        if(s<t[now].s)
        {
            now=t[now].lc;
            continue;
        }
        if(s>t[now].s)
        {
            now=t[now].rc;
            continue;
        }
    }
    return 0;
}

ll maxn()//最大值
{
    ll now=root;
    while(1)
    {
        if(t[now].rc==0)
            return now;
        now=t[now].rc;
    }
}

ll minn()//最小值
{
    ll now=root;
    while(1)
    {
        if(t[now].lc==0)
            return now;
        now=t[now].lc;
    }
}

void update(ll x)//更新平衡树尺寸
{
    ll now=root;
    while(1)
    {
        --t[now].size;
        if(now==x)return;
        if(t[x].s<t[now].s)
        {
            now=t[now].lc;
            continue;
        }
        if(t[x].s>t[now].s)
        {
            now=t[now].rc;
            continue;
        }
    }
    return;
}

void zig(ll x)//左旋
{
    t[t[x].fa].size-=t[t[x].rc].size;
    t[t[x].fa].size-=t[x].js;
    t[x].size+=t[t[t[x].fa].lc].size;
    t[x].size+=t[t[x].fa].js;
    if(root==t[x].fa)
        root=x;
    ll ff=t[t[x].fa].fa;
    t[t[x].fa].fa=x;
    if(ff!=0)
    {
        if(t[ff].lc==t[x].fa)
            t[ff].lc=x;
        if(t[ff].rc==t[x].fa)
            t[ff].rc=x;
    }
    if(t[x].lc!=0)
        t[t[x].lc].fa=t[x].fa;
    t[t[x].fa].rc=t[x].lc;
    t[x].lc=t[x].fa;
    t[x].fa=ff;
    return;
}

void zag(ll x)//右旋
{
    t[t[x].fa].size-=t[t[x].lc].size;
    t[t[x].fa].size-=t[x].js;
    t[x].size+=t[t[t[x].fa].rc].size;
    t[x].size+=t[t[x].fa].js;
    if(root==t[x].fa)
        root=x;
    ll ff=t[t[x].fa].fa;
    t[t[x].fa].fa=x;
    if(ff!=0)
    {
        if(t[ff].lc==t[x].fa)
            t[ff].lc=x;
        if(t[ff].rc==t[x].fa)
            t[ff].rc=x;
    }
    if(t[x].rc!=0)
        t[t[x].rc].fa=t[x].fa;
    t[t[x].fa].lc=t[x].rc;
    t[x].rc=t[x].fa;
    t[x].fa=ff;
    return;
}

void strike_off(ll x)//删除指定下标
{
    while(t[x].lc!=0||t[x].rc!=0)
    {
        if(t[x].lc!=0&&t[x].rc==0)
        {zag(t[x].lc);continue;}
        
        if(t[x].rc!=0&&t[x].lc==0)
        {zig(t[x].rc);continue;}
        
        if(t[x].lc!=0&&t[x].rc!=0)
        {
            if(t[t[x].lc].sum<t[t[x].rc].sum)
                zag(t[x].lc);
            else
                zig(t[x].rc);
        }
    }
    update(x);
    if(t[x].fa!=0)
    {
        if(t[t[x].fa].lc==x)
            t[t[x].fa].lc=0;
        if(t[t[x].fa].rc==x)
            t[t[x].fa].rc=0;
    }
    --size;
    if(size==0)
        root=0;
    return;
}

void delete_(ll s)//删除指定数字
{
    ll x=find(s);
    if(x==0)
        return;
    if(t[x].js>1)
    {
        --t[x].js;
        --size;
        update(x);
        return;
    }
    else
        strike_off(x);
    return;
}

void insert(ll s)//插入
{
    ++size;
    t[++w].s=s;
    t[w].sum=rand()%1000050;
    t[w].size=1;
    t[w].js=1;
    if(size==1)
    {
        root=w;
        return;
    }
    ll pre=root;
    ll now=root;
    while(now!=0)
    {
        if(s==t[now].s)
        {
            ++t[now].size;
            ++t[now].js;
            --w;
            return;
        }
        if(s<t[now].s)
        {
            ++t[now].size;
            pre=now;
            now=t[now].lc;
            continue;
        }
        if(s>t[now].s)
        {
            ++t[now].size;
            pre=now;
            now=t[now].rc;
            continue;
        }
    }
    if(s<t[pre].s)
        t[pre].lc=w;
    if(s>t[pre].s)
        t[pre].rc=w;
    t[w].fa=pre;
    
    while(t[w].sum<t[t[w].fa].sum)
    {
        if(t[t[w].fa].lc==w)
        {
            zag(w);
            continue;
        }
        if(t[t[w].fa].rc==w)
        {
            zig(w);
            continue;
        }
    }
    return;
}

int main()
{
    srand(19981017);
    return 0;
}


Jetbrains全家桶1年46,售后保障稳定


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/213479.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号