一般我们考虑的树都是无向树,即联通非循环图(也就是联通无向图中没有环),如果为非联通非循环图,我们称之为森林。
而在树形结构中,二叉树具有很多很好的性质,也有很多有用的应用,所以一般都以二叉树为基础进行讨论。
二叉搜索树
一种中序遍历为原序列的树形结构,对于每一个子树,左儿子比自己小,右儿子比自己大,如果必要的话,可以给每个结点加权,记录每个数出现次数。
一个二叉搜索树:
基于这种结构,二叉搜索树支持以下功能:
- 插入:从根节点开始不断比较,小往左,大往右,最后放到合适位置;
- 删除:对于叶子结点直接删除;对于有一个儿子的结点,删除后把儿子放到该结点位置;否则,寻找右子树中最小的值(即以右儿子为根,不断往左寻找)进行替换;
- 查找:这个就很简单了;
- 查询排名:带有 size 的深度搜索;
- 前继后继:跟查找差不多;
堆
堆是一种完全二叉树,分为大根堆和小根堆,大根堆即所有子树的根节点都比儿子要大,小根堆同理。
一个大根堆:
基于这种数据结构,堆支持以下功能:
- 查询最值:即堆顶;
- 插入:我们将新的值放在最底层最右边,然后不停向上更新,直到新插入的值小于父亲;
- 弹出:将最后的值放到堆顶,然后不停向下更新更大的儿子,直到该值大于所有儿子;
Treap
尽管对于随机数来说二叉搜索树非常高效,但是在一些特殊情况下,它可能会退化成一条较长的链。所以,随机平衡二叉搜索树——Treap,用于解决平衡问题。Treap=Tree+Heap,Treap结合了二叉搜索树和堆的性质,为每一个结点分配一个随机数,在满足二叉搜索树的性质的前提下,对随机数满足堆的性质。这样由于随机数是随机的,所以二叉搜索树是平衡的。
Treap的核心操作是旋转,从以下例子可以看出,不管是左旋还是右旋,都不会影响Treap的二叉搜索树的性质:
我们可以看出,旋转的规律就是:把当前结点的反儿子(左旋右儿子,右旋左儿子)的正儿子(左旋左儿子,右旋右儿子)拿出来,然后把待旋转的两个结点对应移一下,再把拿出来的结点放到原来结点的反方向。
由于考虑到Treap要满足堆的性质,所以我们在插入、删除结点的时候,还要考虑满足大根堆或者小根堆,这通过旋转来实现。插入和删除的操作跟普通二叉搜索树差不多,只是对于插入操作,某个结点插入了新的儿子时,要对随机数数组 rd 进行 rotate(旋转)操作,以满足堆性质;对于删除操作,可以将待删除的结点 rotate 到叶子结点处,这样方便处理,中途也要考虑满足堆的性质。
其它的功能,凡是二叉搜索树能满足和实现的,Treap也能同样地实现。
Treap模板如下(普通平衡树):
// t为每个结点的值;num为每个值出现次数;ch为儿子指针;rd为结点随机数;siz为结点子树大小
// root记录树根,tot记录结点个数
struct treap
{
int tot,root;
int *t,*num,(*ch)[2],*rd,*siz;
treap(int maxn)
{
t=new int[maxn](); num=new int[maxn]();
rd=new int[maxn](); siz=new int[maxn]();
ch=new int[maxn][2](); root=tot=0;
}
void push_up(int k) {
siz[k]=siz[ch[k][0]]+siz[ch[k][1]]+num[k];}
void rotate(int &k,int d)
{
int x=ch[k][d^1];
ch[k][d^1]=ch[x][d]; ch[x][d]=k;
push_up(k); push_up(x);
k=x;
}
void insert(int &k,int x)
{
if(!k) {
k=++tot; t[k]=x; num[k]=siz[k]=1; rd[k]=rand(); return;}
else if(t[k]==x) {
num[k]++; siz[k]++; return;}
else
{
int d=x>t[k];
insert(ch[k][d],x);
if(rd[k]<rd[ch[k][d]]) rotate(k,d^1);
push_up(k);
}
}
void insert(int x) {
insert(root,x);}
void del(int &k,int x)
{
if(!k) return;
if(x!=t[k]) del(ch[k][x>t[k]],x);
else
{
if(!(ch[k][0]|ch[k][1])) {
num[k]--; siz[k]--; if(!num[k]) k=0;}
else if(!ch[k][0] && ch[k][1]) {
rotate(k,0); del(ch[k][0],x);}
else if(ch[k][0] && !ch[k][1]) {
rotate(k,1); del(ch[k][1],x);}
else {
int d=rd[ch[k][0]]>rd[ch[k][1]]; rotate(k,d); del(ch[k][d],x);}
}
push_up(k);
}
void del(int x) {
del(root,x);}
int ranking(int k,int x)
{
if(!k) return 0;
if(x==t[k]) return siz[ch[k][0]]+1;
if(x<t[k]) return ranking(ch[k][0],x);
return ranking(ch[k][1],x)+siz[ch[k][0]]+num

本文深入探讨了树形数据结构的多种类型及其关键特性,包括二叉搜索树、堆、Treap、Splay和LCT等。每种结构都详细讲解了其支持的操作、应用场景及模板代码,为读者提供了全面的树形数据结构知识。
最低0.47元/天 解锁文章
832

被折叠的 条评论
为什么被折叠?



