treap随想

(希望各位大佬不要怒对我,我只是想跟大家讨论讨论。)

刚才YY了出了一种数据结构,也不知道叫什么名字(我的意思是,估计以前已经有很多人YY出来过并且已经命了名了),也不知道时间复杂度是否正确(毕竟这是YY出来的),如果各位大神发现了本文证明中的问题,欢迎在下方评论区评论。

定义

这种数据结构是一种二叉搜索树,或者说是一种平衡树。

lch(x) l c h ( x ) 表示 x x 结点的左子,rch(x)表示 x x 结点的右子。如果一个结点没有左子,则lch(x)=null。同理,如果一个结点没有右子,则 rch(x)=null r c h ( x ) = n u l l 。另外,规定 lch(null)=rch(null)=null l c h ( n u l l ) = r c h ( n u l l ) = n u l l

len(x) l e n ( x ) 表示以 x x 为根的子树中深度最大的叶子结点的深度,定义式为len(x)=1+max{len(lch(x),len(rch(x)))}。规定 len(null)=0 l e n ( n u l l ) = 0

旋转同treap/splay。左旋表示把原来的根节点变为新根节点的左子,右旋表示把原来的根节点变为新根节点的右子(对不起,我插入不了图片)。

操作

操作很简单,就是每当 |len(lch(x))len(rch(x))|>1 | l e n ( l c h ( x ) ) − l e n ( r c h ( x ) ) | > 1 时,把 x x 旋入len较小的子树。

一个随性的代码

#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <algorithm>
using namespace std;

const int maxn = 2*1000000 + 6;
namespace BST {
    int ch[maxn][2], val[maxn], siz[maxn], len[maxn], ncnt;
    void maintain(int rt) {
        siz[rt] = 1 + siz[ch[rt][0]] + siz[ch[rt][1]];
        len[rt] = 1 + max(len[ch[rt][0]], len[ch[rt][1]]);
    }
    int shortson(int rt) { /// 返回较 "短"的儿子 
        return len[ch[rt][0]]<len[ch[rt][1]] ? 0: 1;
    }
    void rotate(int& x, int d) {
        int k = ch[x][d^1]; ch[x][d^1] = ch[k][d];
        ch[k][d] = x; maintain(x); x = k; maintain(x);
    }
    int dir(int t, int v) {
        return v<val[t] ? 0:1;
    }
    void insert(int& t, int v) {
        if(t == 0) {
            t = ++ ncnt; ch[t][0] = ch[t][1] = 0;
            val[t] = v; siz[t] = 1; len[t] = 1; return;
        }
        int d = dir(t, v); insert(ch[t][d], v); maintain(t);
        if(abs(len[ch[t][0]] - len[ch[t][1]]) > 1)
            rotate(t, shortson(t)); /// 维护最长链 
    }
    int rnk(int t, int v) {
        if(t == 0) return 1;
        int lsiz = 1 + siz[ch[t][0]];
        int d = dir(t, v);
        if(d == 0) return rnk(ch[t][0], v);
        else       return rnk(ch[t][1], v) + lsiz;
    }
    #define inf (0x7f7f7f7f)
    int kth(int t, int k) {
        if(t == 0) return k<=0? -inf:inf;
        int lsiz = 1 + siz[ch[t][0]];
        if(k <= lsiz) return kth(ch[t][0], k);
        else          return kth(ch[t][1], k - lsiz);
    }
    void del(int& t, int x) {
        if(t == 0) return;
        if(val[t] != x) {
            int d = dir(t, x); del(ch[t][d], x);
            maintain(t);
            if(abs(len[ch[t][0]] - len[ch[t][1]]) > 1)
                rotate(t, shortson(t)); /// 维护最长链 
        }else {
            if(ch[t][0]==0 || ch[t][1]==0) {
                t = ch[t][0] + ch[t][1]; /// !不能 maintain(0) 
            }else {
                rotate(t, 0); del(ch[t][0], x); /// 在左子中删除 
                maintain(t);
                if(abs(len[ch[t][0]] - len[ch[t][1]]) > 1)
                    rotate(t, shortson(t)); /// 维护最长链 
            }
        }
    }
    void debug() {
        for(int i = 1; i <= ncnt; i ++) {
            printf("%3d: %3d, %3d, %3d\n", i, ch[i][0], ch[i][1], val[i]);
        }
    }
}

这还有一份:代码 https://paste.ubuntu.com/p/PHFnVZ4Rq4/

证明

我们假定这棵树的任意一个结点x,在插入操作结束时都能保证 |len(lch(x))len(rch(x))|1 | l e n ( l c h ( x ) ) − l e n ( r c h ( x ) ) | ≤ 1 。(如果您感兴趣可以试着去证明一发,不过我把这个当成结论。如果有人证明出这是错的,请在评论区评论。)

