伸展树的旋转和伸展操作

    伸展树(Splay Tree)是一种排序二叉树,其核心操作是伸展。所谓伸展就是把指定节点旋转至树根(同时保持排序二叉树性质)的过程。而伸展操作的基础就是旋转。

    旋转是所有排序二叉树的基本操作,各种平衡二叉树想要维持其平衡性质都离不开旋转。旋转分为左旋和右旋。但实际上,如果指定节点为左儿子,那么它只能右旋;如果指定节点为右儿子,那么它只能左旋。所以如何旋转可以看作是节点本身的一种性质,而非由外界传参决定。


    无论是左旋还是右旋,均是重新确定三对父子关系。上图右旋中,旋转前后有3对父子关系发生了改变,改变后分别是Gt、tP和PB;左旋也一样:Gt、tP和PB。当然祖父节点G不一定存在(P一定存在,因为只会对非根节点做旋转)。

    同时,左旋和右旋从某种意义上是对称的。所以可以只用一个函数完成,就称之为旋转。

    假设使用静态数组实现二叉树,同时将伸展树的节点定义成如下结构体:

struct node_t{
    int parent; 
    int child[2]; 
    int sn;  //本节点是左儿子还是右儿子
             //0表示左,1表示右
    //...... //其他域省略;
}Node[SIZE];  //Node[0]不使用,用于模拟NULL指针

    首先将确定父子关系的代码封装成一个函数,该函数的意思是将p节点的sn儿子设置为t

void _link(int p,int sn,int t){
    Node[p].child[sn] = t;
    Node[t].parent = p;
    Node[t].sn = sn;
}


    则旋转操作可以写成:

void _rotate(int t){
    int p = Node[t].parent;
    int sn = Node[t].sn;
    int osn = sn ^ 1;
    
    //确定3对父子关系
    _link(p,sn,Node[t].child[osn]);
    _link(Node[p].parent,Node[p].sn,t);
    _link(t,osn,p);
}

    对节点t每完成一次旋转操作,t就会提升一层。不停的旋转,t自然就会达到树根。所以最简单的伸展操作可以这样写(该函数的涵义是在根为root的二叉树中将节点t提升至树根):

void _splay(int t,int& root){
    while( Node[t].parent ) _rotate(t);
    root = t;
}

    但是,还有一种稍微复杂一点但效率更高的用于伸展的旋转方法,称为双旋操作或者之字形旋转或者zig-zag操作等等。双旋操作本质上就是两个旋转操作,所以根本不必费心去画图观察如何实现,只要明确调用旋转的条件即可。

    双旋操作是指:如果t及其父亲p的排行(同为左儿子或者同为右儿子)相同,则先旋转p再旋转t;否则,连续旋转t两次。一个双旋可以将t提升两个层次,所以t必须有祖父节点才能进行双旋。当然,此处不必显示的封装一个双旋函数,只需在伸展里面写出即可。使用双旋的伸展函数如下:

void _splay(int t, int& root){
    while(Node[t].parent){
        int p = Node[p].parent;
        if ( p == root ){
            _rotate(t);
            break;
        }

        if ( Node[p].sn == Node[t].sn ){
            _rotate(p);
            _rotate(t);
        }else{
            _rotete(t);
            _rotate(t);
        }
    }

    root = t;
}

    注意rotate(t)有多处重复,所以上述代码可以精简一下,变成:

void splay(int t, int& root){
    while(Node[t].parent){
        int p = Node[t].parent;
        if ( p != root ) Node[p].sn == Node[t].sn ? _rotate(p) : _rotate(t);
        _rotate(t);
    }
    root = t;
}

    有时候,我们需要将指定节点t伸展成指定节点p的儿子,于是将伸展操作修改如下。当参数p取0时,就是将t伸展成树根。

void splay(int t,int p,int& root){
	while( Node[t].parent != p ){
		int pp = Node[t].parent;
		if ( Node[pp].parent != p ) Node[pp].sn == Node[t].sn ? _rotate(pp) : _rotate(t);
		_rotate(t);
	}
	
	if ( 0 == p ) root = t;
}





  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值