博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
题解 P3369 【【模板】普通平衡树】
阅读量:6812 次
发布时间:2019-06-26

本文共 4727 字,大约阅读时间需要 15 分钟。

在网上某篇神奇的教程和@codesonic 大佬的标程帮助下,我又肝完了Leafy Tree,跑过来写篇题解(好像以前写过一篇?)


什么是Leafy Tree?

Leafy Tree由两种节点组成:辅助节点与叶子节点。

叶子节点储存值,而辅助节点储存左右孩子中大的那个值。

注意:辅助节点必定有两个孩子。

操作如何实现?

拿插入操作举例:

一路向下递归,每次拿左子树最大值与插入值作比较,如果大就往左,如果小就往右。

到底了就插入叶子与辅助。

然后再回溯更新。

这时候就会出一个问题:这个算法很容易被数据卡。

解决方案是引入平衡因子,在适当的时候重建这颗树。

重构方法可以拍扁也可以旋转。

拍扁的方法就是中序遍历一遍然后重新建树(具体可以参考),旋转的一会儿会讲。


工具函数

这里是一些简单的重要的工具函数。

  1. 新建节点
inline void newNode(int &pos,int v){    pos=++cnt,size[pos]=1,val[pos]=v;}

cnt是总结点个数,size是子树大小,val是值(废话)。

  1. 复制节点
inline void copyNode(int x,int y){    size[x]=size[y],ls[x]=ls[y],rs[x]=rs[y],val[x]=val[y];}

没什么好说的

  1. 合并节点
void merge(int l,int r){    size[++cnt]=size[l]+size[r],val[cnt]=val[r],ls[cnt]=l,rs[cnt]=r;}
  1. 旋转
void rotate(int pos,bool flag){    if(flag){        merge(ls[pos],ls[rs[pos]]);        ls[pos]=cnt,rs[pos]=rs[rs[pos]];    }else{        merge(rs[ls[pos]],rs[pos]);        rs[pos]=cnt,ls[pos]=ls[ls[pos]];    }}

这是重建依赖的旋转函数,左旋右旋看flag。

具体流程没什么好讲的,可以参考splay的左旋与右旋。

  1. 重建
void maintain(int pos){    if(size[ls[pos]]>size[rs[pos]]*alpha)rotate(pos,0);    else if(size[rs[pos]]>size[ls[pos]]*alpha)rotate(pos,1);    if(size[ls[pos]]>size[rs[pos]]*alpha)rotate(ls[pos],1),rotate(pos,0);    else if(size[rs[pos]]>size[ls[pos]]*alpha)rotate(rs[pos],0),rotate(pos,1);}

这是重建函数,平衡因子就这题而言取4应该是最快的。


插入操作

void insert(int pos,int v){    if(size[pos]==1){        newNode(ls[pos],min(v,val[pos]));        newNode(rs[pos],max(v,val[pos]));        pushup(pos);        return;    }    if(v>val[ls[pos]])insert(rs[pos],v);    else insert(ls[pos],v);    pushup(pos);    maintain(pos);}

思路就是之前讲的一路向下递归。

当子树大小为1的时候(到头了)就在底下新建两个节点,一个叶子一个辅助,然后回溯更新。

如果没到头的话就继续递归,然后递归完了就维护一下左右子树的平衡,看看需不需要重建。


删除操作

void erase(int pos,int v){    if(size[pos]==1){        if(ls[father]==pos)copyNode(father,rs[father]);        else copyNode(father,ls[father]);        return;    }    father=pos;    if(v>val[ls[pos]])erase(rs[pos],v);    else erase(ls[pos],v);    pushup(pos);    maintain(pos);}

删除操作的思路和插入操作一样,一路向下。

需要说明的是father是我们记录的父亲(也可以不这样而是通过传参解决)。


排名查询&排名对应数查询

int kth(int pos,int v){    if(size[pos]==v)return val[pos];    if(v>size[ls[pos]])return kth(rs[pos],v-size[ls[pos]]);    return kth(ls[pos],v);}int rank(int pos,int v){    if(size[pos]==1)return 1;    if(v>val[ls[pos]])return rank(rs[pos],v)+size[ls[pos]];    return rank(ls[pos],v);}

这两个。。。写什么平衡树都会用到肯定大家都会。


然后好像就结束了(Leafy Tree本来码量就不高)

