对六种平衡树的研究与探索【全面】【更新中】

对平衡树的研究与探索

【摘要】

本文较全面地介绍了各种常用平衡树及其特点,复杂度和实用性。 

第一部分提出各种不同的二叉搜索树的性质、性能、实现方法、模板。第二部分主要介绍了可持久化treap。第三部分详细地对各种平衡树进行实战比较。

【关键字】 二叉搜索树 treap splay sbt rbt 替罪羊树 AVL

【正文】

一、引言

     平衡树在信息学竞赛中十分常见,作为较易实现各种功能以及自己自身的特有的代码短、时间优的特点,占据了信息学竞赛的至少30%的数据结构考点。平衡二叉树(Balanced Binary Tree)具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树(rbt)、AVL、替罪羊树、Treap、伸展树(spaly)平衡树的节点的公式如下 F(n)=F(n-1)+F(n-2)+1 这个类似于一个递归的数列,可以参考Fibonacci数列,1是根节点,F(n-1)是左子树的节点数量,F(n-2)是右子树的节点数量。

二、各类平衡树的基本介绍

平衡二叉树

一个长度为n的有序序列,从中查找一个指定的值,要花多少时间?
一个最简单的做法就是一个个去试。如果你运气好,第一个就碰上了;如果你运气不好,最后一个才是你要查的值,那就需要把n个值都检查一遍。时间复杂度O(n)。
当然你可能注意到了有序这个有用的性质,所以可以采用二分查找的方式,具体就不赘述了。时间复杂度O(log(n))。
但是如果要添加一个数据(及动态更新)怎么办?要保证序列的有序性,你必须要插入到适当的位置。这个位置同样可以通过二分查找在O(log(n))的时间中找出。
可是插入的过程呢?我们必须把后面的数据一个个顺次往后挪一格,而这需要O(n)的时间。 这也意味着删除的时间复杂度也就是O(n)。太慢了!无法满足大数据底线O(nlogn)左右的时间复杂度。所以我们需要快一点的方法(数据结构)。

treap

基本介绍
    树堆,在数据结构中也称Treap,是指有一个随机附加域满足堆的性质的二叉搜索树,其结构相当于以随机数据插入的二叉搜索树。其基本操作的期望时间复杂度为O(logn)。相对于其他的平衡二叉搜索树,Treap的特点是实现简单,且能基本实现随机平衡的结构。
    Treap=Tree+Heap
    Treap是一棵二叉排序树,它的左子树和右子树分别是一个Treap,和一般的二叉排序树不同的是,Treap纪录一个额外的数据,就是优先级。Treap在以关键码构成二叉排序树的同时,还满足堆的性质(在这里我们假设节点的优先级大于该节点的孩子的优先级)。但是这里要注意的是Treap和二叉堆有一点不同,就是二叉堆必须是完全二叉树,而Treap可以并不一定是。
补充
替代rand()

对于现在的treap,附加域往往还会选用另一种方法——选用以下代码

inline int random(){
    static int seed=703; //seed可以随便取
    return seed=int(seed*48271LL%2147483647);
}
    以上就可以取遍2147483647中每一个数,对于rand()的可能重复附加域的情况有效地排除了
可持久化

支持部分可持久化功能,详细信息见可持久化模块。

核心的步骤
旋转
    事实上,想要一棵树恰巧满足以上的条件并不容易。绝大多数情况下,我们都需要通过旋转的方法来调整树的形态,使得它满足以上的条件。 
    旋转分左旋和右旋两种,他们都不破坏二叉查找树的性质。如图所示:

这里写图片描述

插入、删除和选择第k小项
    插入和普通的二叉查找树差不多。但是要注意,插入有可能会破坏Treap的堆性质,所以要通过旋转来维护堆性质。下图就是一个例子:

这里写图片描述

    删除就相对容易很多了,由于Treap满足堆性质,只需要将待删除的节点旋转到叶子节点再删除就可以了。
操作及模板

以下代码来自黄学长(hzwer)

支持以下操作
1. 插入x数
2. 删除x数(若有多个相同的数,因只删除一个)
3. 查询x数的排名(若有多个相同的数,因输出最小的排名)
4. 查询排名为x的数
5. 求x的前驱(前驱定义为小于x,且最大的数)
6. 求x的后继(后继定义为大于x,且最小的数)

