关于平衡树(施工中)

news/2024/10/6 3:18:13/文章来源:https://www.cnblogs.com/Elaina-0/p/18275500
$\LARGE {一些无聊的定义}$

二叉搜索树(BST树)

定义

二叉搜索树是一种二叉树的树形数据结构,其定义如下:

  • 空树是二叉搜索树。

  • 若二叉搜索树的左子树不为空,则其左子树上所有点的附加权值均小于其根节点的值。

  • 若二叉搜索树的右子树不为空,则其右子树上所有点的附加权值均大于其根节点的值。

  • 二叉搜索树的左右子树均为二叉搜索树。

复杂度

二叉搜索树上的基本操作所花费的时间与这棵树的高度成\(\color{#40c0bb}{正比}\)。对于一个有 \(n\) 个结点的二叉搜索树中,这些操作的最优时间复杂度为 \(O(\log n)\),最坏为 \(O(n)\)。随机构造这样一棵二叉搜索树的\(\color{#40c0bb}{期望高度}\)\(O(\log n)\)

性质

其实也就是定义

\(x\) 是二叉搜索树中的一个结点。

如果 \(y\)\(x\) 左子树中的一个结点,那么 \(y.key≤x.key\)

如果 \(y\)\(x\) 右子树中的一个结点,那么 \(y.key≥x.key\)

在二叉搜索树中:

  1. 若任意结点的左子树不空,则左子树上所有结点的值均不大于它的根结点的值。

  2. 若任意结点的右子树不空,则右子树上所有结点的值均不小于它的根结点的值。

  3. 任意结点的左、右子树也分别为二叉搜索树。

操作

二叉搜索树通常可以高效地完成以下操作:

  1. 查找最小/最大值

  2. 搜索元素

  3. 插入一个元素

  4. 删除一个元素

  5. 求元素的排名

  6. 查找排名为 k 的元素

平衡树

定义

由二叉搜索树的复杂度分析可知:操作的复杂度与树的高度 \(h\) 有关。

那么我们可以通过一定操作维持树的高度(平衡性)来降低操作的复杂度,这就是\(\color{#40c0bb}{平衡树}\)

\(\color{#40c0bb} \large \textbf{平衡性}\)
通常指每个结点的左右子树的高度之差的绝对值(平衡因子)最多为 \(1\)

平衡的调整过程——树旋转

定义

树旋转是在二叉树中的一种子树调整操作, 每一次旋转并\(\color{#40c0bb}{不影响}\)对该二叉树进行\(\color{#40c0bb}{中序遍历}\)的结果。
树旋转通常应用于需要调整树的局部平衡性的场合。树旋转包括两个不同的方式,分别是\(\color{#40c0bb}{左旋(Left Rotate 或者  zag)}\)\(\color{#40c0bb}{右旋(Right Rotate 或者 zig)}\)。 两种旋转呈镜像,而且互为逆操作。

具体操作

右旋

对于结点 \(A\) 的右旋操作是指:将 \(A\) 的左孩子 \(B\) 向右上旋转,代替 \(A\) 成为根节点,将 \(A\) 结点向右下旋转成为 \(B\) 的右子树的根结点,\(B\) 的原来的右子树变为 \(A\) 的左子树。

左旋

完全同理

具体情况

至此,正片结束

背景

不难发现\(BST树\)的一种极端情况:\(\color{#40c0bb}{退化情况}\)

这种毒瘤数据让时间复杂度从\(O(log(n))\)退化到了恐怖的\(O(n)\)

于是就有各种各样的科学家们,开始思考人生,丧心病狂地创造出了各种优化BST的方法...

Splay

原理

啥是\(Splay\)
她实际上就是一种可以旋转的平衡树。
她可以通过旋转保持\(\color{#40c0bb}{平衡性}\)从而解决退化情况。

(方框表示子树,圆框表示节点)

现在,我们要将 \(x\) 节点往上爬一层到他的父节点 \(y\) ,为了保证不改变中序遍历顺序,我们可以让 \(y\) 成为 \(x\) 的右儿子。

但是原来的 \(x\) 节点是有右儿子 \(B\) 的,显然我们要把 \(B\) 换一个位置才能达到目的。

我们知道: \(x\) 节点的右子树必然是大于 \(x\) 节点的; \(y\) 节点必然是大于 \(x\) 节点的右子树和 \(x\) 节点本身的(因为 \(x\) 节点及其右子树都是原来 \(y\) 的左子树,肯定比 \(y\) 小(根据二叉搜索树性质))

因此我们可以把 \(x\) 节点原来的右子树放在 \(y\) 的左儿子的位置上,达成目的。

实际上,这也就是\(\color{#40c0bb}\textbf{右旋}\)的原理。

对于通解:

若节点 \(x\)\(y\) 节点的位置 \(z\)(\(z=0\) 为左节点,\(z=1\) 为右节点 )

  1. \(y\) 节点放到 \(x\) 节点的 \(z \oplus 1\) 的位置.(也就是, \(x\) 节点为 \(y\) 节点的右子树,那么 \(y\) 节点就放到左子树, \(x\) 节点为 \(y\) 节点左子树,那么 \(y\) 节点就放到右子树位置)

  2. 如果说 \(x\) 节点的 \(z \oplus 1\) 位置上,已经有节点,或者一棵子树,那么我们就将原来 \(x\) 节点 \(z \oplus 1\) 位置上的子树,放到 \(y\) 节点的位置 \(z\) 上面.

操作

才不是因为我懒得写注释所以直接把代码粘过来了...

基本操作

  • \(maintain(x)\):在改变节点位置后,将节点 \(x\)\(\text{size}\) 更新。
  • \(get(x)\):判断节点 \(x\) 是父亲节点的左儿子还是右儿子。
  • \(clear(x)\):清空节点 \(x\)

void maintain(int x){sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+cnt[x];
}bool get(int x){return x==ch[fa[x]][1];
}void clear(int x){ch[x][0]=ch[x][1]=fa[x]=val[x]=sz[x]=cnt[x]=0;
}

旋转操作

void rotate(int x){int y=fa[x],z=fa[y],chk=get(x);ch[y][chk]=ch[x][chk^1];if(ch[x][chk^1]) fa[ch[x][chk^1]]=y;ch[x][chk^1]=y;fa[y]=x;fa[x]=z;if(z) ch[z][y==ch[z][1]]=x;maintain(y);maintain(x);
}

Splay操作

单点操作

void splay(int x) {for (int f = fa[x]; f = fa[x], f; rotate(x))if (fa[f]) rotate(get(x) == get(f) ? f : x);rt = x;
}

区间操作

对应 \(a_{R + 1}\) 的节点的左子树中序遍历为序列 \(a[L, R]\),故其为区间 \([L, R]\) 代表的子树。

void splay(int x,int goal=0){if(goal==0) rt=x;while(fa[x]!=goal){int f=fa[x];if(fa[fa[x]]!=goal){rotate(get(x)==get(f)?f:x);}rotate(x);}
}

插入操作

void ins(int k){//insertif(!rt){val[++tot]=k;cnt[tot]++;rt=tot;maintain(rt);return;}int cur=rt,f=0;while(1){if(val[cur]==k){cnt[cur]++;maintain(cur);maintain(f);splay(cur);break;}f=cur;cur=ch[cur][val[cur]<k];if(!cur){val[++tot]=k;cnt[tot]++;fa[tot]=f;ch[f][val[f]<k]=tot;maintain(tot);maintain(f);splay(tot);break;}}
}

查询 \(x\) 的排名

int rk(int k){//the rank of "k"int res=0,cur=rt;while(1){if(k<val[cur]){cur=ch[cur][0];}else{res+=sz[ch[cur][0]];if(!cur) return res+1;if(k==val[cur]){splay(cur);return res+1;}res+=cnt[cur];cur=ch[cur][1];}}
}

查询排名 \(x\) 的数

int kth(int k){//the number whose rank is "k"int cur=rt;while(1){if(ch[cur][0] && k<=sz[ch[cur][0]]){cur=ch[cur][0];}else{k-=cnt[cur]+sz[ch[cur][0]];if(k<=0){splay(cur);return val[cur];}cur=ch[cur][1];}}
}

查询前驱&后继

前驱

int pre(){//precursorint cur=ch[rt][0];if(!cur) return cur;while(ch[cur][1]) cur=ch[cur][1];splay(cur);return cur;
}

后继

其实就是查前驱的反面

int nxt(){//next or successorint cur=ch[rt][1];if(!cur) return cur;while(ch[cur][0]) cur=ch[cur][0];splay(cur);return cur;
}

查前驱后继有好多种写法,如果想偷懒只写一遍就可以酱紫

int prenxt(int x,int k){//0 pre 1 nxtfind(x);int cur=rt;if(val[cur]<x && !k) return cur;if(val[cur]>x && k) return cur;cur=ch[cur][k];while(ch[cur][!k]){cur=ch[cur][!k];}return cur;
}

删除操作

void del(int k){//deleterk(k);if(cnt[rt]>1){cnt[rt]--;maintain(rt);return;}if(!ch[rt][0] && !ch[rt][1]){clear(rt);rt=0;return;}if(!ch[rt][0]){int cur=rt;rt=ch[rt][1];fa[rt]=0;clear(cur);return;}if(!ch[rt][1]){int cur=rt;rt=ch[rt][0];fa[rt]=0;clear(cur);return;}int cur=rt,x=pre();fa[ch[cur][1]]=x;ch[x][1]=ch[cur][1];clear(cur);maintain(rt);
}

Code

Elaina's Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define rd read()
#define inf 0x3f
#define INF 0x3f3f3f3f3f3f3f3f
#define mst(a,b) memset((a),(b),sizeof((a)))
#define Elaina 0
inline int read(){int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';return x*f;
}
const int N=1e7+100;struct Slpay{int rt;//根 int tot;//节点编号 int fa[N];//父节点 int ch[N][2];//子节点 左0右1 int val[N];//权值 int cnt[N];//节点大小 int sz[N];//子树大小 void maintain(int x){//更新编号为x的结点的子树大小sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+cnt[x];}bool get(int x){//判断节点x是父亲节点的左儿子还是右儿子 return x==ch[fa[x]][1];}void clear(int x){//清空x节点 ch[x][0]=ch[x][1]=fa[x]=val[x]=sz[x]=cnt[x]=0;}void rotate(int x){int y=fa[x],z=fa[y],chk=get(x);ch[y][chk]=ch[x][chk^1];if(ch[x][chk^1]) fa[ch[x][chk^1]]=y;ch[x][chk^1]=y;fa[y]=x;fa[x]=z;if(z) ch[z][y==ch[z][1]]=x;maintain(y);maintain(x);}void splay(int x,int goal=0){if(goal==0) rt=x;while(fa[x]!=goal){int f=fa[x];if(fa[fa[x]]!=goal){rotate(get(x)==get(f)?f:x);}rotate(x);}}void ins(int k){//insertif(!rt){val[++tot]=k;cnt[tot]++;rt=tot;maintain(rt);return;}int cur=rt,f=0;while(1){if(val[cur]==k){cnt[cur]++;maintain(cur);maintain(f);splay(cur);break;}f=cur;cur=ch[cur][val[cur]<k];if(!cur){val[++tot]=k;cnt[tot]++;fa[tot]=f;ch[f][val[f]<k]=tot;maintain(tot);maintain(f);splay(tot);break;}}}int rk(int k){//the rank of "k"int res=0,cur=rt;while(1){if(k<val[cur]){cur=ch[cur][0];}else{res+=sz[ch[cur][0]];if(!cur) return res+1;if(k==val[cur]){splay(cur);return res+1;}res+=cnt[cur];cur=ch[cur][1];}}}int kth(int k){//the number whose rank is "k"int cur=rt;while(1){if(ch[cur][0] && k<=sz[ch[cur][0]]){cur=ch[cur][0];}else{k-=cnt[cur]+sz[ch[cur][0]];if(k<=0){splay(cur);return val[cur];}cur=ch[cur][1];}}}int pre(){//precursorint cur=ch[rt][0];if(!cur) return cur;while(ch[cur][1]) cur=ch[cur][1];splay(cur);return cur;}int nxt(){//next or successorint cur=ch[rt][1];if(!cur) return cur;while(ch[cur][0]) cur=ch[cur][0];splay(cur);return cur;}void del(int k){//deleterk(k);if(cnt[rt]>1){cnt[rt]--;maintain(rt);return;}if(!ch[rt][0] && !ch[rt][1]){clear(rt);rt=0;return;}if(!ch[rt][0]){int cur=rt;rt=ch[rt][1];fa[rt]=0;clear(cur);return;}if(!ch[rt][1]){int cur=rt;rt=ch[rt][0];fa[rt]=0;clear(cur);return;}int cur=rt,x=pre();fa[ch[cur][1]]=x;ch[x][1]=ch[cur][1];clear(cur);maintain(rt);}void find(int x){int cur=rt;if(!cur) return;while(ch[cur][x>val[cur]]&&x!=val[cur]){cur=ch[cur][x>val[cur]];}splay(cur,0);}int get_pre(int x){find(x);int cur=rt;if(val[cur]<x) return cur;cur=ch[cur][0];while(ch[cur][1]){cur=ch[cur][1];}return cur;}int get_nxt(int x){find(x);int cur=rt;if(val[cur]>x) return cur;cur=ch[cur][1];while(ch[cur][0]){cur=ch[cur][0];}return cur;}int prenxt(int x,int k){//0 pre 1 nxtfind(x);int cur=rt;if(val[cur]<x && !k) return cur;if(val[cur]>x && k) return cur;cur=ch[cur][k];while(ch[cur][!k]){cur=ch[cur][!k];}return cur;}
}tr;signed main(){int m=rd;while(m--){int opt=rd,x=rd;if(opt==1){tr.ins(x);}else if(opt==2){tr.del(x);}else if(opt==3){printf("%lld\n",tr.rk(x));}else if(opt==4){printf("%lld\n",tr.kth(x));}else if(opt==5){tr.ins(x),printf("%lld\n",tr.val[tr.pre()]),tr.del(x);}else{tr.ins(x),printf("%lld\n",tr.val[tr.nxt()]),tr.del(x);}}return Elaina;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/738921.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux 提权-SUID/SGID_1

本文通过 Google 翻译 SUID | SGID Part-1 – Linux Privilege Escalation 这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校正及个别注释补充。导航0 前言 1 了解特殊权限 2 寻找 SUID/SGID 二进制文件 – 手动方法2.1 枚举 SUID 二进制文件 2.2 枚举 SGID 二…

Java解析并修改JSON:将isShow属性改为false

哈喽,大家好,我是木头左!在Java中,可以使用各种库来处理JSON数据。其中,Jackson和Gson是两个非常流行且功能强大的库。在这篇文章中,将使用Jackson库来解析给定的JSON字符串,将其转换为Map对象,然后修改其中的"isShow"属性,最后再将其转回JSON字符串。 准备…

#cmd的常用命令(Dos)

cmd的常用命令首先win+r输入cmd并回车进入cmd命令中cd 命令:进入指定目录cd d:进入d盘目录.会发现进入不了d盘,因为cd只能在当前目录下操作不能跨区操作. 键入d:回车进入d盘.我d盘下有aaa文件夹cd aaa进入文件夹aaa目录下提示 ".."为上一级目录."."为当前…

StarRocks数据导入慢问题解决

一、问题描述依据StarRocks官网快速开始安装教程,用docker compose安装了starrocks,log模块从rabbitMq的队列批量获取log消息,发现队列消息有堆积,一晚上下来大概能对接4000条消息。经单元测试发现insert into到starrocks中时间竟然相差几百倍。 mysql每条insert sql执行3.…

CAN转PN网关模块连接激光切割机的配置方法

本文介绍了兴达易控CAN转Profinet网关模块(XD-PN_CAN20)用于连接CAN激光切割机的使用方法,激光切割机在工业生产中被广泛应用,而激光发射器与控制设备常以不同的协议存在两者之间,CAN总线和Profinet以各自的特点被广泛用于设备当中。本文将介绍介绍兴达易控CAN转Profinet网…

R语言、SAS潜类别(分类)轨迹模型LCTM分析体重指数 (BMI)数据可视化|附代码数据

全文下载链接: http://tecdat.cn/?p=26105 最近我们被客户要求撰写关于LCTM的研究报告,包括一些图形和统计输出。 在本文中,潜类别轨迹建模 (LCTM) 是流行病学中一种相对较新的方法,用于描述生命过程中的暴露,它将异质人群简化为同质模式或类别。然而,对于给定的数据集…

第二章 和式

记号 求和的符号有两种形式 第一种是确定界限的形式,也叫封闭形式,例如:\(\sum\limits_{k=1}^n a_k\) 第二种叫做一般形式,就是把一个或者多个条件写在 \(\sum\) 符号的下面,例如刚刚的例子可以写成 \(\sum\limits_{1\le k \le n} a_k\) 和式和递归式的转化 和式和递归式之…

Andriod SDK安装教程

前言 最简单的方式 我们使用ANDROID STUDIO这款开发工具下载对应的Andriod SDK。 可是我们如果不开发安卓,只是用它的一些SDK包的话而安装整个开发工具,就没必要了。 这里讲的是用独立的 命令行工具 来操作。 下载命令行工具 点击此处进入下载页面, 滑动到最下边,选择合适的…

动态规划--打家劫舍-零钱兑换-算法刷题01

目录1. 概念2. 打家劫舍3 零钱兑换 1. 概念 关于动态规划这类问题 强烈建议学完下面的帖子: https://blog.csdn.net/qq_16664581/article/details/89598243 理解动态规划的使用场景强烈建议读一下这个故事: https://www.cnblogs.com/sdjl/articles/1274312.html 步骤:确定问…

一天快速入门Django:从0到1创建属于自己的Web应用

本文详细讲解了从零开始构建自己的 Web 应用所需的 Django 操作步骤。文章以简明易懂的方式引导读者设置开发环境,创建 Django 项目和应用,定义数据模型,编写视图函数和模板,以及配置 URL 路由。强调了 Django 框架的高效性和灵活性,特别是其基于 MTV(模型、模板、视图)…

羽云十六进制编辑器之插件开发文档

羽云十六进制编辑器的开发文档首页【占位】本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可本文来自博客园,作者:寂静的羽夏 ,一个热爱计算机技术的菜鸟转载请注明原文链接:https://www.cnblogs.com/wingsummer/p/18286419

Mybatis PageHelper编译SQL引发的一次性能问题.18286262

起源 最近一直在跟大佬们做公司项目的性能优化,我这种小卡乐咪基本上负责的就是慢接口优化,但实际上只有以下几种情况需要进行接口代码级别的改造:循环查库、RPC 数据库设计不合理 业务流程太长,代码耦合性太高等随着对接口分析的深入,我们越来越发现系统中有很多拖后腿的…