划着划着水一不小心NOIP还考的凑合了…
所以退役的打算要稍微搁置一下了…
要准备准备省选了….
但是自己已经啥也不会了…
所以只能重新拾起来…
从splay开始吧…

splay我以前扔了个板子来着, 之前理解的还是不够深入于是就一直没有填坑…
现在又重新拾了一下就来填坑…
不过讲平衡树要画好多图的说OvO

BST(二叉查找树)大家应该都知道吧, 所以就不讲了…
大家也都知道BST会被卡成一条链, 复杂度动不动就退化成O(n)
所以就出现了各种各样的二叉平衡树…

splay是个很年轻的数据结构…好像是什么1985年由tarjan提出的…
splay的主要特点就是不怎么平衡, 但是一直在转所以深度并不会太深…
而且会将最近访问的条目放在根的附近, 这样重复访问的时候效率会加快..
(听上去好像挺有用, 但实际生活更多用的还是RBT之类的吧?)

splay的主要操作就是rotate和splay(招牌)的说…
其他的操作就是BST的操作了Emmmm…
我们一个一个讲:

rotate

rotate跟很多其他什么二叉平衡树是一样的…

由题意, 如图示, 尽管箭头画的丑的一笔大小都不一样但是凑合着看吧… 旋转完还是保持二叉排序树性质的~
我们来分析一下旋转的过程
假如我们要旋转4号节点(下面称为x), 就是上面左图箭头指的那个节点..
因为他是左儿子, 所以要右旋(有些人喜欢分左右旋, 但是你把左儿子左旋是几个意思←_←)
然后就是中图红色和绿色的拆边和建边:
说明好麻烦啊, 上伪代码吧:

if grandfa->ch[0]==fa fa_dir=0 else fa_dir=1 //确定父亲是祖父的哪个儿子
grandfa->ch[fa_dir]=x //将这个儿子置为x
if fa->ch[0]==x dir=0 else dir=1 //确定x是父亲的哪个儿子
fa->ch[dir]=x->ch[dir^1] //将x父亲的dir儿子置为x的dir另一侧的儿子 (为了保持性质,这很显然)
x->ch[dir^1]=fa //将x变为x原来父亲的父亲(没大没小..) (为了保持性质,要放在dir的对侧)
//当然这里要更新所有改变父子关系中所有的父亲咯~

以上几步如果不乱的话应该顺序是无所谓的吧…(不过还是形成一个正确的板子背过的好, 不然老是挂(这几天都不知道把rotate里面的句子调了多少遍了))
然后我们就能整理出这样的代码:

void rotat(node *now){ //这里强行去掉一个e是防止与STL冲突(虽然好像无所谓??)
int wh=now->getwh(); node *fa=now->fa,*fafa=fa->fa;
if(fafa!=null) fafa->ch[fa->getwh()]=now;
fa->setch(wh,now->ch[wh^1]);
now->setch(wh^1,fa); //这里的setch是包括设置儿子和父亲的
now->fa=fafa;
}

rotate就是这样…
其实自己多画一画就比较方便理解…

splay

splay之所以叫splay是因为splay有个叫splay的操作..(绕口令)
我们说过, splay会把最近访问的点转到根上…(其实就是伸个懒腰….把这个点扭上去)
这里的splay操作有一种很显然的做法: 就是不停的rotate
这种操作叫做单旋(敲黑板) 这么显然的做法显然是会被卡的…比如转着转着就特别特别不平衡了什么的…
(丧心病狂的出题人可能会先问这个端点, 再问那个端点, 就炸了…)
这么写的做法我们亲切地称为spaly(来找不同啊)
所以我们为了保持比较平衡的splay(其实仍然歪七扭八的…) 我们要双旋(又敲黑板)
Emmmm
双旋的过程, 我们要判断x和x的父亲是不是同向的(即都是自己父亲的左儿子or右儿子)
- 如果是同向的, 就要先转父亲再转x
- 否则转2遍x…
其实为什么这样做还是挺明显的, 可以自己画一下rotate的过程看看不同的顺序会对平衡产生什么差异(才不是懒得画呢╭(╯^╰)╮)
由于rotate已经写好了, 我们的splay就呼之欲出了

