[模板]Treap

前言

今天学习了平衡树中的Treap,打了一道模板题不会做了,写个博客总结一下吧。

概念

二叉查找树

二叉查找树(Binary Search Tree)是基于插入思想的一种在线的排序数据结构。它又叫二叉搜索树(Binary Search Tree)、二叉排序树(Binary Sort Tree),简称BST。

这种数据结构的基本思想是在二叉树的基础上,规定一定的顺序,使数据可以有序地存储。二叉查找树运用了像二分查找一样的查找方式,并且基于链式结构存储,从而实现了高效的查找效率和完美的插入时间。

定义

二叉查找树(Binary Search Tree)或者是一棵空树,或者是具有下列性质的二叉树:

  1. 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  2. 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  3. 它的左、右子树也分别为二叉查找树。

可以看出,二叉查找树的插入和查询时间复杂度应该为查找点在树中的深度,当形成的树为一条链时,操作的效率会大大降低。那么,就引入了平衡树的概念。

平衡树

随机的进行 n ^ 2 (n >= 1000) 次插入和删除之后,二叉查找树会趋向于向左偏沉。为什么会出现这种情况,原因在于删除时,我们总是选择将待删除节点的后继代替它本身。这样就会造成总是右边的节点数目减少,以至于树向左偏沉。已经被证明,随机插入或删除n ^ 2次以后,树的期望深度为Θ(n ^ (1 / 2))。对待随机的数据二叉查找树已经做得很不错了,但是如果有像这样6,5,4,3,2,1有序的数据插入树中时,会有什么后果出现?如图所示。二叉查找树退化成为了一条链。这个时候,无论是查找、插入还是删除,都退化成了O(N)的时间。们需要使二叉查找树变得尽量平衡,才能保证各种操作在O(logN)的期望时间内完成,于是各种自动平衡二叉查找树(Self-Balancing Binary Search Tree)因而诞生。

随机二叉查找树-Treap

Treap = Tree + Heap
 Treap是一种平衡树。Treap发音为[ ]。这个单词的构造选取了Tree(树)的前两个字符和Heap(堆)的后三个字符,Treap = Tree + Heap。顾名思义,Treap把BST和Heap结合了起来。它和BST一样满足许多优美的性质,而引入堆目的就是为了维护平衡。
 Treap在BST的基础上,添加了一个修正值。在满足BST性质的基础上,Treap节点的修正值还满足最小堆性质 。最小堆性质可以被描述为每个子树根节点都小于等于其子节点。

定义

Treap可以定义为有以下性质的二叉树:

  1. 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值,而且它的根节点的修正值小于等于左子树根节点的修正值;
  2. 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值,而且它的根节点的修正值小于等于右子树根节点的修正值;
  3. 它的左、右子树也分别为Treap。 修正值是节点在插入到Treap中时随机生成的一个值,它与节点的值无关。
Treap维护平衡的原理

 我们发现,BST会遇到不平衡的原因是因为有序的数据会使查找的路径退化成链,而随机的数据使BST退化的概率是非常小的。
 在Treap中,修正值的引入恰恰是使树的结构不仅仅取决于节点的值,还取决于修正值的值。然而修正值的值是随机生成的,出现有序的随机序列是小概率事件,所以Treap的结构是趋向于随机平衡的。

如何构建Treap

为了使Treap中的节点同时满足BST性质和小根堆性质,不可避免地要对其结构进行调整,调整方式被称为旋转(Rotate)。在维护Treap的过程中,只有两种旋转 ,分别是左旋转(简称左旋)和右旋转(简称右旋)。旋转是相对于子树而言的,左旋和右旋的命名体现了旋转的一条性质:

旋转的性质.1

左旋一个子树,会把它的根节点旋转到根的左子树位置,同时根节点的右子节点成为子树的根;右旋一个子树,会把它的根节点旋转到根的右子树位置,同时根节点的左子节点成为子树的根。

旋转的性质.2

对子树旋转后,子树仍然满足BST性质。

插入操作

在Treap中插入元素,与在BST中插入方法相似。首先找到合适的插入位置,然后建立新的节点,存储元素。但是要注意建立新的节点的过程中,会随机地生成一个修正值,这个值可能会破坏堆序,因此我们要根据需要进行恰当的旋转。具体方法如下:

  1. 从根节点开始插入;
  2. 如果要插入的值小于等于当前节点的值,在当前节点的左子树中插入,插入后如果左子节点的修正值小于当前节点的修正值,对当前节点进行右旋;
  3. 如果要插入的值大于当前节点的值,在当前节点的右子树中插入,插入后如果右子节点的修正值小于当前节点的修正值,对当前节点进行左旋;
  4. 如果当前节点为空节点,在此建立新的节点,该节点的值为要插入的值,左右子树为空,插入成功。
删除操作

