2024年09月29日
程设计科 / 算法与数据结构
2707字
阅读量:Loading

二叉搜索树的插入、查找、删除等操作的效率与树高成正比,因此在创建二叉搜索树时要尽可能地通过调平衡压缩树高。平衡树有很多种,例如AVL树、Treap、伸展树(Splay)、SBT、红黑树等。

Treap简介

特点与作用

Treap,即Tree+Heap,又叫做树堆,它同时满足了二叉搜索树和堆两种性质。二叉搜索树满足中序有序性,输入的序列不同,创建的二叉搜索树也不同,在最坏的情况下(比如只有左子树或者只有右子树),会退化为线性。

若一个二叉搜索树插入的节点顺序时随机的,则得到的二叉搜索树在大多情况下是平衡的,即使存在一些极端的情况但这种情况发生的概率很小,因此以随机顺序创建的二叉搜索树,其期望高度为。可以将输入的数据随机打乱,再创建二叉搜索树,但我们有时并不能事前得知所有待插入的节点,而Treap可以解决该问题。

基本结构

Treap是一种平衡二叉树,它给每个节点都附加了一个随机数,使其满足堆的性质,而节点的值又满足二叉搜索树的有序性,其基本操作的期望时间复杂度为相对于其他平衡二叉搜索树,Treap的特点是实现简单,而且可以基本实现随机平衡。

在Treap构建过程中,插入节点时会给每个节点都附加一个随机数作为优先级,该优先级满足堆的性质(最大堆或者最小堆均可,本文以最大堆为例,根的优先级大于左右子树的节点),数值满足二叉搜索树的性质(中序有序性,左子树大于根,右子树小于根)。

Treap操作

左旋和右旋

Treap需要两种操作:左旋和右旋。

  1. 右旋():节点右旋时,会携带自己的右子树,向右旋转到的右子树位置,的右子树被抛弃,此时右旋后左子树正好空闲,将的右子树放在的左子树位置,旋转后的树根为
  2. 左旋():节点左旋时,会携带自己的左子树,向左旋转到的左子树位置,的左子树被抛弃,此时左旋后右子树正好空闲,将的左子树放在的右子树位置,旋转后的树根为

所以,无论是左旋还是右旋,旋转后总有一棵子树被抛弃,一个指针空闲,正好配对。

右旋代码:

inline void zig(reg int &p)
{
reg int q=tree[p].lc;
tree[p].lc=tree[q].rc;
tree[q].rc=p;
tree[q].size=tree[p].size;
Update(p);
p=q;
return;
}

左旋代码:

inline void zag(reg int &p)
{
reg int q=tree[p].rc;
tree[p].rc=tree[q].lc;
tree[q].lc=p;
tree[q].size=tree[p].size;
Update(p);
p=q;
return;
}

左旋右旋后,节点所对应的子树大小也会发生变化,需要更新每个节点更新后所对应的子树个数

更新子树大小代码:

inline void Update(reg int p)
{
tree[p].size=tree[tree[p].lc].size+tree[tree[p].rc].size+tree[p].num;
return;
}

插入

Treap的插入操作与二叉搜索树一样,首先根据有序性找到插入的位置,然后创建节点插入该位置。其中每个点都会有一个随机值,该值为优先级,会自下向上检查这一棵树是否满足堆的性质,若不满足会进行左旋或者右旋的操作。

新建节点代码:

inline int New(reg int val)
{
tree[++cnt].val=val;
tree[cnt].pri=rand();
tree[cnt].num=tree[cnt].size=1;
tree[cnt].rc=tree[cnt].lc=0;
return cnt;
}

当我们要插入一个数时,从根节点出发,如果节点为空,就直接创建新节点,将元素作为根节点,并赋值一个随机数作为优先级,否则会进行下面三种判断:

  1. 如果的值小于当前节点的值,就在它的左子树递归插入。回溯的时候根据优先级进行左旋或者右旋。
  2. 如果的值大于当前节点的值,就在它的右子树递归插入。回溯的时候根据优先级进行左旋或者右旋。
  3. 如果的值等于当前节点的值,就将当前节点的的值加一,在这里表示相同元素的数量。如果只要保留不重复的元素就什么也不做。

插入代码:

inline void Insert(reg int &p,reg int val)
{
if(!p)
{
p=New(val);
return;
}
tree[p].size++;
if(val==tree[p].val)
{
tree[p].num++;
return;
}
else if(val<tree[p].val)
{
Insert(tree[p].lc,val);
if(tree[p].pri<tree[tree[p].lc].pri)
zig(p);
}
else
{
Insert(tree[p].rc,val);
if(tree[p].pri<tree[tree[p].rc].pri)
zag(p);
}
return;
}

