排序二叉树和treap

排序二叉树:它是这样的一棵树,根的左儿子的键值小于根节点的键值,右儿子的键值大于根节点的键值,并且他的子树也满足这样的性质。为什么会叫它排序二叉树呢,因为如果按照中序排序,那么结果是一个递增的序列。可以证明的是,在一颗排序二叉树中查找、插入和删除的算法复杂度都是树的高度,但是由于排序二叉树的建树和建树过程中选择的序列顺序有关,那么建树的好坏会直接影响其结构的效率,理论上数学期望为O(logn),最坏为O(n),即为一条链的情况,所以如何建树,和在操作过程中调整树的结构式尤其重要的。

下图是二叉树的一个图例:

C++中的STL就可以set,map,multiset等就是BST(排序二叉树)的实现,但是由于过度封装,只能支持一些简单的操作


平衡二叉树:即二叉树的一个特殊形态,根节点的左右子树深度不会超过1,平衡二叉树有很多实现结构,如红黑树,伸展树,treap。在这里我们介绍的是treap。


简单的说treap是这样的:一个节点有两个值,一个是键值,一个是优先级。对于键值而言这个二叉树是排序二叉树,对于优先级而言,这个是树是堆(最大堆,记根节点优先级最大),不难证明,如果每个节点的优先级提前给定,那么这个堆也就确定了,即二叉树也就确定了,那么我们随机给每个接地啊一个优先级,因此每个节点的插入操作也是随机的,那么由于二叉树的插入操作的数学期望为O(logn),那么我们所得到的的BST对于插入,删除和查找的复杂度期望为O(logn)。实际表现也是不错的。节点定义如下:


struct node{
    node *ch[2];//左右儿子
    int r;//优先级
    int v;//键值
    
    int cmp(int x) const{
        if(x==v)return -1;
        return x<v?0:1;
    }
}

在treap的操作中常常要用到树的旋转,如下图:

向右我们称之为右旋,反之为左旋。


代码如下:

//d代表旋转,0是左旋,1是右旋
void rotate(node* &o,int d){
    node *k=o->ch[d^1];
    o->ch[d^1]=k->ch[d];
    k->ch[d]=o;
    o=k;
}
插入节点时,首先随机给节点一个优先级,然后执行普通的排序二叉树插入算法(根据键值大小判断节点应该插到那个子树中去)。执行完插入操作后,利用左右旋转让这个节点往上走,从而保持堆的性质。

代码如下:

void insert(node* &o,int x){
    if(o==NULL){
        o=new node();
        o->ch[0]=o->ch[1]=NULL;
        o-v=x;
        o-r=rand();
    }else{
        int d=o->cmp(x);
        insert(o->ch[d],x);
        if(o->ch[d]->r>o->r)rotate(o,d^1);
    }
}

删除节点时,首先找到该节点,如果它只有一颗子树,那么只要把这个子树的根节点代替这个待删除的节点即可。但是如果o有两颗子树,那么我们需要把优先级高的一颗子树旋转到根,然后递归地在另一颗子树中删除节点o。

代码如下:

void remove(node* &o,int x){
    int d=o->cmp(x);
    if(d==-1){
        if(o->ch[0]==NULL)o=o->ch[1];
        else if(o->ch[1]==NULL)o=o->ch[0];
        else{
            int d2=(o->ch[0]->r>o-ch[1]->r?1:0);
            rotate(o,d2);remove(o->ch[d2],x);
        }
    }else remove(o->ch[d],x);
}

查找就很简单啦,即是普通的排序二叉树查找

代码如下:

int find(node* o,int x){
    while(o!=NULL){
        int d=o->cmp(x);
        if(d==-1)return 1;
        else o=o->ch[d];
    }
    return 0;
}

利用treap可以做很多事情,如实现名次树(名次树中每个节点均有一个size域,表示以它为根的节点总数)。

名次树支持两种操作:

1)Kth(k)找出第k小的元素

2)Rank(k)值x的名次,即比x小的节点个数+1(表现为以节点x为根的节点总数)。


实现方法如下:treap新增一个成员变量size;

需要额外编写一个maintain函数

void maintain(node* o){
    o->s=1;
    if(o->ch[0]!=NULL)o->s+=o->ch[0]->s;
    if(o->ch[1]!=NULL)o->s+=o->ch[1]->s;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值