伸展树 学习笔记

听说 lemon 不太资瓷指针和动态开内存,所以学了一下数组非递归版的伸展树,简单记一下。(没有仔细写,只是为了方便复习,所以并不是一篇入门教程)

以模版题 普通平衡树 为例。
维护信息:

int rt,ch[maxn][2],fa[maxn],v[maxn],c[maxn],sz[maxn],tot=0;
inline void mt(int x) {sz[x]=sz[ch[x][0]]+c[x]+sz[ch[x][1]];}

核心操作

旋转操作:将下标为 x x x 的节点旋转到其父节点的位置。

inline void rotate(int x) {
	int f=fa[x],ff=fa[f],k=ch[f][1]==x;
	ch[ff][ch[ff][1]==f]=x;fa[x]=ff;
	ch[f][k]=ch[x][k^1];fa[ch[x][k^1]]=f;
	ch[x][k^1]=f;fa[f]=x;mt(f);mt(x);
}

步骤:

  • 记录父节点,祖先节点以及当前节点是父节点的哪个儿子。
  • 将祖父节点与 x x x 相连。
  • x x x 的父节点与 x x x 的子节点相连。
  • x x x 与父节点相连。
  • 先维护父节点,再维护 x x x。(自下而上)

伸展操作:将 x x x 伸展到 g g g 的子节点。

inline void splay(int x,int g) {
	while(fa[x]!=g) {
		int f=fa[x],ff=fa[f];
		if(ff!=g) (ch[f][0]==x)^(ch[ff][0]==f)?rotate(x):rotate(f);
		rotate(x);
	}
	if(!g) rt=x;
}

步骤:

  • 持续循环直到 x x x 的父节点为 g g g
  • 记录 x x x 的父节点和祖父节点。
  • 如果祖父节点不为 g g g(父节点不为目标节点),则需要旋转两次,此时若不在同一直线上则旋转 x x x,否则旋转其父节点。
  • 无论如何都要旋转一次 x x x
  • 如果 g g g 0 0 0,更新根节点。

基本操作

插入节点:插入值为 x x x 的节点。

inline void insert(int x) {
	int u=rt,f=0;
	while(u&&v[u]!=x) {f=u;u=ch[u][x>v[u]];}
	if(u) ++c[u];
	else {
		u=++tot;ch[u][0]=ch[u][1]=0;fa[u]=f;v[u]=x;c[u]=sz[u]=1;
		if(f) ch[f][x>v[f]]=u;
	}
	splay(u,0);
}

步骤:

  • 定义两个变量分别保存当前节点与其父节点。
  • 一直循环向子节点查找直到找到或到达空节点。
  • 如果节点不为空,则直接增加上面的个数。
  • 否则新建一个节点,同时一直维护的父节点更新其子节点
  • 将新插入的节点伸展到根节点。

查找节点:查找值为 x x x 的节点,若未找到则返回一个其附近的节点,将找到的节点伸展到根。

inline void find(int x) {
	int u=rt;if(!u) return;
	while(ch[u][x>v[u]]&&x!=v[u]) u=ch[u][x>v[u]];
	splay(u,0);
}

步骤:

  • 记录当前节点,判空直接返回。
  • 循环找出最后一个不为空的节点,若中途找到跳出循环。
  • 将找到的节点伸展到根节点。

查找区间第 k k k 大。

inline int kth(int k) {
	int u=rt;if(sz[u]<k) return 0;
	while(1) {
		int lc=ch[u][0];
		if(k<=sz[lc]) u=lc;
		else if(k<=sz[lc]+c[u]) return v[u];
		else {k-=sz[lc]+c[u];u=ch[u][1];}
	}
}

判空一下后面比较简单不说了。

查找前驱和后继。

inline int pre(int x) {
	find(x);int u=rt;if(v[u]<x) return u;
	u=ch[u][0];while(ch[u][1]) u=ch[u][1];
	return u;
}
inline int suf(int x) {
	find(x);int u=rt;if(v[u]>x) return u;
	u=ch[u][1];while(ch[u][0]) u=ch[u][0];
	return u;
}

步骤:

  • find 函数查找节点,若在其前/后直接返回,否则找相反子树最靠近当前位置的节点。

删除节点:删除值为 x x x 的节点。

inline void remove(int x) {
	int lst=pre(x),nxt=suf(x);splay(lst,0);splay(nxt,lst);
	int del=ch[nxt][0];
	if(c[del]>1) {--c[del];splay(del,0);}
	else ch[nxt][0]=0;
}

步骤:

  • 查找前驱后继后伸展使得满足条件的节点被放置在一棵子树中。
  • 如果节点中的个数大于 1 1 1 直接减一,然后伸展到根节点。
  • 否则从其父节点删除对它的索引。
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值