定义 f(H) f ( H ) 表示数高为H的树拥有的最少的结点数,那么有 f(H)=1+f(H1)+f(H2) f ( H ) = 1 + f ( H − 1 ) + f ( H − 2 ) 。理由是这棵树有根节点,除去根节点之后的两棵子树的高度为H-1。又因为两棵树可以有1的高度差,所以可以有一棵子树的高度为H-2。

根据计算可以得出:

f[  2]=         3
f[  3]=         5
f[  4]=         9
f[  5]=        15
f[  6]=        25
f[  7]=        41
f[  8]=        67
f[  9]=       109
f[ 10]=       177
f[ 11]=       287
f[ 12]=       465
f[ 13]=       753
f[ 14]=      1219
f[ 15]=      1973
f[ 16]=      3193
f[ 17]=      5167
f[ 18]=      8361
f[ 19]=     13529
f[ 20]=     21891
f[ 21]=     35421
f[ 22]=     57313
f[ 23]=     92735
f[ 24]=    150049
f[ 25]=    242785
f[ 26]=    392835
f[ 27]=    635621
f[ 28]=   1028457
f[ 29]=   1664079
f[ 30]=   2692537

f[ 28]= 1028457 所以,一棵1000000的树的深度不会超过28(随机数据实测深度为25)。

求了一下通项公式,不知道对不对,求各位大佬指导。

fn=(1+15)(1+52)n+(115)(152)n1 f n = ( 1 + 1 5 ) ⋅ ( 1 + 5 2 ) n + ( 1 − 1 5 ) ⋅ ( 1 − 5 2 ) n − 1

我的校验程序:

#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
#define long long long

const int maxn = 100 + 3;
long f[maxn], g[maxn];

double d(double i) {
    double sqrt5 = sqrt(5.0);
    double x0 = (1.0 + sqrt5)/2.0;
    double x1 = (1.0 - sqrt5)/2.0;
    double A = (sqrt5 - 1.0)/2.0;
    double B = (-1.0 - sqrt5)/2.0;
    double ans = A*pow(x0, i) + B*pow(x1, i);
    return ans;
}

double Sd(int n) {
    double sqrt5 = sqrt(5.0);
    double x0 = (1.0 + sqrt5)/2.0;
    double x1 = (1.0 - sqrt5)/2.0;
    double A = (sqrt5 - 1.0)/2.0;
    double B = (-1.0 - sqrt5)/2.0;
    double ans = A*x0*(1.0-pow(x0,n))/(1.0-x0) + B*x1*(1.0-pow(x1,n))/(1.0-x1);
    return ans;
}

double Ans(int n) {
    double sqrt5 = sqrt(5);
    double x0 = (1.0 + sqrt5)/2.0;
    double x1 = (1.0 - sqrt5)/2.0;
    double A = 1.0 + 1.0/sqrt5;
    double B = 1.0 - 1.0/sqrt5;
    double ans = A*pow(x0, n) + B*pow(x1, n) -1;
    return ans;
}

int main() {
    for(int i = -1; i <= 10; i ++) {
        printf("%3d : %lf\n", i, Sd(i));
    }
    f[0] = 1; f[1] = 1; g[0] = 0; g[1] = 1;
    for(int i = 2; i <= 30; i ++) {
        f[i] = 1 + f[i-1] + f[i-2];
        g[i] = g[i-1] + g[i-2];
        printf("f[%3d] %10lld %20lf\n", i, f[i], Ans(i));
    }
    return 0;
}

运行结果:

 -1 : 1.000000
  0 : 0.000000
  1 : 2.000000
  2 : 3.000000
  3 : 6.000000
  4 : 10.000000
  5 : 17.000000
  6 : 28.000000
  7 : 46.000000
  8 : 75.000000
  9 : 122.000000
 10 : 198.000000
f[  2]          3             3.000000
f[  3]          5             5.000000
f[  4]          9             9.000000
f[  5]         15            15.000000
f[  6]         25            25.000000
f[  7]         41            41.000000
f[  8]         67            67.000000
f[  9]        109           109.000000
f[ 10]        177           177.000000
f[ 11]        287           287.000000
f[ 12]        465           465.000000
f[ 13]        753           753.000000
f[ 14]       1219          1219.000000
f[ 15]       1973          1973.000000
f[ 16]       3193          3193.000000
f[ 17]       5167          5167.000000
f[ 18]       8361          8361.000000
f[ 19]      13529         13529.000000
f[ 20]      21891         21891.000000
f[ 21]      35421         35421.000000
f[ 22]      57313         57313.000000
f[ 23]      92735         92735.000000
f[ 24]     150049        150049.000000
f[ 25]     242785        242785.000000
f[ 26]     392835        392835.000000
f[ 27]     635621        635621.000000
f[ 28]    1028457       1028457.000000
f[ 29]    1664079       1664079.000000
f[ 30]    2692537       2692537.000000
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值