0x46蓝书习题:普通平衡树

Treap/平衡二叉树



蓝书习题:普通平衡树

这道题是一道平衡树模板题,可以用多种解法,这里用最简单的Treap,下面简单说一下各种操作的思路

  • 添加
    当要添加一个值时,先判断所要加入的以p为根节点的子树是否为空,为空添加新的节点:New(val)。 当然平衡树,当加入新节点后,子节点dat变得大于a[p].dat时,要旋转,左旋右旋见代码,好理解。

    void Insert(int &p,int val){
      if(!p){
          p=New(val);
          return;
      }
      if(val==a[p].val){
          a[p].cnt++,Updata(p);
          return;
      }
      if(val<a[p].val){
          Insert(a[p].l,val);
          if(a[p].dat<a[a[p].l].dat) zig(p);
      }
      else{
          Insert(a[p].r,val);
          if(a[p].dat<a[a[p].r].dat) zag(p);
      }
      Updata(p);
    }
  • 删除 :
    平衡树删除要容易,就是将要删除的节点不停旋转到叶子上,再删掉。见代码

    void Remove(int &p,int val){
      if(p==0) return;
      if(val==a[p].val){
          if(a[p].cnt>1){     //val这个值有重复,直接减少cnt即可
              a[p].cnt--,Updata(p);
              return;
          }
          if(a[p].l||a[p].r){     //p有子树,不是叶子
              if(a[p].r==0||a[a[p].l].dat > a[a[p].r].dat) zig(p),Remove(a[p].r,val);     //左子树长于右子树,向右旋
              else zag(p),Remove(a[p].l,val);
              Updata(p);
          }
          else p=0;               //p是叶子直接删除
          return;
      }
      if(val<a[p].val) Remove(a[p].l,val);
      else Remove(a[p].r,val);
      Updata(p);
    
    }
  • 输出一个值的排名:
    根据平衡二叉树的性质,最左边的值最小,那么如果我知道p的左子树siz,那么p的排名就是 a[a[p].l].siz+1,根据这一点。如果val<a[p].val,说明val在左子树中,递归处理左子树。当val>a[p].val,时,递归右子树时返回值要加上p左子树的siz和p的重复次数cnt这是由于递归右子树返回值,表示的是val在右子树中的排名,但是易知,val大于左子树和p的val。

    int GetRankByVal(int p,int val){
      if(p==0) return 0;
      if(val==a[p].val) return a[a[p].l].siz+1;
      else if(val<a[p].val) return GetRankByVal(a[p].l,val);
      return GetRankByVal(a[p].r,val)+a[a[p].l].siz+a[p].cnt;
    }
    
  • 输出某排名的值:
    这个看代码吧。

    int GetvalByRank(int p,int rak){
      if(p==0) return INF;
      if(a[a[p].l].siz>=rak) return GetvalByRank(a[p].l,rak);
      if(a[a[p].l].siz+a[p].cnt>=rak) return a[p].val;
      return GetvalByRank(a[p].r,rak-a[a[p].l].siz-a[p].cnt);
    }
    //2.左子树siz>rak,说明排名为rak的节点在左子树中
    //3.左子树的siz+p点的cnt>rank,而且左子树的siz<rak,说明排名为rak的就是p点的val
    //4.其他就,递归处理右子树,注意要减去左子树siz和p点的重复值
  • 找前驱/后继:
    前驱是比val小的最大值 ,后继是比val大的最小值**

    int GetPre(int val){
      int ans=1;
      int p=root;
      while(p){
          if(a[p].val==val){
              if(a[p].l>0){
                  p=a[p].l;
                  while(a[p].r>0) p=a[p].r;
                  ans=p;
              }
              break;
          }
          if(a[p].val<val&&a[p].val>a[ans].val) ans=p;
          p= val<a[p].val ? a[p].l : a[p].r;
      }
      return a[ans].val;
    }
    int GetNext(int val){
      int ans=2;
      int p=root;
      while(p){
          if(val==a[p].val){
              if(a[p].r){
                  p=a[p].r;
                  while(a[p].l>0)p=a[p].l;
                  ans=p;
              }
              break;
          }
          if(a[p].val>val&&a[p].val<a[ans].val) ans=p;
          p= val<a[p].val ? a[p].l : a[p].r;
      }
      return a[ans].val;
    }

完整代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MA=1e5+5;
const int INF=0x3f3f3f3f;

struct Treap
{
    int l,r;
    int val,dat;    //val:权值,dat:随机值,用来判断左右旋
    int cnt,siz;    //cnt:记录这个权值的重复次数,siz:子树大小
}a[MA];
int tot,n,root;

int New(int val){
    a[++tot].val=val;
    a[tot].siz=a[tot].cnt=1;
    a[tot].dat=rand();
    return tot;
}

