• 平衡树算法


    一、平衡树用来干什么

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

    1. 插入 xxx 数
    2. 删除 xxx 数(若有多个相同的数,因只删除一个)
    3. 查询 xxx 数的排名(排名定义为比当前数小的数的个数 +1+1+1 )
    4. 查询排名为 xxx 的数
    5. xxx 的前驱(前驱定义为小于 xxx,且最大的数)
    6. xxx 的后继(后继定义为大于 xxx,且最小的数)

    二、平衡树与二叉排序树区别

    平衡树是二叉搜索树和堆合并构成的数据结构,它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡树的平均查找长度要小于等于二叉排序树的平均查找长度

    平衡树是二叉排序树通过旋转来达到最优二叉排序树

    平衡树本身就是二叉排序树

    不懂二叉排序树的可以看一下:二叉排序树的构造 && 二叉树的先序、中序、后序遍历

    三、二叉平衡树复杂度

    前提:包含n个顶点的二叉平衡树(AVL树)

    1、查找一个节点时间复杂度为O(lgn)

    2、插入的时间复杂度O(lgn)

    3、删除一个节点时间复杂度为O(lgn)

    代码中会发现每一次操作之后都会进行旋转操作,这样导致平衡树的时间复杂度常数很大

    四、平衡树的构造

    参考链接:https://blog.csdn.net/a_comme_amour/article/details/79382104

    1、变量声明

    f[i]表示i的父结点,ch[i][0]表示i的左儿子,ch[i][1]表示i的右儿子,key[i]表示i的关键字(即结点i代表的那个数字),cnt[i]表示i结点的关键字出现的次数(相当于权值),sizes[i]表示包括i的这个子树的大小;sz为整棵树的大小,rt为整棵树的根。

    注意:

    sz代表的是不同节点种类数,比如你要插入5 5 4 3 4这些数,那么sz的大小是3

    sizes代表的就是节点有几个,就不是种类个数了

    几个小函数:

    void clears(int x) //删除x点信息
    {
        f[x]=cnt[x]=ch[x][0]=ch[x][1]=sizes[x]=key[x]=0;
    }
    bool get(int x) //判断x是父节点的左孩子还是右孩子
    {
        return ch[f[x]][1]==x;  //返回1就是右孩子,返回0就是左孩子
    }
    void pushup(int x)  //重新计算一下x这棵子树的节点数量
    {
        if(x)
        {
            sizes[x]=cnt[x];
            if(ch[x][0]) sizes[x]+=sizes[ch[x][0]];
            if(ch[x][1]) sizes[x]+=sizes[ch[x][1]];
        }
    }

    2、旋转操作

     怎么旋转看下面

     让4旋转到他的父亲位置2(我们代码中的旋转就是让一个节点移动到他的父亲节点位置)

    首先我们要知道平衡树就是二叉排序树,那么二叉排序树儿子节点的特点就是左节点的值小于父节点的值,右节点的值大于父节点的值

    那么4节点移动到了2节点(4节点的父亲节点)的位置,4节点会把它的右儿子(也就是9号节点)给2号节点充当左儿子,然后2号节点在作为4号节点的右儿子节点出现

     旋转操作代码:

     1 void rotates(int x)  //将x移动到他父亲的位置,并且保证树依旧平衡
     2 {
     3     int fx=f[x],ffx=f[fx],which=get(x);
     4     //x点父亲,要接受x的儿子。而且x与x父亲身份交换
     5     ch[fx][which]=ch[x][which^1];
     6     f[ch[fx][which]]=fx;
     7 
     8     ch[x][which^1]=fx;
     9     f[fx]=x;
    10 
    11     f[x]=ffx;
    12     if(ffx) ch[ffx][ch[ffx][1]==fx]=x;
    13 
    14     pushup(fx);
    15     pushup(x);
    16 }

    3、splay操作

    这个就是把树上的一个节点旋转的树根位置,分为两种操作:

    <一>、

    一个点p和他的父亲(fa)同为一边的儿子(如图同为左儿子) 此时应先rotate(p.fa), 再rotate(p)

    <二>、

    一个点p和他的父亲不是同一边的儿子 此时两次rotate(p)即可

     1 void splay(int x)  //将x移动到数根节点的位置,并且保证树依旧平衡
     2 {
     3     for(int fx; fx=f[x]; rotates(x))
     4     {
     5         if(f[fx])
     6         {
     7             rotates((get(x)==get(fx))?fx:x);
     8             //如果祖父三代连城一条线,就要从祖父哪里rotate
     9             //至于为什么要这样做才能最快……可以去看看Dr.Tarjan的论文
    10         }
    11     }
    12     rt=x;
    13 }

    注意:void splay(int x)  这个x是这个节点在树上的位置

    假设上面节点1、fa、3的cnt值都为1 

    那么这个fa这个节点在树上的位置就是2,3这个节点在树上的位置就是3

    4、void inserts(int x),插入一个节点(这个x是你要插入值的大小)

    代码实现

     1 void inserts(int x)
     2 {
     3     if(rt==0)
     4     {
     5         sz++;
     6         key[sz]=x;
     7         rt=sz;
     8         cnt[sz]=sizes[sz]=1;
     9         f[sz]=ch[sz][0]=ch[sz][1]=0;
    10         return;
    11     }
    12     int now=rt,fx=0;
    13     while(1)
    14     {
    15         if(x==key[now])
    16         {
    17             cnt[now]++;
    18             pushup(now);
    19             pushup(fx);
    20             splay(now);  //splay的过程会rotates now点的所有祖先节点,这个时候它们所有子树权值也更新了
    21             return;
    22         }
    23         fx=now;
    24         now=ch[now][key[now]<x];
    25         if(now==0)
    26         {
    27             sz++;
    28             sizes[sz]=cnt[sz]=1;
    29             ch[sz][0]=ch[sz][1]=0;
    30             ch[fx][x>key[fx]]=sz;  //二叉查找树特性”左大右小“
    31             f[sz]=fx;
    32             key[sz]=x;
    33             pushup(fx);
    34             splay(sz);
    35             return ;
    36         }
    37     }
    38 }

    注意:插入完之后要把这个刚插入到树上的点给通过splay函数旋转到树根,至于为什么要这样做。大家可以这样想,假设插入之前的树是一颗最优二叉排序树,那么插入一个点之后可能就不最优了,所以我们要旋转这棵树,以保证这棵树还是最优的

    5、int rnk(int x)  //查询x的排名

    这个函数就是插入x这个值,他在平衡树上的位置

    代码实现

     1 /*
     2 有人问:
     3 很想知道为什么rnk操作也要splay操作呢?如果del要用的话直接splay(x)是不是就可以了
     4 
     5 原博客答:
     6 呃不不不这个貌似不是随便splay以下就可以的 首先find之后的splay就是将找到的这个点转到根,
     7 当然你不加这个应该是也可以,只不过这道题加上的话对于这一堆操作来说比较方便,不过一般来说转一转splay的
     8 平衡性会好一点(当然也不要转得太多了就tle了...) 但是del之前直接splay(x)要视情况而定,关键在于分清楚
     9 “点的编号”和“点的权值”这两个概念。如果你已经知道了该转的点的编号,当然可以直接splay(x),但是如果你只
    10 知道应该splay的点的权值,你需要在树里find到这个权值的点的编号,然后再splay 其实最后splay写起来都是非
    11 常灵活的,而且有可能一个点带若干个权之类的。对于初学者的建议就是先把一些最简单的情况搞清楚,比如说一
    12 个编号一个权的这种,然后慢慢地多做题就能运用得非常熟练了。最好的方法就是多画画树自己转一转,对之后
    13 复杂题目的调试也非常有益
    14 
    15 我说:
    16 我在洛谷上得模板题上交了一下rnk里面不带splay(now)的,一共12个样例,就对了两个样例。错了一个样例,其他全TLE
    17 
    18 我解释:
    19 为什么作者解释可以删去,但是删过之后还错了。因为它的代码中函数之前是相互联系的
    20 就比如它调用rnk(x)函数之后就已经认为x为平衡树树根,然后直接对它进行下一步操作(这个假设在del函数里面)
    21 
    22 如果你光删了rnk(x)里面的splay(),你肯定还要改其他地方代码。。。。。。
    23 */
    24 int rnk(int x)  //查询x的排名
    25 {
    26     int now=rt,ans=0;
    27     while(1)
    28     {
    29         if(x<key[now]) now=ch[now][0];
    30         else
    31         {
    32             ans+=sizes[ch[now][0]];
    33             if(x==key[now])
    34             {
    35                 splay(now); //这个splay是为了后面函数的调用提供前提条件
    36 //就比如pre函数的前提条件就是x(x是我们要求谁的前驱,那个谁就是x)已经在平衡树树根
    37                 return ans+1;
    38             }
    39             ans+=cnt[now];  //cnt代表now这个位置值(key[now])出现了几次
    40             now=ch[now][1];
    41         }
    42     }
    43 }

    6、int kth(int x)

    这个是查找树上面第x大的数是多少

     1 int kth(int x)
     2 {
     3     int now=rt;
     4     while(1)
     5     {
     6         if(ch[now][0] && x<=sizes[ch[now][0]])
     7         {
     8             //满足这个条件就说明它在左子树上
     9             now=ch[now][0];
    10         }
    11         else
    12         {
    13             int temp=sizes[ch[now][0]]+cnt[now];
    14             if(x<=temp) //这个temp是now左子树权值和now节点权值之和
    15                 return key[now]; //进到这个判断里面说明他不在左子树又不在右子树,那就是now节点了
    16             x-=temp;
    17             now=ch[now][1];
    18         }
    19     }
    20 }

    7、求根节点的前驱节点和后继结点在树上的位置

    int pre()//由于进行splay后,x已经到了根节点的位置
    {
        //求x的前驱其实就是求x的左子树的最右边的一个结点
    //为什么呢,因为这是平衡树(带有二叉排序树特点),根据二叉排序树中序遍历结果我么可以知道,一个数的前序就在
    //x的左子树的最右边的一个结点
        int now=ch[rt][0];
        while(ch[now][1]) now=ch[now][1];
        return now;
    }
    int next()
    {
        //求后继是求x的右子树的最左边一个结点
    //为什么呢,因为这是平衡树(带有二叉排序树特点),根据二叉排序树中序遍历结果我么可以知道,一个数的前序就在
    //x的右子树的最左边一个结点
        int now=ch[rt][1];
        while(ch[now][0])  now=ch[now][0];
        return now;
    }

    8、删除一个值

     1 /*
     2 删除操作是最后一个稍微有点麻烦的操作。
     3 step 1:随便find一下x。目的是:将x旋转到根。
     4 step 2:那么现在x就是根了。如果cnt[root]>1,即不只有一个x的话,直接-1返回。
     5 step 3:如果root并没有孩子,就说名树上只有一个x而已,直接clear返回。
     6 step 4:如果root只有左儿子或者右儿子,那么直接clear root,然后把唯一的儿子当作根就可以了(f赋0,root赋为唯一的儿子)
     7 剩下的就是它有两个儿子的情况。
     8 step 5:我们找到新根,也就是x的前驱(x左子树最大的一个点),将它旋转到根。然后将原来x的右子树接到新根的
     9 右子树上(注意这个操作需要改变父子关系)。这实际上就把x删除了。不要忘了update新根。
    10 */
    11 void del(int x)
    12 {
    13     rnk(x);
    14     if(cnt[rt]>1)//如果这个位置权值大于1,那就不用删除这个点
    15     {
    16         cnt[rt]--;
    17         pushup(rt);
    18         return;
    19     }
    20     if(!ch[rt][0] && !ch[rt][1]) //这个就代表平衡树只有一个节点
    21     {
    22         clears(rt);
    23         rt=0;
    24         return;
    25     }
    26     if(!ch[rt][0]) //只有左儿子,树根只有左儿子那就把树根直接删了就行
    27     { //然后左儿子这棵子树变成新的平衡树
    28         int frt=rt;
    29         rt=ch[rt][1];
    30         f[rt]=0;
    31         clears(frt);
    32         return;
    33     }
    34     else if(!ch[rt][1]) //只有右儿子,和上面差不多
    35     {
    36         int frt=rt;
    37         rt=ch[rt][0];
    38         f[rt]=0;
    39         clears(frt);
    40         return;
    41     }
    42     int frt=rt;
    43     int leftbig=pre();
    44     splay(leftbig); //让前驱做新根
    45     ch[rt][1]=ch[frt][1];  //这个frt指向的还是之前的根节点
    46     /*
    47     看着一点的时候就会发现,数在数组里面的位置一直没有改变,平衡树旋转改变的是根节点儿子数组ch[x][]指向的值
    48     */
    49     f[ch[frt][1]]=rt;
    50     clears(frt);
    51     pushup(rt);
    52 }

    五、例题

    P3369 【模板】普通平衡树

    题目描述

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

    1. 插入 xxx 数
    2. 删除 xxx 数(若有多个相同的数,因只删除一个)
    3. 查询 xxx 数的排名(排名定义为比当前数小的数的个数 +1+1+1 )
    4. 查询排名为 xxx 的数
    5. xxx 的前驱(前驱定义为小于 xxx,且最大的数)
    6. xxx 的后继(后继定义为大于 xxx,且最小的数)

    输入格式

    第一行为 nnn,表示操作的个数,下面 nnn 行每行有两个数 opt ext{opt}opt 和 xxx,opt ext{opt}opt 表示操作的序号( 1≤opt≤6 1 leq ext{opt} leq 6 1opt6 )

    输出格式

    对于操作 3,4,5,63,4,5,63,4,5,6 每行输出一个数,表示对应答案

    输入输出样例

    输入 #1
    10
    1 106465
    4 1
    1 317721
    1 460929
    1 644985
    1 84185
    1 89851
    6 81968
    1 492737
    5 493598
    输出 #1
    106465
    84185
    492737

    说明/提示

    【数据范围】
    对于 100%100\%100% 的数据,1≤n≤1051le n le 10^51n105,∣x∣≤107|x| le 10^7x107

     
    代码:
      1 /*
      2 注意:
      3 1、看平衡树之前你要注意,对于1 3 5 3 2这一组数据。sz的值是4,因为sz保存的是节点种类
      4    为什么要这样,因为sz涉及到要为几个点开空间
      5 
      6 2、sizes[x]保存的是以x为树根的子树上节点数量,比如x这颗子树所有节点为1,2,1.那么它的sizes[x]=3
      7    而且实际上不会有两个1放在树上。而是给1的权值数组cnt[1]加1
      8 
      9 3、对于1 3 5 3 2这一组数据。sz的值是4。那么1对应位置就是1,3对应位置就是2,5对应位置就是3,2对应位置就是4
     10    之后他们所对应的位置都不会改变
     11    在平衡树旋转的过程中只是每一个点的儿子节点ch[x][0]和ch[x][1]里面保存的值在改变
     12 */
     13 #include<stdio.h>
     14 #include<string.h>
     15 #include<algorithm>
     16 #include<iostream>
     17 using namespace std;
     18 const int maxn=1e5+10;
     19 int f[maxn],cnt[maxn],ch[maxn][2],sizes[maxn],key[maxn],sz,rt;
     20 /*
     21 f[i]:i节点的父节点,cnt[i]每个点出现的次数,ch[i][0/1]:0表示左孩子,
     22 1表示右孩子, size[i]表示以i为根节点的子树的节点个数
     23 key[i]表示点i代表的数的值;sz为整棵树的节点种类数,rt表示根节点
     24 */
     25 void clears(int x) //删除x点信息
     26 {
     27     f[x]=cnt[x]=ch[x][0]=ch[x][1]=sizes[x]=key[x]=0;
     28 }
     29 bool get(int x) //判断x是父节点的左孩子还是右孩子
     30 {
     31     return ch[f[x]][1]==x;  //返回1就是右孩子,返回0就是左孩子
     32 }
     33 void pushup(int x)  //重新计算一下x这棵子树的节点数量
     34 {
     35     if(x)
     36     {
     37         sizes[x]=cnt[x];
     38         if(ch[x][0]) sizes[x]+=sizes[ch[x][0]];
     39         if(ch[x][1]) sizes[x]+=sizes[ch[x][1]];
     40     }
     41 }
     42 void rotates(int x)  //将x移动到他父亲的位置,并且保证树依旧平衡
     43 {
     44     int fx=f[x],ffx=f[fx],which=get(x);
     45     //x点父亲,要接受x的儿子。而且x与x父亲身份交换
     46     ch[fx][which]=ch[x][which^1];
     47     f[ch[fx][which]]=fx;
     48 
     49     ch[x][which^1]=fx;
     50     f[fx]=x;
     51 
     52     f[x]=ffx;
     53     if(ffx) ch[ffx][ch[ffx][1]==fx]=x;
     54 
     55     pushup(fx);
     56     pushup(x);
     57 }
     58 void splay(int x)  //将x移动到数根节点的位置,并且保证树依旧平衡
     59 {
     60     for(int fx; fx=f[x]; rotates(x))
     61     {
     62         if(f[fx])
     63         {
     64             rotates((get(x)==get(fx))?fx:x);
     65             //如果祖父三代连城一条线,就要从祖父哪里rotate
     66             //至于为什么要这样做才能最快……可以去看看Dr.Tarjan的论文
     67         }
     68     }
     69     rt=x;
     70 }
     71 /*
     72 将x这个值插入到平衡树上面
     73 如果这个值在树上存在过,那就sz不再加1,更新一下权值即可
     74 如果这个值在树上不存在,那就sz加1,再更新一下权值
     75 
     76 sz是书上节点种类数
     77 sizes[x]是x这棵子树上有多少节点
     78 */
     79 void inserts(int x)
     80 {
     81     if(rt==0)
     82     {
     83         sz++;
     84         key[sz]=x;
     85         rt=sz;
     86         cnt[sz]=sizes[sz]=1;
     87         f[sz]=ch[sz][0]=ch[sz][1]=0;
     88         return;
     89     }
     90     int now=rt,fx=0;
     91     while(1)
     92     {
     93         if(x==key[now])
     94         {
     95             cnt[now]++;
     96             pushup(now);
     97             pushup(fx);
     98             splay(now);  //splay的过程会rotates now点的所有祖先节点,这个时候它们所有子树权值也更新了
     99             return;
    100         }
    101         fx=now;
    102         now=ch[now][key[now]<x];
    103         if(now==0)
    104         {
    105             sz++;
    106             sizes[sz]=cnt[sz]=1;
    107             ch[sz][0]=ch[sz][1]=0;
    108             ch[fx][x>key[fx]]=sz;  //二叉查找树特性”左大右小“
    109             f[sz]=fx;
    110             key[sz]=x;
    111             pushup(fx);
    112             splay(sz);
    113             return ;
    114         }
    115     }
    116 }
    117 /*
    118 有人问:
    119 qwq很想知道为什么find操作也要splay操作呢?如果del要用的话直接splay(x)是不是就可以了
    120 
    121 原博客答:
    122 呃不不不这个貌似不是随便splay以下就可以的 首先find之后的splay就是将找到的这个点转到根,
    123 当然你不加这个应该是也可以,只不过这道题加上的话对于这一堆操作来说比较方便,不过一般来说转一转splay的
    124 平衡性会好一点(当然也不要转得太多了就tle了...) 但是del之前直接splay(x)要视情况而定,关键在于分清楚
    125 “点的编号”和“点的权值”这两个概念。如果你已经知道了该转的点的编号,当然可以直接splay(x),但是如果你只
    126 知道应该splay的点的权值,你需要在树里find到这个权值的点的编号,然后再splay 其实最后splay写起来都是非
    127 常灵活的,而且有可能一个点带若干个权之类的。对于初学者的建议就是先把一些最简单的情况搞清楚,比如说一
    128 个编号一个权的这种,然后慢慢地多做题就能运用得非常熟练了。最好的方法就是多画画树自己转一转,对之后
    129 复杂题目的调试也非常有益
    130 
    131 我说:
    132 我在洛谷上得模板题上交了一下rnk里面不带splay(now)的,一共12个样例,就对了两个样例。错了一个样例,其他全TLE
    133 
    134 我解释:
    135 为什么作者解释可以删去,但是删过之后还错了。因为它的代码中函数之前是相互联系的
    136 就比如它调用rnk(x)函数之后就已经认为x为平衡树树根,然后直接对它进行下一步操作(这个假设在del函数里面)
    137 
    138 如果你光删了rnk(x)里面的splay(),你肯定还要改其他地方代码。。。。。。
    139 */
    140 int rnk(int x)  //查询x的排名
    141 {
    142     int now=rt,ans=0;
    143     while(1)
    144     {
    145         if(x<key[now]) now=ch[now][0];
    146         else
    147         {
    148             ans+=sizes[ch[now][0]];
    149             if(x==key[now])
    150             {
    151                 splay(now); //这个splay是为了后面函数的调用提供前提条件
    152 //就比如pre函数的前提条件就是x(x是我们要求谁的前驱,那个谁就是x)已经在平衡树树根
    153                 return ans+1;
    154             }
    155             ans+=cnt[now];  //cnt代表now这个位置值(key[now])出现了几次
    156             now=ch[now][1];
    157         }
    158     }
    159 }
    160 int kth(int x)
    161 {
    162     int now=rt;
    163     while(1)
    164     {
    165         if(ch[now][0] && x<=sizes[ch[now][0]])
    166         {
    167             //满足这个条件就说明它在左子树上
    168             now=ch[now][0];
    169         }
    170         else
    171         {
    172             int temp=sizes[ch[now][0]]+cnt[now];
    173             if(x<=temp) //这个temp是now左子树权值和now节点权值之和
    174                 return key[now]; //进到这个判断里面说明他不在左子树又不在右子树,那就是now节点了
    175             x-=temp;
    176             now=ch[now][1];
    177         }
    178     }
    179 }
    180 int pre()//由于进行splay后,x已经到了根节点的位置
    181 {
    182     //求x的前驱其实就是求x的左子树的最右边的一个结点
    183 //为什么呢,因为这是平衡树(带有二叉排序树特点),根据二叉排序树中序遍历结果我么可以知道,一个数的前序就在
    184 //x的左子树的最右边的一个结点
    185     int now=ch[rt][0];
    186     while(ch[now][1]) now=ch[now][1];
    187     return now;
    188 }
    189 int next()
    190 {
    191     //求后继是求x的右子树的最左边一个结点
    192 //为什么呢,因为这是平衡树(带有二叉排序树特点),根据二叉排序树中序遍历结果我么可以知道,一个数的前序就在
    193 //x的右子树的最左边一个结点
    194     int now=ch[rt][1];
    195     while(ch[now][0])  now=ch[now][0];
    196     return now;
    197 }
    198 /*
    199 删除操作是最后一个稍微有点麻烦的操作。
    200 step 1:随便find一下x。目的是:将x旋转到根。
    201 step 2:那么现在x就是根了。如果cnt[root]>1,即不只有一个x的话,直接-1返回。
    202 step 3:如果root并没有孩子,就说名树上只有一个x而已,直接clear返回。
    203 step 4:如果root只有左儿子或者右儿子,那么直接clear root,然后把唯一的儿子当作根就可以了(f赋0,root赋为唯一的儿子)
    204 剩下的就是它有两个儿子的情况。
    205 step 5:我们找到新根,也就是x的前驱(x左子树最大的一个点),将它旋转到根。然后将原来x的右子树接到新根的
    206 右子树上(注意这个操作需要改变父子关系)。这实际上就把x删除了。不要忘了update新根。
    207 */
    208 void del(int x)
    209 {
    210     rnk(x);
    211     if(cnt[rt]>1)//如果这个位置权值大于1,那就不用删除这个点
    212     {
    213         cnt[rt]--;
    214         pushup(rt);
    215         return;
    216     }
    217     if(!ch[rt][0] && !ch[rt][1]) //这个就代表平衡树只有一个节点
    218     {
    219         clears(rt);
    220         rt=0;
    221         return;
    222     }
    223     if(!ch[rt][0]) //只有左儿子,树根只有左儿子那就把树根直接删了就行
    224     { //然后左儿子这棵子树变成新的平衡树
    225         int frt=rt;
    226         rt=ch[rt][1];
    227         f[rt]=0;
    228         clears(frt);
    229         return;
    230     }
    231     else if(!ch[rt][1]) //只有右儿子,和上面差不多
    232     {
    233         int frt=rt;
    234         rt=ch[rt][0];
    235         f[rt]=0;
    236         clears(frt);
    237         return;
    238     }
    239     int frt=rt;
    240     int leftbig=pre();
    241     splay(leftbig); //让前驱做新根
    242     ch[rt][1]=ch[frt][1];  //这个frt指向的还是之前的根节点
    243     /*
    244     看着一点的时候就会发现,数在数组里面的位置一直没有改变,平衡树旋转改变的是根节点儿子数组ch[x][]指向的值
    245     */
    246     f[ch[frt][1]]=rt;
    247     clears(frt);
    248     pushup(rt);
    249 }
    250 int main()
    251 {
    252     int n;
    253     scanf("%d",&n);
    254     for (int i=1; i<=n; i++)
    255     {
    256         int type,k;
    257         scanf("%d%d",&type,&k);
    258         if (type==1) inserts(k);
    259         if (type==2) del(k);
    260         if (type==3) printf("%d
    ",rnk(k));
    261         if (type==4) printf("%d
    ",kth(k));
    262         if (type==5)
    263         {
    264             inserts(k);
    265             //插入操作中存在splay操作,这样的话插入之后平衡树树根就是k
    266             printf("%d
    ",key[pre()]);
    267             del(k);
    268         }
    269         if (type==6)
    270         {
    271             inserts(k);
    272             printf("%d
    ",key[next()]);
    273             del(k);
    274         }
    275     }
    276     printf("%d %d %d %d
    ",sz,sizes[1],sizes[2],sizes[3]);
    277     return 0;
    278 }
  • 相关阅读:
    Excel导出失败的提示
    C#中将一个引用赋值null的作用
    POJ2112Optimal Milking(二分法+floyd最短+网络流量)
    三年流水账
    OpenCV(C++接口)学习笔记1-图像读取、显示、保存
    thinkphp3.2 代码生成并点击验证码
    8.19! 今天我有18生日,点击阅读或顶部 尾随幸运的一天!生日知识!↓——【Badboy】
    如何系统地学习JavaScript
    HDU 3065 病毒在继续 (AC自己主动机)
    使用Canvas和Paint自己绘制折线图
  • 原文地址:https://www.cnblogs.com/kongbursi-2292702937/p/12214388.html
Copyright © 2020-2023  润新知