非旋Treap

Posted akakw1

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了非旋Treap相关的知识,希望对你有一定的参考价值。

非旋Treap

->模板地址

(为便于理解,以下演示代码均使用数组版)

概述

非旋Treap是一种按照堆的原理以随机权值来维护平衡树平衡的平衡树,在一般情况下,非旋Treap趋于平衡,但不稳定。非旋Treap的基本操作少,实现简单,作用范围广,基本可以实现Splay所支持的操作

(节点压缩的Treap请跳转,此篇博客为基本操作和使用)

(此博客不包含可持久化Treap,可持久化请跳转)

平衡树的定义

由四个部分组成:左右儿子、节点大小、随机权值、节点数值,其中节点数值为任意类型

struct node {
    int son[2];//左右儿子
    int s,v;//节点大小和随机权值
    int c;//节点数值
    node() {
        son[0] = son[1] = 0;
        s = 0;
        v = rand();//给节点附随机权值
    }
}t[100001];
void push_up(int p) { //更新节点信息
    t[p].s = t[p * 2].s + t[p * 2 + 1].s + 1;
}

基本操作

注意访问到空节点是要立即返回,否则可能回使空节点的size不为0导致各种错误

分裂(split)

将一棵Treap按照一定的要求分裂成两课平衡树,常用的有数值分裂排名分裂

分裂操作因为每次只需要递归一个子树,所以复杂度平均是树高(log(n))的

数值分裂

把数值小于等于给定数值的节点置于一棵平衡树中,其它的置于另一棵平衡树中

为方便表述,我们将放置数值小于等于给定数值的节点的平衡树称为左侧平衡树,另一棵称为右侧平衡树

从给定的根节点开始,若当前节点的数值大于给定数值,将其及其右子树置于右侧平衡树,继续分裂左子树,反之将其及其左子树置于左侧平衡树继续分裂右子树

//p为当前节点,v为给定数值,x为分裂后的左侧平衡树的根节点,y为分裂后的右侧平衡树的根节点
void split_v(int p, int v, int &x, int &y) {
    if(!p)return void(x = y = 0);//分裂到空节点
    if(t[p].c <= v) {
        x = p;//置于左侧平衡树
        split_v(t[p].son[1], v, t[p].son[1], y);//分裂右子树
    }
    else {
        y = p;//置于右侧平衡树
        split_v(t[p].son[0], v, x, t[p].son[0]);//分裂右子树
    }
    push_up(p);
}
//示例
/*
//root {1,2,3,4,5,6}
split_v(root, 3, r1, r2)
//r1 {1,2,3}
//r2 {4,5,6}
*/

注:引用的使用(不能理解引用的看这里)

x和y均使用引用,这样能使x、y在函数中被赋值,能简化大量代码(不用记录父节点)

对于引用的x和y会在函数中被赋值,附的值分别为子树的根的编号,而一个节点的子节点是子树的根节点,所以可以这样使用(实在不能理解多就打几遍或手推一下)

排名分裂

把前k个节点分在一起,原理同数值分裂(代码复制一下再修改一点即可)

void split_k(int p, int k, int &x, int &y) {
    if(!p)return void(x = y = 0);
    if(k <= t[t[p].son[0]].s) {
        y = p;
        split_k(t[p].son[0], k, x, t[p].son[0]);
    }
    else {
        x = p;
        split_k(t[p].son[1], k - t[t[p].son[0]].s - 1, t[p].son[1], y);//注意这里
    }
    push_up(p);
}
//示例
/*
//root {1,3,5,7,9,11}
split_k(root, 3, r1, r2)
//r1 {1,3,5}
//r2 {7,9,11}
*/

合并(merge)

维护平衡树平衡的操作就在这里,原理就是把权值大(小)的作为权值小(大)的节点的父节点,这样就能满足堆的性质

这里的合并不是指的将两个毫不相关的平衡树合并在一起,只是将一棵平衡树接在另一棵后面(如一棵中序遍历为[1,3,5]的Treap和一棵中序遍历为[2,4,6]的平衡树合并后中序遍历为[1,3,5,2,4,5]而不是[1,2,3,4,5,6])

合并操作因为每次最多需要递归一个子树,所以复杂度平均是树高(log(n))的

//将x与y合并,返回合并后的根节点
int merge(int x, int y) {
    if(! x || ! y)return x + y;//一个节点为0则返回不为0的节点
    if(t[x].val < t[y].val) {
        //将x作为父节点,合并x的右子树和y
        t[x].son[1] = merge(t[x].son[1], y);
        push_up(x);
        return x;
    }
    else {
        //将y作为父节点,合并y的左子树和x
        t[y].son[0] = merge(x, t[y].son[0]);
        push_up(y);
        return y;
    }
}
//示例
/*
//r2 {1,2,3}
//r1 {7,8,9}
root=merge(r1,r2)
//root {7,8,9,1,2,3}
*/

常用操作

所有的常用操作都是由基本操作来的,所以上面的模板可以直接用

对于每个操作,我们可以把Treap想象成一个序列,这样能减少思维复杂度

新建一个节点

int tot=0;
int new_node(int v) {
    t[++tot].c = v;
    t[tot].s=1;
    return tot;
}

插入

将需要插入的地方分裂,然后直接合并