void Updata(int p){
    a[p].siz=a[a[p].l].siz+a[a[p].r].siz+a[p].cnt;
}

void Build(){
    New(-INF),New(INF);
    root=1,a[1].r=2;
    Updata(root);
}

void zig(int &p){
    int q=a[p].l;
    a[p].l=a[q].r,a[q].r=p;
    p=q;
    Updata(a[p].r);
    Updata(p);
}

void zag(int &p){
    int q=a[p].r;
    a[p].r=a[q].l,a[q].l=p;
    p=q;
    Updata(a[p].l);
    Updata(p);
}

void Insert(int &p,int val){
    if(!p){
        p=New(val);
        return;
    }
    if(val==a[p].val){
        a[p].cnt++,Updata(p);
        return;
    }
    if(val<a[p].val){
        Insert(a[p].l,val);
        if(a[p].dat<a[a[p].l].dat) zig(p);
    }
    else{
        Insert(a[p].r,val);
        if(a[p].dat<a[a[p].r].dat) zag(p);
    }
    Updata(p);
}

void Remove(int &p,int val){
    if(p==0) return;
    if(val==a[p].val){
        if(a[p].cnt>1){     //val这个值有重复,直接减少cnt即可
            a[p].cnt--,Updata(p);
            return;
        }
        if(a[p].l||a[p].r){     //p有子树,不是叶子
            if(a[p].r==0||a[a[p].l].dat > a[a[p].r].dat) zig(p),Remove(a[p].r,val);     //左子树长于右子树,向右旋
            else zag(p),Remove(a[p].l,val);
            Updata(p);
        }
        else p=0;               //p是叶子直接删除
        return;
    }
    if(val<a[p].val) Remove(a[p].l,val);
    else Remove(a[p].r,val);
    Updata(p);

}

int GetRankByVal(int p,int val){
    if(p==0) return 0;
    if(val==a[p].val) return a[a[p].l].siz+1;
    else if(val<a[p].val) return GetRankByVal(a[p].l,val);
    return GetRankByVal(a[p].r,val)+a[a[p].l].siz+a[p].cnt;
}

int GetvalByRank(int p,int rak){
    if(p==0) return INF;
    if(a[a[p].l].siz>=rak) return GetvalByRank(a[p].l,rak); //左子树siz>rak,说明排名为rak的节点在左子树中
    if(a[a[p].l].siz+a[p].cnt>=rak) return a[p].val;        //左子树的siz+p点的cnt>rank,而且左子树的siz<rak,说明排名为rak的就是p点的val
    return GetvalByRank(a[p].r,rak-a[a[p].l].siz-a[p].cnt); //其他就,递归处理右子树,注意要减去左子树siz和p点的重复值
}

int GetPre(int val){
    int ans=1;
    int p=root;
    while(p){       //子树为处理完
        if(a[p].val==val){      //找到val的节点
            if(a[p].l>0){       //如果p有左子树,那就让p->左子树,然后一直向右子树移动,最后得到的p就是小于val的最大值
                p=a[p].l;
                while(a[p].r>0) p=a[p].r;
                ans=p;
            }
            break;
        }
        if(a[p].val<val&&a[p].val>a[ans].val) ans=p;    //用p的值更新ans,找小于val的最大值
        p= val<a[p].val ? a[p].l : a[p].r;              //递归合适的子树
    }
    return a[ans].val;
}

int GetNext(int val){
    int ans=2;
    int p=root;
    while(p){
        if(val==a[p].val){
            if(a[p].r){
                p=a[p].r;
                while(a[p].l>0)p=a[p].l;
                ans=p;
            }
            break;
        }
        if(a[p].val>val&&a[p].val<a[ans].val) ans=p;
        p= val<a[p].val ? a[p].l : a[p].r;
    }
    return a[ans].val;
}

int main()
{
    Build();
    scanf("%d",&n);
    while(n--){
        int opt,x;
        scanf("%d%d",&opt,&x);
        if(opt==1) Insert(root,x);
        else if(opt==2) Remove(root,x);
        else if(opt==3) printf("%d\n",GetRankByVal(root,x)-1);
        else if(opt==4) printf("%d\n",GetvalByRank(root,x+1));
        else if(opt==5) printf("%d\n",GetPre(x));
        else if(opt==6) printf("%d\n",GetNext(x));
    }
    return 0;
}

原文地址:https://www.cnblogs.com/A-sc/p/12244363.html

时间: 2024-08-07 01:44:51

0x46蓝书习题:普通平衡树的相关文章

分数化小数(decimal) 白书习题 2-5