//将节点now转到tar的下一位上(Emmmm..这个地方划重点,不是转到tar上哦~) 若tar为null则表示转到根上
void splay(node *now,node *tar){
for(;now->fa!=tar;rotat(now)) //因为不是转到tar上所以是fa!=tar...
if(now->fa->fa!=tar) //防止双旋转过,如果只转一下就到那就不双旋了
//反正双旋的第二步都是转我们就只转第一步就好了(因为第二步被压到for循环里去了2333)
now->getwh()==now->fa->getwh()?rotat(now->fa):rotat(now);
if(tar==null) rt=now; //转到根上就要重置根...
}

反正就是这样… 剩下的就是bst的操作了OvO
用其他的什么树也都能搞定了OvO

例题的话我们就用普通平衡树吧OvO 门
我们来逐个分析操作:
1.插入一个数x
这不是bst操作么… 用从根开始找到该插入的位置插入即可…

void insert(int val){
node *last = null, *now = rt, *nnow = NEW(); //NEW是自定义函数(不太会重载new)
nnow->val = val; nnow->cnt = nnow->sz = 1; //申请新节点
while (now != null){
last = now; //last存储父亲
if (nnow->val == now->val){ //如果树上已经有了数值相同的点
now->cnt++; now->sz++; //直接修改cnt和size就行了
splay(now, null); return; //把最近访问的节点旋到根上
}
if (nnow->val<now->val) now = now->ch[0]; //如果比当前节点值小显然要往左走
else now = now->ch[1]; //否则往右走
}
if (last == now) rt = nnow; //如果没有根当前点就是根
else if (nnow->val<last->val) last->setch(0, nnow); //如果小于最后走到的位置就设为左叶子
else last->setch(1, nnow); //否则右叶子
splay(nnow, null); //转到根上
}

2.删除数x
在此之前我们要先找到x是不是OvO…
所以……bst的查找!!!

node *find(int val){
node *now = rt;
while (now != null){
if (now->val == val) break; //找到了
if (now->val<val) now = now->ch[1]; //当前节点小于要查询的值 往右走
else now = now->ch[0]; //否则往左走
}
if (now != null) splay(now, null); //找到了就转到根上
return now;
}

找到这个点的位置之后删掉就完了OvO.. 不过要分类讨论(注意这里并没有回收内存所以并不是真的删掉了OvO 如果开始的时候里面有点那要池子要开双倍的)

void delet(int val){ //不要问我为啥去个e... delete是关键字啊= =
node *tar = find(val); //找到的时候就旋到根上了 这就很和善..
if (tar == null) return; //没找到删个毛线啊
if (tar->cnt>1){
tar->cnt--; tar->sz--; return; //如果是重复的减去一个就行了很省事..
} //无聊的分类讨论
if (tar->ch[0] == null&&tar->ch[1] == null)
rt = null; //如果这就是根 删了就完了
else if (tar->ch[0] == null)
tar->ch[1]->fa = null, rt = tar->ch[1]; //如果没有左子树,直接将右儿子作为根
else if (tar->ch[1] == null)
tar->ch[0]->fa = null, rt = tar->ch[0]; //如果没有右子树,直接将左儿子作为根
else{ //左右儿子都有是最麻烦 但是最常见的..
node *rch = tar->ch[0];
while (rch->ch[1] != null) rch = rch->ch[1]; //我们要找到左儿子中最右的儿子来当新根...
splay(rch, null); //把这个点转到根上
rch->setch(1, tar->ch[1]); //把右儿子接到上面
rch->fa = null; rt = rch; //更新一下新根信息
}
}

3.查询x数的排名
这个嘛= = 我们要维护每个点子树的大小…
我们写一个update函数…