不开O2不加读优大概是311ms左右(竟然还没有我替罪羊树快),应该是写丑了吧。。。

代码如下:

#include
#include
#include
using namespace std;const int N=100100;const int alpha=4;int n,cnt,father,root;int val[N<<2],size[N<<2],ls[N<<2],rs[N<<2];inline void newNode(int &pos,int v){ pos=++cnt,size[pos]=1,val[pos]=v;}inline void copyNode(int x,int y){ size[x]=size[y],ls[x]=ls[y],rs[x]=rs[y],val[x]=val[y];}void merge(int l,int r){ size[++cnt]=size[l]+size[r],val[cnt]=val[r],ls[cnt]=l,rs[cnt]=r;}void rotate(int pos,bool flag){ if(flag){ merge(ls[pos],ls[rs[pos]]); ls[pos]=cnt,rs[pos]=rs[rs[pos]]; }else{ merge(rs[ls[pos]],rs[pos]); rs[pos]=cnt,ls[pos]=ls[ls[pos]]; }}void maintain(int pos){ if(size[ls[pos]]>size[rs[pos]]*alpha)rotate(pos,0); else if(size[rs[pos]]>size[ls[pos]]*alpha)rotate(pos,1); if(size[ls[pos]]>size[rs[pos]]*alpha)rotate(ls[pos],1),rotate(pos,0); else if(size[rs[pos]]>size[ls[pos]]*alpha)rotate(rs[pos],0),rotate(pos,1);}void pushup(int pos){ if(!size[ls[pos]])return; size[pos]=size[ls[pos]]+size[rs[pos]]; val[pos]=val[rs[pos]];}void insert(int pos,int v){ if(size[pos]==1){ newNode(ls[pos],min(v,val[pos])); newNode(rs[pos],max(v,val[pos])); pushup(pos); return; } if(v>val[ls[pos]])insert(rs[pos],v); else insert(ls[pos],v); pushup(pos); maintain(pos);}void erase(int pos,int v){ if(size[pos]==1){ if(ls[father]==pos)copyNode(father,rs[father]); else copyNode(father,ls[father]); return; } father=pos; if(v>val[ls[pos]])erase(rs[pos],v); else erase(ls[pos],v); pushup(pos); maintain(pos);}int kth(int pos,int v){ if(size[pos]==v)return val[pos]; if(v>size[ls[pos]])return kth(rs[pos],v-size[ls[pos]]); return kth(ls[pos],v);}int rank(int pos,int v){ if(size[pos]==1)return 1; if(v>val[ls[pos]])return rank(rs[pos],v)+size[ls[pos]]; return rank(ls[pos],v);}int main(){ scanf("%d",&n); newNode(root,2147483647); while(n--){ int s,a; scanf("%d%d",&s,&a); if(s==1)insert(root,a); if(s==2)erase(root,a); if(s==3)printf("%d\n",rank(root,a)); if(s==4)printf("%d\n",kth(root,a)); if(s==5)printf("%d\n",kth(root,rank(root,a)-1)); if(s==6)printf("%d\n",kth(root,rank(root,a+1))); }}

转载于:https://www.cnblogs.com/ilverene/p/9850564.html

你可能感兴趣的文章
laravel常用拓展库
查看>>
sourcetree向github推送代码提示密码错误
查看>>
Webpack webpack+gulp实现自动构建部署
查看>>
WordPress 插件机制的简单用法和原理(Hook 钩子)
查看>>
CSS文本超出2行就隐藏并且显示省略号
查看>>
什么是多线程,锁,死锁,怎么避免死锁(转)
查看>>
左外联接测试
查看>>
11-linux基础八-正则表达式
查看>>
Nacos环境搭建
查看>>
全栈工程师就是一棵歪脖子树
查看>>
转:Java中abstract和interface的区别
查看>>
app.jsNodejs启动测试服务
查看>>
管理员取得所有权限.reg
查看>>
杭电1087
查看>>
Js跑马灯效果 && 在Vue中使用
查看>>
2017-2018-2 20179209《网络攻防》第二周作业
查看>>
ORA-12519: TNS:no appropriate service handler found 解决
查看>>
序列化对象
查看>>
javascript模式 (3)——工厂模式和装饰模式
查看>>
u-boot-2012.04.01移植笔记——支持NAND启动
查看>>