#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
struct data{
    int l,r,v,size,rnd,w;
}tr[100005];
int n,size,root,ans;
void update(int k)//更新结点信息
{
    tr[k].size=tr[tr[k].l].size+tr[tr[k].r].size+tr[k].w;
}
void rturn(int &k)
{
    int t=tr[k].l;tr[k].l=tr[t].r;tr[t].r=k;
    tr[t].size=tr[k].size;update(k);k=t;
}
void lturn(int &k)
{
    int t=tr[k].r;tr[k].r=tr[t].l;tr[t].l=k;
    tr[t].size=tr[k].size;update(k);k=t;
}
void insert(int &k,int x)
{
    if(k==0)
    {
        size++;k=size;
        tr[k].size=tr[k].w=1;tr[k].v=x;tr[k].rnd=rand();
        return;
    }
    tr[k].size++;
    if(tr[k].v==x)tr[k].w++;
    else if(x>tr[k].v)
    {
        insert(tr[k].r,x);
        if(tr[tr[k].r].rnd<tr[k].rnd)lturn(k);
    }
    else 
    {
        insert(tr[k].l,x);
        if(tr[tr[k].l].rnd<tr[k].rnd)rturn(k);
    } 
}
void del(int &k,int x)
{
    if(k==0)return; 
    if(tr[k].v==x)
    {
        if(tr[k].w>1)
        {
            tr[k].w--;tr[k].size--;return;
        }
        if(tr[k].l*tr[k].r==0)k=tr[k].l+tr[k].r;
        else if(tr[tr[k].l].rnd<tr[tr[k].r].rnd)
            rturn(k),del(k,x);
        else lturn(k),del(k,x);
    }
    else if(x>tr[k].v)
        tr[k].size--,del(tr[k].r,x);
    else tr[k].size--,del(tr[k].l,x);
}
int query_rank(int k,int x)
{
    if(k==0)return 0;
    if(tr[k].v==x)return tr[tr[k].l].size+1;
    else if(x>tr[k].v)
        return tr[tr[k].l].size+tr[k].w+query_rank(tr[k].r,x);
    else return query_rank(tr[k].l,x);
}
int query_num(int k,int x)
{
    if(k==0)return 0;
    if(x<=tr[tr[k].l].size)
        return query_num(tr[k].l,x);
    else if(x>tr[tr[k].l].size+tr[k].w)
        return query_num(tr[k].r,x-tr[tr[k].l].size-tr[k].w);
    else return tr[k].v;
}
void query_pro(int k,int x)
{
    if(k==0)return;
    if(tr[k].v<x)
    {
        ans=k;query_pro(tr[k].r,x);
    }
    else query_pro(tr[k].l,x);
}
void query_sub(int k,int x)
{
    if(k==0)return;
    if(tr[k].v>x)
    {
        ans=k;query_sub(tr[k].l,x);
    }
    else query_sub(tr[k].r,x);
}
int main()
{
    scanf("%d",&n);
    int opt,x;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&opt,&x);
        switch(opt)
        {
        case 1:insert(root,x);break;
        case 2:del(root,x);break;
        case 3:printf("%d\n",query_rank(root,x));break;
        case 4:printf("%d\n",query_num(root,x));break;
        case 5:ans=0;query_pro(root,x);printf("%d\n",tr[ans].v);break;
        case 6:ans=0;query_sub(root,x);printf("%d\n",tr[ans].v);break;
        }
    }
    return 0;
}

sbt(节点大小平衡树)

基本介绍
    它是由中国广东中山纪念中学的陈启峰发明的。陈启峰于2006年底完成论文《Size Balanced Tree》,并在2007年的全国青少年信息学奥林匹克竞赛冬令营中发表。相比红黑树、AVL树等自平衡二叉查找树,SBT更易于实现。据陈启峰在论文中称,SBT是“目前为止速度最快的高级二叉搜索树”。SBT能在O(log n)的时间内完成所有二叉搜索树(BST)的相关操作,而与普通二叉搜索树相比,SBT仅仅加入了简洁的核心操作Maintain。由于SBT赖以保持平衡的是size域而不是其他“无用”的域,它可以很方便地实现动态顺序统计中的select和rank操作。
    由于普通sbt在实用中很频繁,所以本文重点介绍另一种在竞赛条件下可能更快的退化版sbt。
补充
与标准版的比较
标准版SBT 退化版SBT
平衡方式:Maintain 平衡方式:Maintain
If s[left[left[t]]]>s[right[t]]
right_rotate(t);
If s[right[right[t]]]>s[left[t]]
left_rotate(t);
特点:相对SPLAY,AVL,TREAP速度很快,代码短,不会退化,保证深度非常小。 特点:相对标准版SBT速度更快,代码更短,随机、有序数据不会退化(除人字形数据),深度也很小。
适用范围:任何程序中。 使用范围:在信息学竞赛中很实用,因为不太可能有人字型数据。但在实际应用中就不能保证一定不退化。
插入人字形数据后退化的SBT

这里写图片描述

模板
//标准版
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>
#include <set>
#include <queue>
#include <stack>
#include <climits>//形如INT_MAX一类的
#define MAX 111111
#define INF 0x7FFFFFFF
#define REP(i,s,t) for(int i=(s);i<=(t);++i)
#define ll long long
#define mem(a,b) memset(a,b,sizeof(a))
#define mp(a,b) make_pair(a,b)
#define L(x) x<<1
#define R(x) x<<1|1
# define eps 1e-5
//#pragma comment(linker, "/STACK:36777216") ///传说中的外挂
using namespace std;

struct sbt {
    int l,r,s,key;
} tr[MAX];
int top , root;
void left_rot(int &x) {
    int y = tr[x].r;
    tr[x].r = tr[y].l;
    tr[y].l = x;
    tr[y].s = tr[x].s; //转上去的节点数量为先前此处节点的size
    tr[x].s = tr[tr[x].l].s + tr[tr[x].r].s + 1;
    x = y;
}

void right_rot(int &x) {
    int y = tr[x].l;
    tr[x].l = tr[y].r;
    tr[y].r = x;
    tr[y].s = tr[x].s;
    tr[x].s = tr[tr[x].l].s + tr[tr[x].r].s + 1;
    x = y;
}

void maintain(int &x,bool flag) {
    if(flag == 0) { //左边
        if(tr[tr[tr[x].l].l].s > tr[tr[x].r].s) {
  //左孩子左子树size大于右孩子size
            right_rot(x);
        } else if(tr[tr[tr[x].l].r].s > tr[tr[x</
  • 14
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值