void node::update(){
sz=ch[0]->sz+ch[1]->sz+cnt; //左子树大小+右子树大小+该数值的个数
}

只要改变树形态(修改儿子的时候)调用就好了…(常数在哭泣…)
查排名的时候就可以:

int rank(int val){
int ls = 0; //记录比走到当前节点前一定更小的数的个数..
node *now = rt;
while (now != null){
if (now->val == val){
//找到这个值就把小的数的个数和他左子树的大小+1作为名次..
int ans = ls + now->ch[0]->sz + 1;
//把当前查询的点转到根上..
splay(now, null);
return ans;
}
//还是比较大小然后分往左右走..
if (now->val<val) ls += now->ch[0]->sz + now->cnt, now = now->ch[1];
else now = now->ch[0];
}
return -1;
}

4.查排名为x的数
不想写文字说明了OvO

int pos(int k){
int ls = 0; node *now = rt;
while (now != null){
int po = ls + now->ch[0]->sz;
if (po + 1 <= k&&k <= po + now->cnt){
splay(now, null); return now->val; //如果当前点符合要求就是他了(记得转到根上)
}
//还是判断左右...
if (po<k) ls = po + now->cnt, now = now->ch[1];
else now = now->ch[0];
}
return -1;
}

5.查询前驱(后继一样我就一起写了)

int pre(int val){
int ans = -INF; node *now = rt;
while (now != null){
//一路左右找就行...反正相等也不满足条件...
if (now->val<val)
ans = max(ans, now->val), now = now->ch[1];
else now = now->ch[0];
}
return ans;
}
int nxt(int val){
int ans = INF; node *now = rt;
while (now != null){
if (now->val <= val) now = now->ch[1];
else ans = min(ans, now->val), now = now->ch[0];
}
return ans;
}

就这样咯~ 下面附上完整的代码:

