平衡树之treap

    xiaoxiao2025-05-18  13

    之前的博客中简单地阐述了二叉查找树的概念及简单应用,并介绍了平衡树。接下来我们便开始研究平衡树中实现较为简单的一种——treap的具体写法。

    一、treap简介

    介绍treap之前,先允许我来介绍一下笛卡尔树。这种树有两个值,如果只看第一个值(以下称为第一关键字),其满足二叉查找树的性质,而只看第二个值(以下称为第二关键字),其满足堆的性质(即某节点所有子树值比其大或小)。

    顾名思义,treap=tree+heap。因此,treap便是一种特殊的笛卡尔树。treap给原二叉查找树每个节点随机赋予第二关键字,并且要求原树满足笛卡尔树的性质。虽然treap的随机性使其可能会出现极端情况,但在大多数情况下,其查找的时间复杂度是接近O(logn)的。这一点比普通的二叉查找树好了许多。

    二、treap操作

    1、旋转

    要使第二关键字满足堆的条件并非易事。假设现在有一颗建好的二叉排序树,要使其在第一关键字满足条件的情况下,调整其第二关键字,我们就需要使用到平衡树的独家技巧——旋转。

    所有的平衡树都要用到旋转操作,treap的旋转操作相对简单,仅分为两种——左旋与右旋。

    如上图,从左至右为右旋,从右至左为左旋(分不清的可类比汽车方向盘)。

    旋转操作不改变第一关键字满足的二叉查找树条件,也就是说旋转前后二叉查找树的中序遍历始终不变,保持有序性。与此同时它也对第二关键字的顺序作出了修改。

    下面以右旋操作为例讲解旋转的操作顺序:

    可以看到,右旋之前Y为X的父亲节点。A、B分别为X的左右子树,C为Y的右子树(图中子树均以点代替),D为Y的父亲节点。

    (1)将B变为Y的左子树,B的父亲节点变成Y

    (2)将Y变为X的右子树,Y的父亲节点变成X

    (3)X填补Y的空位变为D的子树(左右方向与原Y一致),X的父亲节点变成D

    需要注意的是,因为treap的操作基本为递归操作,所以无需记录父节点。在旋转时第(3)步可直接使用传址完成。

    附上旋转操作的具体代码:

    struct node { int ch[2];//ch[0]为左子节点,ch[1]为右子节点 int val,w;//val为第一关键字,w为第二关键字 }t[maxn];//定义treap节点 void rot(int &f,int c)//c=1为左旋,c=0为右旋 { int son=t[f].ch[c]; t[f].ch[c]=t[son].ch[c^1];//步骤(1) t[son].ch[c^1]=f;//步骤(2) f=son;//步骤(3) }

    2、插入

    treap的插入操作与二叉查找树大同小异。唯一一点需要注意的是:treap在插入完毕后需要递归进行旋转操作使其第二关键字满足堆的性质。

    附上插入操作具体代码:

    void insert(int num,int& h) { if(!h) { t[++cnt].val=num; t[cnt].w=rand(); root=cnt; return; }//如之前没有节点则该节点为根节点 if((num>t[h].val&&!t[h].ch[1])||(num<=t[h].val&&!t[h].ch[0]))//子节点为空 { t[++cnt].val=num;//第一关键字 t[cnt].w=rand();//随机赋予第二关键字 t[h].ch[num>t[h].val]=cnt;//设置为该节点的子节点 if(t[cnt].w<t[h].w)rot(h,num>t[h].val);//不满足性质则旋转 } else { insert(num,t[h].ch[num>t[h].val]);//继续查找 if(t[t[h].ch[num>t[h].val]].w<t[h].w)rot(h,num>t[h].val);//不满足性质则旋转 } }

    3、删除

    treap的删除操作也是递归进行的。其主要步骤如下:

    (1)根据删除值找到要删除的节点

    (2)在保持第二关键字满足条件的情况下将其旋转直至其左右子树中至少有一个为空

    (3)此时删除该节点将不会对整棵树造成影响,将其删去,将其子树直接接上其父亲节点

    附上删除操作具体代码:

    void del(int num,int& h) { if(t[h].val==num)//查找到了 { if(!t[h].ch[0]||!t[h].ch[1])h=t[h].ch[0]+t[h].ch[1];//左右子树有一个为空,直接删去 else { rot(h,t[h].ch[1]<t[h].ch[0]);//将左右子树中较小的一个转上来,才能保持堆的性质 del(num,h);//递归操作 } return; } //没找到,继续查找 if(num<t[h].val)del(num,t[h].ch[0]); else del(num,t[h].ch[1]); }

    4、查找

    treap的查找操作与二叉查找树相同,在此略去。

    三、总结

    treap可以说是平衡树的入门,其维护条件简单易懂,容易掌握,虽然有一点不确定因素,但在它强大的功能面前可以忽略不计。大家可以在做题时尝试使用此方法。

    转载请注明原文地址: https://ju.6miu.com/read-1299003.html
    最新回复(0)