删除

首先找到删除的节点,将该节点向优先级大的子节点旋转,一直旋转到叶子,最后删除叶子。

具体来说,我们要删除一个元素,首先从根节点开始,会出现下面的三种情况:

  1. 如果的值小于当前节点的值,则在的左子树中递归删除。
  2. 如果的值大于当前节点的值,则在的右子树中递归删除。
  3. 如果的值等与当前节点的值,则:
    1. 若当前节点只有左子树或者是右子树,就将当前节点赋值为其儿子的状态,。
    2. 若当前节点左儿子的优先级大于右儿子的优先级,则左旋。继续在右子树中递归删除。
    3. 若当前节点左儿子的优先级小于右儿子的优先级,则右旋。继续在左子树中递归删除。

代码:

inline void Delete(reg int &p,reg int val)
{
if(!p)
return;
tree[p].size--;
if(val==tree[p].val)
{
if(tree[p].num>1)
{
tree[p].num--;
return;
}
if(!tree[p].lc||!tree[p].rc)
p=tree[p].lc+tree[p].rc;
else if(tree[tree[p].lc].pri>tree[tree[p].rc].pri)
{
zig(p);
Delete(tree[p].rc,val);
}
else
{
zag(p);
Delete(tree[p].lc,val);
}
return;
}
if(val<tree[p].val)
Delete(tree[p].lc,val);
else
Delete(tree[p].rc,val);
return;
}

前驱与后继

首先理解什么是前驱、后继:

  • 前驱结点:节点值小于该节点值并且值最大的节点

  • 后继节点:节点值大于该节点值并且值最小的节点

在Treap中求一个节点的前驱时,首先从树根开始,若当前节点的值小于,则用变量暂存该节点的值,在当前节点的右子树中寻找,否则在当前节点的左子树中寻找,直到当前节点为空,范围,即为的前驱。

在Treap中求一个节点的后继时,首先从树根开始,若当前节点的值大于,则用变量暂存该节点的值,在当前节点的左子树中寻找,否则在当前节点的右子树中寻找,直到当前节点为空,返回,即为的后继。

求前驱代码:

inline int GetPre(reg int val)
{
reg int p=rt;
reg int res=0;
while(p)
{
if(tree[p].val<val)
{
res=tree[p].val;
p=tree[p].rc;
}
else
p=tree[p].lc;
}
return res;
}

求后继代码:

inline int GetNext(reg int val)
{
reg int p=rt;
reg int res=0;
while(p)
{
if(tree[p].val>val)
{
res=tree[p].val;
p=tree[p].lc;
}
else
p=tree[p].rc;
}
return res;
}

根据查找排名

从树根出发,如果当前要查找的值大于当前节点的值,就向右递归搜索,将答案加上当前节点左儿子的子树大小与当前节点的元素个数。如果当前要查找的值小于当前节点的值,就向左递归搜索。如果两个值相等,就返回当前节点的左儿子子树大小

代码:

inline int GetRankByVal(reg int p,reg int val)
{
if(!p)
return 1;
if(tree[p].val==val)
return tree[tree[p].lc].size+1;
if(val<tree[p].val)
return GetRankByVal(tree[p].lc,val);
else
return GetRankByVal(tree[p].rc,val)+tree[tree[p].lc].size+tree[p].num;
}

根据排名查找

从树根出发,如果当前要查找的排名小于当前节点左子树大小,就向左子树递归搜索。如果当前要查找的排名小于等于当前节点p的左子树的大小与当前节点的元素数量之和,就返回当前节点的值。如果当前要查找的排名大于当前节点的左子树大小与当前节点的元素数量之和,就向右子树递归搜索,此时搜索的值要减去当前节点的左子树大小和当前节点的元素数量和。

代码:

inline int GetValByRank(reg int p,reg int rank)
{
if(!p)
return 0;
if(tree[tree[p].lc].size>=rank)
return GetValByRank(tree[p].lc,rank);
if(tree[tree[p].lc].size+tree[p].num>=rank)
return tree[p].val;
return GetValByRank(tree[p].rc,rank-tree[tree[p].lc].size-tree[p].num);
}

代码