//插入一个新节点
void insert(int v, int &r) {
    int r1;
    r.split_v(r, v, r, r1);
    r = merge(r, merge(new_node(v), r1));
}

删除

将要删除的节点单独分裂出来在合并其它节点即可

void remove(int v, int &r) {
    int r1, r2;
    split_v(r, v - 1, r, r1);
    split_k(r1, 1, r2, r1);
    r = merge(r, r1);
}
//r2即为被删除的节点

查询第k个节点(k小值)

把前k-1个分裂出来,再把右侧平衡树的第1个节点分裂出来

int kth(int k, int r) {
    int r1, r2, ans;
    split_k(r, k - 1, r, r1);
    split_k(r1, 1, r1, r2);
    ans = t[r2].c;
    r = merge(r, merge(r1, r2));
    return ans;
}

前驱、后继

这个很简单,也有很多方法,原理和上面的差不多(跳过)

排名

这个最简单,分裂后直接输出size就行了

int rank(int v, int r) {
    int r1;
    split_v(r, v - 1, r, r1);
    int ans = t[r].s;
    r = merge(r, r1);
    return ans + 1;
}

区间操作

非旋Treap也支持区间操作,将要操作的区间分离出来即可,注意如果是需要打标记的操作就需要在分裂中对于每个被访问的节点标记下放

int r1, r2;
split_k(root, l - 1, r1, root);
split_k(root, r - 1, root, r2);
/*do something*/
root = merge(r1, merge(root, r2));

典例

首先将板子复制过来然后再考虑怎么做(Treap的正确使用方法)

(典例持续补充中)

普通平衡树

luogu P3369

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

  1. 插入x数
  2. 删除x数(若有多个相同的数,因只删除一个)
  3. 查询x数的排名(排名定义为比当前数小的数的个数+1。若有多个相同的数,因输出最小的排名)
  4. 查询排名为x的数
  5. 求x的前驱(前驱定义为小于x,且最大的数)
  6. 求x的后继(后继定义为大于x,且最小的数)
#include<bits/stdc++.h>
using namespace std;
inline int gi() {
    register int x=0,op=1,c;
    while(c=getchar(),c<'0'||c>'9')if(c=='-')op=-op;
    x=c^48;
    while(c=getchar(),c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48);
    return x*op;
}
struct node {
    int son[2];
    int val, s;
    int v;
    node() {
        son[0] = son[1] = 0;
        s = 0;
        val = rand();
    }
}t[100001];
void push_up(int p) {
    t[p].s = t[t[p].son[0]].s + t[t[p].son[1]].s + 1;
}
void split_v(int p, int v, int &x, int &y) {
    if(!p)return void(x = y = 0);
    if(t[p].v>v) {
        y = p;
        split_v(t[p].son[0], v, x, t[p].son[0]);
    }
    else {
        x = p;
        split_v(t[p].son[1], v, t[p].son[1], y);
    }
    push_up(p);
}
void split_k(int p, int k, int &x, int &y) {
    if(!p)return void(x = y = 0);
    if(k <= t[t[p].son[0]].s) {
        y = p;
        split_k(t[p].son[0], k, x, t[p].son[0]);
    }
    else {
        x = p;
        split_k(t[p].son[1], k - t[t[p].son[0]].s - 1, t[p].son[1], y);
    }
    push_up(p);
}
int merge(int x, int y) {
    if(! x || ! y)return x + y;
    if(t[x].val < t[y].val) {
        t[x].son[1] = merge(t[x].son[1], y);
        push_up(x);
        return x;
    }
    else {
        t[y].son[0] = merge(x, t[y].son[0]);
        push_up(y);
        return y;
    }
}
int tot=0;
int new_node(int v) {
    t[++tot].v = v;
    t[tot].s=1;
    return tot;
}
int root=0;
int main() {
    srand(time(0));
    int n=gi();
    int op,x;
    int r1,r2;
    while(n--) {
        op=gi(),x=gi();
        switch(op) {
        case 1://插入
            split_v(root,x,root,r1);
            root=merge(root,merge(new_node(x),r1));
            break;
        case 2://删除
            split_v(root,x-1,root,r1);
            split_k(r1,1,r2,r1);
            root=merge(root,r1);
            break;
        case 3://排名
            split_v(root,x-1,root,r1);
            printf("%d
",t[root].s+1);
            root=merge(root,r1);
            break;
        case 4://k小值
            split_k(root,x-1,root,r1);
            split_k(r1,1,r1,r2);
            printf("%d
",t[r1].v);
            root=merge(root,merge(r1,r2));
            break;
        case 5://前驱
            split_v(root,x-1,root,r1);
            split_k(root,t[root].s-1,root,r2);
            printf("%d
",t[r2].v);
            root=merge(root,merge(r2,r1));
            break;
        case 6://后继
            split_v(root,x,root,r1);
            split_k(r1,1,r1,r2);
            printf("%d
",t[r1].v);
            root=merge(root,merge(r1,r2));
        }
    }
    return 0;
}

以上是关于非旋Treap的主要内容,如果未能解决你的问题,请参考以下文章

沉迷数据结构1(treap&非旋treap)

非旋Treap总结 : 快过Splay 好用过传统Treap

非旋Treap

非旋treap

非旋 treap 结构体数组版(无指针)详解,有图有真相

非旋 treap 结构体数组版(无指针)详解,有图有真相