#include <cstdio>
const int N = 100010;
const int INF = ~0U >> 1;
inline int gn(int a = 0, char c = 0, int f = 1){
for (; (c<'0' || c>'9') && c != '-'; c = getchar()); if (c == '-') c = getchar(), f = -1;
for (; c >= '0'&&c <= '9'; c = getchar()) a = a * 10 + c - '0'; return a*f;
}
inline int max(const int &a, const int &b){ return a>b ? a : b; }
inline int min(const int &a, const int &b){ return a<b ? a : b; }
struct node{
int sz, val, cnt;
node *ch[2], *fa;
void update();
int getwh();
void setch(int wh, node *child);
}*rt, *null, pool[N]; int tot = 0;
void node::update(){
sz = ch[0]->sz + ch[1]->sz + cnt;
}
int node::getwh(){
return fa->ch[0] == this ? 0 : 1;
}
void node::setch(int wh, node *child){
ch[wh] = child;
if (child != null) child->fa = this;
update();
}
node *NEW(){
node *now = pool + ++tot;
now->sz = now->cnt = now->val = 0;
now->ch[0] = now->ch[1] = now->fa = null;
return now;
}
void init(){
null = pool;
null->ch[0] = null->ch[1] = null;
null->cnt = null->sz = null->val = 0;
rt = null;
}
void rotate(node *now){
node *fa = now->fa, *fafa = fa->fa;
int wh = now->getwh();
if (fafa != null) fafa->ch[fa->getwh()] = now;
fa->setch(wh, now->ch[wh ^ 1]);
now->setch(wh ^ 1, now->fa);
now->fa = fafa;
}
void splay(node *now, node *tar){
for (; now->fa != tar; rotate(now))
if (now->fa->fa != tar)
now->getwh() == now->fa->getwh() ? rotate(now->fa) : rotate(now);
if (tar == null) rt = now;
}
node *find(int val){
node *now = rt;
while (now != null){
if (now->val == val) break;
if (now->val<val) now = now->ch[1];
else now = now->ch[0];
}
if (now != null) splay(now, null);
return now;
}
void insert(int val){
node *last = null, *now = rt, *nnow = NEW();
nnow->val = val; nnow->cnt = nnow->sz = 1;
while (now != null){
last = now;
if (nnow->val == now->val){
now->cnt++; now->sz++;
splay(now, null); return;
}
if (nnow->val<now->val) now = now->ch[0];
else now = now->ch[1];
}
if (last == now) rt = nnow;
else if (nnow->val<last->val) last->setch(0, nnow);
else last->setch(1, nnow);
splay(nnow, null);
}
void delet(int val){
node *tar = find(val);
if (tar == null) return;
if (tar->cnt>1){
tar->cnt--; tar->sz--; return;
} //无聊的分类讨论
if (tar->ch[0] == null&&tar->ch[1] == null)
rt = null;
else if (tar->ch[0] == null)
tar->ch[1]->fa = null, rt = tar->ch[1];
else if (tar->ch[1] == null)
tar->ch[0]->fa = null, rt = tar->ch[0];
else{
node *rch = tar->ch[0];
while (rch->ch[1] != null) rch = rch->ch[1];
splay(rch, null);
rch->setch(1, tar->ch[1]);
rch->fa = null; rt = rch;
}
}
int pre(int val){
int ans = -INF; node *now = rt;
while (now != null){
if (now->val<val)
ans = max(ans, now->val), now = now->ch[1];
else now = now->ch[0];
}
return ans;
}
int nxt(int val){
int ans = INF; node *now = rt;
while (now != null){
if (now->val <= val) now = now->ch[1];
else ans = min(ans, now->val), now = now->ch[0];
}
return ans;
}
int rank(int val){
int ls = 0; node *now = rt;
while (now != null){
if (now->val == val){
int ans = ls + now->ch[0]->sz + 1;
splay(now, null);
return ans;
}
if (now->val<val) ls += now->ch[0]->sz + now->cnt, now = now->ch[1];
else now = now->ch[0];
}
return -1;
}
int pos(int k){
int ls = 0; node *now = rt;
while (now != null){
int po = ls + now->ch[0]->sz;
if (po + 1 <= k&&k <= po + now->cnt){
splay(now, null); return now->val;
}
if (po<k) ls = po + now->cnt, now = now->ch[1];
else now = now->ch[0];
}
return -1;
}
void debugtree(node *nod){
if (nod->ch[0] != null) debugtree(nod->ch[0]);
printf("%d\n", nod->val);
if (nod->ch[1] != null) debugtree(nod->ch[1]);
}
int main(){
init(); int n = gn();
for (int i = 1; i <= n; ++i){
int opt = gn(), x = gn();
switch (opt){
case 1:insert(x);break;
case 2:delet(x); break;
case 3:printf("%d\n", rank(x)); break;
case 4:printf("%d\n", pos(x)); break;
case 5:printf("%d\n", pre(x)); break;
case 6:printf("%d\n", nxt(x)); break;
}
//puts("*********");
//debugtree(rt);
}
}