1 /* 2 分数化小数(decimal) 白书习题 2-5 3 输入正整数 a , b , c , 输出 a/b 的小数形式,精确到小数点后 c 位 .a,b<=10^6 , c <= 100. 4 输入包含多组数据,结束标志为 a = b = c = 0 ; 5 */ 6 #include<stdio.h> 7 int main() 8 { 9 int a,b,c,y; //y用来存储 a/b 的余数 10 while(scanf("%d%d%d",&

蓝书例题之UVa 10253 Series-Parallel Networks

挺有趣的一道题 首先转化模型,思路参考蓝书,可得出等同于求共n个叶子,且每个非叶结点至少有两个子结点的无标号树的个数的二倍,设个数为\(f[n]\) 考虑怎么求\(f[n]\),假设有一个\(n\)的整数划分,分别代表每棵子树中的叶节点个数,然后用可重组合,乘法原理和加法原理把\(f[n]\)递推出来 这个过程可以用\(dp\)来完成,设\(g[i][j]\)表示子树中叶结点数量最大值小于等于\(i\),共有\(j\)个叶结点的树的个数,转移时枚举最大的叶结点数量\(i\)和叶结点数量为\(i\

蓝书《哈希与哈希表》——知识整理

一.前言: 有些数据不经处理是难以利用的.所谓哈希,就是通过哈希函数将这种难以简单利用的数据(比如矩阵.字符串等等)转化为可以用一个变量表示甚至可以作为数组下标的哈希值.有了哈希值,就可以实现时间复杂度近乎为常数的快速查找与匹配,更简单有效地利用一些复杂数据. 二.字符串哈希: 即对象为字符串的哈希.常将字符串看做一个不严格的b进制的数(有时数位上的数有可能要比b还要大),转换为10机制后再模一个数p(以便存储)得到哈希值.即定义哈希函数: H(C)=(c1*b^(m-1) + c2*b^(m-

《机器学习》 西瓜书习题 第 2 章

习题 \(2.1\) 数据集包含 \(1000\) 个样本, 其中 \(500\) 个正例.\(500\) 个反例, 将其划分为包含 \(70\%\) 样本的训练集和 \(30\%\) 样本的测试集用于留出法评估, 试估算共有多少种划分方式. 如果划分要保证正例和反例一样多的话, 那么划分方式数量 \(n\) 有 \[\begin{aligned} n &= C^{500\times35\%}_{500}\times C_{500}^{500\times 35\%}\&=(C^{175}_

薛毅书习题 3.5,3.7,3.8,3.10,3.11

虽然这个雪姨的书,体系上有点似乎不太清晰,但大致上来讲,还是差不多的,习题也不错.. plot语句: + 使用boxplot函数: 注射了菌种一和菌种二三的存活,存在较大的差异... plo (1) 体重对于身高的散点图: (2)不同性别情况下体重与身高的散点图: (3)不同年龄段,体重与身高的散点图: (4) 不同性别不同年龄段下,体重与身高的散点图: 在R-studiol里面出现的错误,在R里面则绘制成功,如上图: Error in plot.new() : figure margins t

《机器学习》西瓜书习题 第 3 章

习题 3.1 试析在什么情况下式 \((3.2)\) 中不必考虑偏置项 \(b\) . 书中有提到, 可以把 \(x\) 和 \(b\) 吸收入向量形式 \(\hat{w} = (w;b)\) .此时就不用单独考虑 \(b\) 了. 3.2 试证明, 对于参数 \(w\), 对率回归的目标函数 \((3.18)\) 是非凸的, 但其对数似然函数 \((3.27)\) 是凸的. \[y = \frac{1}{1 + e^{-(\boldsymbol w^\mathrm T\boldsymbol x

《机器学习》西瓜书习题 第 4 章

习题 4.1 试证明对于不含冲突数据 (即特征向量完全相同但标记不同) 的训练集, 必存在与训练集一致 (即训练误差为 0)的决策树. 既然每个标记不同的数据特征向量都不同, 只要树的每一条 (从根解点到一个叶节点算一条) 枝干代表一种向量, 这个决策树就与训练集一致. 4.2 试析使用 "最小训练误差" 作为决策树划分选择准则的缺陷. \(4.1\) 说明了如果数据不冲突, 可以完全拟合数据集, 这正是使用 "最小训练误差" 作为决策树划分选择准则的结果. 而这是

紫书 习题3-5

#include <iostream> #include <cstdio> #include <algorithm> using namespace std; const char inst[] = "ABLR"; const int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; int main(void) { int t = 0; char s[5][6]; char c; while ((s[0

紫书 习题2-5 分数化小数

1 #include<stdio.h> //基础版 2 #define MAX 110 3 4 int main(void) 5 { 6 int a, b, c; 7 scanf("%d %d %d",&a,&b,&c); 8 9 int integer = a/b; 10 int remainderTemp=a%b; 11 int arr[MAX]; 12 13 for(int i = 0; i< c; i++){ 14 int result