来源:洛谷:P3369 【模板】普通平衡树洛谷:P6136 【模板】普通平衡树(数据加强版)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
using namespace std;
#define LL long long
#define uLL unsigned long long
#define reg register
#define PI acos(-1.0)
#define pb(x) push_back(x)
#define mp(a,b) make_pair(a,b)
#define fi first
#define se second
#define pr(x) cerr<<#x<<"="<<(x)<<endl
#define pri(x,lo) {cerr<<#x<<"={";for (int ol=0;ol<=lo;ol++)cerr<<x[ol]<<",";cerr<<"}"<<endl;}
#define inf 100000000
#define N 1000
#define M 4000001
template<class T>inline void read(T &x)
{
x=0;register char c=getchar();register bool f=0;
while(!isdigit(c))f^=c=='-',c=getchar();
while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(f)x=-x;
}
template<class T>inline void print(T x)
{
if(x<0)putchar('-'),x=-x;
if(x>9)print(x/10);
putchar('0'+x%10);
}
struct Node
{
int lc,rc,val,pri,num,size;
}tree[M];
int cnt,rt,n,m,Last,ans;
inline int New(reg int val)
{
tree[++cnt].val=val;
tree[cnt].pri=rand();
tree[cnt].num=tree[cnt].size=1;
tree[cnt].rc=tree[cnt].lc=0;
return cnt;
}
inline void Update(reg int p)
{
tree[p].size=tree[tree[p].lc].size+tree[tree[p].rc].size+tree[p].num;
return;
}
inline void zig(reg int &p)
{
reg int q=tree[p].lc;
tree[p].lc=tree[q].rc;
tree[q].rc=p;
tree[q].size=tree[p].size;
Update(p);
p=q;
return;
}
inline void zag(reg int &p)
{
reg int q=tree[p].rc;
tree[p].rc=tree[q].lc;
tree[q].lc=p;
tree[q].size=tree[p].size;
Update(p);
p=q;
return;
}
inline void Insert(reg int &p,reg int val)
{
if(!p)
{
p=New(val);
return;
}
tree[p].size++;
if(val==tree[p].val)
{
tree[p].num++;
return;
}
else if(val<tree[p].val)
{
Insert(tree[p].lc,val);
if(tree[p].pri<tree[tree[p].lc].pri)
zig(p);
}
else
{
Insert(tree[p].rc,val);
if(tree[p].pri<tree[tree[p].rc].pri)
zag(p);
}
return;
}
inline void Delete(reg int &p,reg int val)
{
if(!p)
return;
tree[p].size--;
if(val==tree[p].val)
{
if(tree[p].num>1)
{
tree[p].num--;
return;
}
if(!tree[p].lc||!tree[p].rc)
p=tree[p].lc+tree[p].rc;
else if(tree[tree[p].lc].pri>tree[tree[p].rc].pri)
{
zig(p);
Delete(tree[p].rc,val);
}
else
{
zag(p);
Delete(tree[p].lc,val);
}
return;
}
if(val<tree[p].val)
Delete(tree[p].lc,val);
else
Delete(tree[p].rc,val);
return;
}
inline int GetPre(reg int val)
{
reg int p=rt;
reg int res=0;
while(p)
{
if(tree[p].val<val)
{
res=tree[p].val;
p=tree[p].rc;
}
else
p=tree[p].lc;
}
return res;
}
inline int GetNext(reg int val)
{
reg int p=rt;
reg int res=0;
while(p)
{
if(tree[p].val>val)
{
res=tree[p].val;
p=tree[p].lc;
}
else
p=tree[p].rc;
}
return res;
}
inline int GetRankByVal(reg int p,reg int val)
{
if(!p)
return 1;
if(tree[p].val==val)
return tree[tree[p].lc].size+1;
if(val<tree[p].val)
return GetRankByVal(tree[p].lc,val);
else
return GetRankByVal(tree[p].rc,val)+tree[tree[p].lc].size+tree[p].num;
}
inline int GetValByRank(reg int p,reg int rank)
{
if(!p)
return 0;
if(tree[tree[p].lc].size>=rank)
return GetValByRank(tree[p].lc,rank);
if(tree[tree[p].lc].size+tree[p].num>=rank)
return tree[p].val;
return GetValByRank(tree[p].rc,rank-tree[tree[p].lc].size-tree[p].num);
}
int main()
{
srand(time(NULL));
read(n),read(m);
for(int i=1;i<=n;i++)
{
reg int a;
read(a);
Insert(rt,a);
}
for(reg int i=1;i<=m;i++)
{
reg int opt,x;
read(opt),read(x);
x^=Last;
if(opt==1) Insert(rt,x);
else if(opt==2) Delete(rt,x);
else if(opt==3) {Last=GetRankByVal(rt,x);ans^=Last;}
else if(opt==4) {Last=GetValByRank(rt,x);ans^=Last;}
else if(opt==5) {Last=GetPre(x);ans^=Last;}
else if(opt==6) {Last=GetNext(x);ans^=Last;}
}
printf("%d",ans);
return 0;
}
作者信息:老官童鞋gogoho
发表于:2024年09月29日