当前位置: 首页 > news >正文

BST,Treap学习随笔

BST,Treap学习随笔

0 前言

学习随笔

1 BST

二叉查找树

1.1 性质

对于一个点来说

左子树中所有权值均小于当前点权值小于右子树中所有点权值

还有一个神奇的性质:

中序遍历一下 发现天然有序

上图中序遍历即为1,2,3,4,5,6,7,8,9,10,11

1.2 支持基本操作

插入(\(logn\))(最坏\(n\)

删除(\(logn\))(最坏\(n\)

找v在树上大小排第几(\(logn\))(最坏\(n\)

求第k小(\(logn\))(最坏\(n\)

求前驱(权值严格小于v的最大值)(\(logn\))(最坏\(n\)

求后继(全职严格大于v的最小值)(\(logn\))(最坏\(n\)

(因为对于同一棵树满足BST性质的形状有很多种,在一些数据下树会变成一条链,从而使操作都退化到\(O(n)\)

1.3 维护

因为Treap为BST升级版,基本操作都一样,所以直接看Treap的维护吧

2 Treap

杂交数据结构

Treap=BST(二叉查找树)+Heap(堆)

2.1 性质

既满足BST(点权)

又满足Heap(我们给点附的优先值,这里优先值是rand出来的)(可以有效避免退化成一条链的情况)

优先值满足大根堆性质(小根堆也行 只要是个堆即可)

2.2 支持操作

插入(\(logn\)

删除(\(logn\)

找v在树上大小排第几(\(logn\)

求第k小(\(logn\)

求前驱(权值严格小于v的最大值)(\(logn\)

求后继(全职严格大于v的最小值)(\(logn\)

(因为有了rand出的优先级 树在没有被神秘宇宙射线影响的情况下是不会退化到链的)

2.3 维护

2.3.1 点的信息

struct node{int l,r,val,siz,cnt;//左节点,右节点,权值,子树大小(含自己),自己有几个unsigned pri;//随机生成的优先值
}a[N];

2.3.2 插入

2.3.2.1 zig,zag

发现严重问题:如何同时维护两个性质

我们左思右想 决定先满足其中一个性质

先满足BST性质

首先在原树上找到应该放当前权值的位置

然后需要在不破坏当前BST性质的情况下使rand出的优先值满足Heap

于是发明出zig(右旋),zag(左旋)操作

对应不同需交换情况

/*Y                X/        =>     /  \X         zig   A    Y /  \                  /
A    B                BY                    X\          =>     /  \X         zag   Y    B/   \              \
A    B               A*/

发现原本的BST性质没有被破坏且X,Y父子关系还交换了

这就很像Heap中的挂在叶子节点一直往上交换的操作

size更新的时候需注意:我们在插入重复值时是直接把当前权值对应点中的cnt++

因此计算size时需要加上自己的cnt

void siz_update(int u){if(!u)return;a[u].siz=a[a[u].l].siz+a[a[u].r].siz+a[u].cnt;//加上自己的个数
}void zig(int &y){//右旋  将y与y的左子树交换//& 是为了方便直接将当前访问的y的值改变为x int x=a[y].l;//y的左子树a[y].l=a[x].r;//将x的右子树挂在y的左子树上a[x].r=y;//将y挂在x的左子树上siz_update(y);//先更新y的size(因为y现在在x下面)siz_update(x);//再更新x的sizey=x;
}void zag(int &y){//左旋 将y与y的右子树交换int x=a[y].r;//y的右子树a[y].r=a[x].l;//将x的左子树挂在y的右子树上a[x].l=y;//将y挂在x的左子树上siz_update(y);//更新sizesiz_update(x);y=x;
}

2.3.2.2 Insert

有了zig,zag我们就可以愉快地insert了

int add(int v){a[++tot]={0,0,v,1,1,rnd()};//l,r初始化为0 表示没有左节点和右节点 //val=v//siz和cnt初始化为1//pri优先级rand一下return tot;
}
void Insert(int &rt,int v){//& 也是方便直接改值if(!rt){//扫到叶子节点了rt=add(v);siz_update(rt);//别忘了更新大小return;}if(v==a[rt].val){//有这个权值了a[rt].cnt++;//直接加siz_update(rt);//依旧别忘了更新return;}else if(v<a[rt].val){//比当前权值小 说明还要往左走Insert(a[rt].l,v);//以左节点为根继续维护if(a[a[rt].l].pri<a[rt].pri){//维护Heap的性质 这里只可能是左节点有问题zig(rt);//右旋交换}}else if(v>a[rt].val){//同上Insert(a[rt].r,v);if(a[a[rt].r].pri<a[rt].pri){zag(rt);}}siz_update(rt);//别忘了更新
}

2.3.3 删除

void Erase(int &rt,int v){if(!rt){//找到了 返回return;}if(v==a[rt].val){//找到了if(a[rt].cnt>1){//还有 直接减a[rt].cnt--;siz_update(rt);//别忘了更新!return;}else if(!a[rt].l&&!a[rt].r){//交换到了叶子节点就可以直接删除了rt=0;//直接一直返回return;}else if(!a[rt].r||(a[rt].l&&a[a[rt].l].pri>a[a[rt].r].pri)){//没有右子树 或者有左子树并且左子树优先级比右子树优先级大zig(rt);//右旋上来Erase(a[rt].r,v);//交换过后原本要删的点在右边}else if(!a[rt].l||(a[rt].r&&a[a[rt].r].pri>a[a[rt].l].pri)){//没有左子树 或者有右子树并且右子树优先级比左子树优先级大zag(rt);//左旋上来Erase(a[rt].l,v);//交换后原本要删的点在左边}}else if(v<a[rt].val){//找左子树Erase(a[rt].l,v);}else if(v>a[rt].val){//找右子树Erase(a[rt].r,v);}siz_update(rt);//更新!!!!!!
}

2.3.4 查找排名

int Rank(int rt,int v){if(!rt)return 1;//空树则自己就是第一if(v==a[rt].val){//找到了return a[a[rt].l].siz+1;//左子树中所有一定比自己小 加一就是排名}else if(v<a[rt].val){return Rank(a[rt].l,v);//一定在左子树中排名}else{return a[a[rt].l].siz+a[rt].cnt+Rank(a[rt].r,v);//左子树大小加中间点个数再加右子树中排名}
}

2.3.5 找第k小

int kth(int rt,int k){if(!rt)return -1;//没找到if(k<=a[a[rt].l].siz){//如果还没左子树大小大 说明在左子树中return kth(a[rt].l,k);//递归左子树}k-=a[a[rt].l].siz;//如果比左子树大就减去if(k<=a[rt].cnt){//剩下的比中间点个数小 说明就是中间点return a[rt].val;}k-=a[rt].cnt;//还大 减去return kth(a[rt].r,k);//递归右子树
}

2.3.6 找前驱/后继

int ask_pre(int rt,int v){//前驱int res=-INF;//初始化为极小值while(rt){//一直找到底才是严格小于他的最大值if(v<=a[rt].val){//大了rt=a[rt].l;//往左边找小的}else{res=a[rt].val;//够了rt=a[rt].r;//往右边找有没有正在比v小的情况下更大的值}}return res;
}int ask_nex(int rt,int v){//后继  细节同上int res=INF;while(rt){if(v>=a[rt].val){rt=a[rt].r;}else{res=a[rt].val;rt=a[rt].l;}}return res;
}

2.4 模板题

【模板】普通平衡树

附上代码

#include<bits/stdc++.h>
using namespace std;
#define int long longconst int N=2e5+5;
const int INF=0x3f3f3f3f3f3f3f;static unsigned seed=(unsigned)chrono::steady_clock::now().time_since_epoch().count();
unsigned rnd(){seed^=seed<<12,seed^=seed>>5,seed^=seed<<11;return seed;
}
struct node{int l,r,val,siz,cnt;unsigned pri;
}a[N];
int tot=0;
int root=0;
int n;int add(int v){a[++tot]={0,0,v,1,1,rnd()};return tot;
}void siz_update(int u){if(!u)return;a[u].siz=a[a[u].l].siz+a[a[u].r].siz+a[u].cnt;}void zig(int &y){int x=a[y].l;a[y].l=a[x].r;a[x].r=y;siz_update(y);siz_update(x);y=x;
}void zag(int &y){int x=a[y].r;a[y].r=a[x].l;a[x].l=y;siz_update(y);siz_update(x);	y=x;
}void Insert(int &rt,int v){if(!rt){rt=add(v);siz_update(rt);return;}if(v==a[rt].val){a[rt].cnt++;siz_update(rt);return;}else if(v<a[rt].val){Insert(a[rt].l,v);if(a[a[rt].l].pri<a[rt].pri){zig(rt);}}else if(v>a[rt].val){Insert(a[rt].r,v);if(a[a[rt].r].pri<a[rt].pri){zag(rt);}}siz_update(rt);
}void Erase(int &rt,int v){if(!rt){return;}if(v==a[rt].val){if(a[rt].cnt>1){a[rt].cnt--;siz_update(rt);return;}else if(!a[rt].l&&!a[rt].r){rt=0;return;}else if(!a[rt].r||(a[rt].l&&a[a[rt].l].pri>a[a[rt].r].pri)){zig(rt);Erase(a[rt].r,v);}else if(!a[rt].l||(a[rt].r&&a[a[rt].r].pri>a[a[rt].l].pri)){zag(rt);Erase(a[rt].l,v);}}else if(v<a[rt].val){Erase(a[rt].l,v);}else if(v>a[rt].val){Erase(a[rt].r,v);}siz_update(rt);
}int Rank(int rt,int v){if(!rt)return 1;if(v==a[rt].val){return a[a[rt].l].siz+1;}else if(v<a[rt].val){return Rank(a[rt].l,v);}else{return a[a[rt].l].siz+a[rt].cnt+Rank(a[rt].r,v);}
}int kth(int rt,int k){if(!rt)return -1;if(k<=a[a[rt].l].siz){return kth(a[rt].l,k);}k-=a[a[rt].l].siz;if(k<=a[rt].cnt){return a[rt].val;}k-=a[rt].cnt;return kth(a[rt].r,k);
}int ask_pre(int rt,int v){int res=-INF;while(rt){if(v<=a[rt].val){rt=a[rt].l;}else{res=a[rt].val;rt=a[rt].r;}}return res;
}int ask_nex(int rt,int v){int res=INF;while(rt){if(v>=a[rt].val){rt=a[rt].r;}else{res=a[rt].val;rt=a[rt].l;}}return res;
}signed main(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);cin>>n;while(n--){int op,x;cin>>op>>x;if(op==1){Insert(root,x);}else if(op==2){Erase(root,x);}else if(op==3){cout<<Rank(root,x)<<endl;}else if(op==4){cout<<kth(root,x)<<endl;}else if(op==5){cout<<ask_pre(root,x)<<endl;}else if(op==6){cout<<ask_nex(root,x)<<endl;}}return 0;
}

完结撒花🎉🎉🎉

http://www.jsqmd.com/news/257809/

相关文章:

  • Qwen3-1.7B政务问答系统:某市大数据局部署实战案例
  • 南京市浦口江宁六合溧水高淳区英语雅思培训辅导机构推荐,2026权威出国雅思课程中心学校口碑排行榜 - 老周说教育
  • 微服务架构蓝绿部署验收测试:测试从业者的实战指南
  • Burp Suite Professional 2026.1 for Windows x64 - 领先的 Web 渗透测试软件
  • Paris Commune
  • Microsoft SQL Server 2022 RTM GDR CU23 (2026 年 1 月安全更新 | 累计更新)
  • Udemy pragmatic-system-design
  • Kotaemon微服务改造:拆分组件实现高可用架构升级
  • fastboot驱动中USB枚举过程的实战案例分析
  • 【节点】[Integer节点]原理解析与实际应用
  • Burp Suite Professional 2026.1 发布,新增功能简介
  • Burp Suite Professional 2026.1 for macOS x64 ARM64 - 领先的 Web 渗透测试软件
  • 初学Prompt工程 - 教程
  • Apple Creator Studio 2026 发布 - 强大的创意套装 (音乐制作、视频剪辑、图像设计与办公工具)
  • 制造业QMS质量管理系统推荐榜单 - 详解
  • 2026隔音板定制厂家排名,教你如何选择好厂家 - 工业品牌热点
  • 欧姆龙CP1E PLC与台达变频器Modbus RTU通讯实战
  • 在 Ubuntu 上安装 noVNC
  • 1.2 深度学习核心概念一网打尽:神经网络、激活函数与损失函数详解
  • 行式存储 vs 列式存储:原理、差异与真实业务案例解析
  • 收集自己的每日学习知识点数量,统计每周学习总知识点,输出学习进度评分。
  • 2026年华数杯赛题浅析-助攻快速选题
  • 1.3 PyTorch实战入门:打造你的第一个图像分类项目
  • C++中类内的成员变量和成员函数分开存储,只有非静态成员变量才存储在类的对象上
  • 1.4 评估指标与可解释性:如何科学评价你的AI模型
  • Managerial communication
  • 2.1 Transformer解密:自注意力机制与位置编码全解析
  • 完善我的第一个工作流: 增加循环逻辑
  • 攻克边缘设备AI部署:基于Jetson Nano的YOLOv5零基础部署与性能调优实战
  • RK3588嵌入式AI工业部署:YOLOv11 + OpenCV实时推理系统全栈实现