Treap的删除与普通二叉查找树不同。因为要维护堆序,比较好的方法是利用旋转的方法把要删除的结点旋转到叶结点位置,再做删除操作。

 情况1. 该节点为叶节点或链节点,则该节点是可以直接删除的节点。若该节点有非空子节点,用非空子节点代替该节点的,否则用空节点代替该节点,然后删除该节点。

 情况2. 该节点有两个非空子节点。我们的策略是通过旋转,使该节点变为可以直接删除的节点。如果该节点的左子节点的修正值小于右子节点的修正值,右旋该节点,使该节点降为右子树的根节点,然后访问右子树的根节点,继续讨论;反之,左旋该节点,使该节点降为左子树的根节点,然后访问左子树的根节点,继续讨论,直到变成可以直接删除的节点。

其他操作

略, gugugug

code
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define MAXN 100005
#define LL long long
#define Int_Max 2147483647
#define INF 0x3f3f3f3f
#define Int register int
struct node
{
    int Size, Key, l, r, Rank, Xiu;
}Point[MAXN];
int Num;
int Max(int x,int y)
{
    return x > y ? x : y;
}
int Min(int x,int y)
{
    return x < y ? x : y;
}
inline void read(int &x)
{
    x = 0;
    int f = 1;
    char s = getchar();
    while (s < '0' || s > '9')
    {
       if (s == '-')
  	   f = -1;
       s = getchar();
    }
    while (s >= '0' && s <= '9')
    {
       x = (x << 3) + (x << 1) + (s ^ 48);
       s = getchar();
    }
    x *= f;
}
int GetHash()
{
    static int Seed = 122520;
    Seed = (int)Seed * 520122ll % Int_Max;
    return Seed;
}
void Update(int x)
{
    Point[x].Size = Point[Point[x].l].Size + Point[Point[x].r].Size + Point[x].Rank;
}
void Lturn(int &x)
{
    int rson = Point[x].r;
    Point[x].r = Point[rson].l;
    Point[rson].l = x;
    Point[rson].Size = Point[x].Size;
    Update( x );
    x = rson;
}
void Rturn(int &x)
{
    int lson = Point[x].l;
    Point[x].l = Point[lson].r;
    Point[lson].r = x;
    Point[lson].Size = Point[x].Size;
    Update( x );
    x = lson;
}
void Insert(int &x,int val)
{
    if (! x)
    {
     	x = ++ Num;
     	Point[x].Size = Point[x].Rank = 1;
     	Point[x].Key = val;
     	Point[x].Xiu = GetHash();
     	return ;
    }
    Point[x].Size ++;
    if (Point[x].Key == val)
     	Point[x].Rank ++;
    else if (val > Point[x].Key)
    {
     	Insert(Point[x].r, val);
     	if (Point[Point[x].r].Xiu < Point[x].Xiu)
      	    Lturn( x );
    }
    else
    {
     	Insert(Point[x].l, val);
     	if (Point[Point[x].l].Xiu < Point[x].Xiu)
      	    Rturn( x );
     }
}
void Delete(int &x,int val)
{
    if (! x)
     	return ;
    if (Point[x].Key == val)
    {
     	if (Point[x].Rank > 1)
      	   Point[x].Rank --, Point[x].Size --;
     	else
     	{
     	   if (! Point[x].l || ! Point[x].r)
       		x = Point[x].l + Point[x].r;
      	   else if (Point[Point[x].l].Xiu < Point[Point[x].r].Xiu)
       		Rturn( x ), Delete(x, val);
           else Lturn( x ), Delete(x, val);
        }
    }
    else if (val > Point[x].Key)
    	 Point[x].Size --, Delete(Point[x].r, val);
    else Point[x].Size --, Delete(Point[x].l, val);
}
int GetRank(int x,int val)
{
    if (! x)
     	return 0;
    if (Point[x].Key == val)
    	return Point[Point[x].l].Size + 1;
    if (val > Point[x].Key)
     	return Point[Point[x].l].Size + Point[x].Rank + GetRank(Point[x].r, val);
    else return GetRank(Point[x].l, val);
}
int GetXth(int x,int val)
{
    if (! x)
     	return 0;
    if (val <= Point[Point[x].l].Size)
     	return GetXth(Point[x].l, val);
    val -= Point[Point[x].l].Size;
    if (val <= Point[x].Rank)
     	return Point[x].Key;
    val -= Point[x].Rank;
    return GetXth(Point[x].r, val);
}
int FindHead(int x,int val)
{
    if (! x)
     	return -INF;
    if (Point[x].Key >= val)
    	 return FindHead(Point[x].l, val);
    else return Max(Point[x].Key, FindHead(Point[x].r, val));
}
int FindBack(int x,int val)
{
    if (! x)
     	return INF;
    if (Point[x].Key <= val)
    	return FindBack(Point[x].r, val);
    else return Min(Point[x].Key, FindBack(Point[x].l, val));
}
int Root;
int main()
{
    //srand(*new unsigned);
    int n;
    read( n );
    while (n --)
    {
     	int Or, x;
    	read( Or ); read( x );
     	switch ( Or )
        {
      	    case 1:
       		Insert(Root, x); break;
            case 2:
        	Delete(Root, x); break;
            case 3:
      		printf("%d\n", GetRank(Root, x)); break;
            case 4:
       		printf("%d\n", GetXth(Root, x)); break;
            case 5:
       		printf("%d\n", FindHead(Root, x)); break;
            case 6:
       		printf("%d\n", FindBack(Root, x)); break;
       }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值