伸展树的旋转和伸展操作

来源:互联网 发布:java开发工作经历描述 编辑:程序博客网 时间:2024/05/01 12:49

    伸展树(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;}





0 0