就这样吧…
终于把自己挖的坑填上了…
自己挖的坑跪着也要填完。。(累觉不爱

【模板篇】splay(填坑)+模板题(普通平衡树)的更多相关文章

  1. 【模板】Splay(伸展树)普通平衡树(数据加强版)/洛谷P6136

    题目链接 https://www.luogu.com.cn/problem/P6136 题目大意 需要写一种数据结构,来维护一些非负整数( \(int\) 范围内)的升序序列,其中需要提供以下操作: ...

  2. 蒟蒻的splay 1---------洛谷板子题普通平衡树

    前言部分 splay是个什么东西呢? 它就是个平衡树,支持以下操作 这些操作还可以用treap,替罪羊树,红黑树,multiset balabala(好像混进去什么奇怪的东西) 这里就只说一下spla ...

  3. React Native填坑之旅--与Native通信之iOS篇

    终于开始新一篇的填坑之旅了.RN厉害的一个地方就是RN可以和Native组件通信.这个Native组件包括native的库和自定义视图,我们今天主要设计的内容是native库方面的只是.自定义视图的使 ...

  4. LCT总结——概念篇+洛谷P3690[模板]Link Cut Tree(动态树)(LCT,Splay)

    为了优化体验(其实是强迫症),蒟蒻把总结拆成了两篇,方便不同学习阶段的Dalao们切换. LCT总结--应用篇戳这里 概念.性质简述 首先介绍一下链剖分的概念(感谢laofu的讲课) 链剖分,是指一类 ...

  5. 平衡树简单教程及模板(splay, 替罪羊树, 非旋treap)

    原文链接https://www.cnblogs.com/zhouzhendong/p/Balanced-Binary-Tree.html 注意是简单教程,不是入门教程. splay 1. 旋转: 假设 ...

  6. 10-C++远征之模板篇-学习笔记

    C++远征之模板篇 将会学到的内容: 模板函数 & 模板类 -> 标准模板类 友元函数 & 友元类 静态数据成员 & 静态成员函数 运算符重载: 一切皆有可能 友元函数 ...

  7. Node学习笔记(四):gulp+express+io.socket部署angularJs2(填坑篇)

    这篇就先暂停下上篇博客--你画我猜的进度,因为在做这个游戏的时候,想采用最新的ng2技术,奈何坑是一片又一片,这边就先介绍下环境部署和填坑史 既然要用ng2,首先要拿到资源,我这边用的是angular ...

  8. React Native填坑之旅--Flow篇(番外)

    flow不是React Native必会的技能,但是作为正式的产品开发优势很有必要掌握的技能之一.所以,算是RN填坑之旅系列的番外篇. Flow是一个静态的检查类型检查工具,设计之初的目的就是为了可以 ...

  9. React Native填坑之旅--Navigation篇

    React Native的导航有两种,一种是iOS和Android通用的叫做Navigator,一种是支持iOS的叫做NavigatorIOS.我们这里只讨论通用的Navigator.会了Naviga ...

随机推荐

  1. 虚拟IP---Linux下一个网卡配置多个IP

    转:http://blog.csdn.net/turkeyzhou/article/details/16971225 Linux下配置网卡ip别名何谓ip别名?用windows的话说,就是为一个网卡配 ...

  2. DLL劫持技术例子: HijackDll

    控制台程序:DllLoader Dll加载器,用于动态加载目标Dll,并动态调用目标函数 #include <cstdio> #include <windows.h> type ...

  3. A1075 PAT Judge (25 分)

    The ranklist of PAT is generated from the status list, which shows the scores of the submissions. Th ...

  4. 《DSP using MATLAB》Problem 9.4

    只放第1小题. 代码: %% ------------------------------------------------------------------------ %% Output In ...

  5. 在Logstash的配置文件中对日志事件进行区分

    1.多个日志文件作为输入源 input { # 通过给日志事件定义类型来区分 file { path => ["/var/log/nginx/access.log"] typ ...

  6. 21-2-substring

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. 如何使用Hive集成Solr?

    (一)Hive+Solr简介 Hive作为Hadoop生态系统里面离线的数据仓库,可以非常方便的使用SQL的方式来离线分析海量的历史数据,并根据分析的结果,来干一些其他的事情,如报表统计查询等. So ...

  8. 2019-8-16-调试时限制程序使用-CPU-核心数模拟低端设备

    title author date CreateTime categories 调试时限制程序使用 CPU 核心数模拟低端设备 lindexi 2019-08-16 16:11:32 +0800 20 ...

  9. c++11 Thread库写多线程程序

    一个简单的使用线程的Demo c++11提供了一个新的头文件<thread>提供了对线程函数的支持的声明(其他数据保护相关的声明放在其他的头文件中,暂时先从thread头文件入手吧),写一 ...

  10. leetcode-157周赛-5215黄金矿工

    题目描述: 方法一:dfs class Solution: def getMaximumGold(self, grid: List[List[int]]) -> int: maxx = 0 R, ...