SPlay 伸展树

来源:互联网 发布:mac强制关掉进程 编辑:程序博客网 时间:2024/06/06 09:48

Splay伸展树

伸展树(Splay Tree)是一种二叉排序树,能在最坏平摊LogN时间内完成插入、查找和删除操作。是一种自调整形式的二叉树,它会沿着某个节点到跟的路径,通过一系列旋转把这个节点搬到根节点去。

Splay树的基本操作:
1、旋转和伸展
2、查找
3、插入、删除
4、最大最小值
5、前驱后继
6、合并、分离
一般Splay的节点数据:

struct node{    int val,size;       //节点值,节点大小    node * ch[2],*fa;   //左右节点,父节点}*root=NULL;            

Splay旋转:

void Rotate(Node *&x,int c){//c==0 左旋 c==1右旋     Node *y=x->fa;    //PushDown(y);    //PushDown(x);    //PushDown(x->ch[c]);    y->ch[!c] = x->ch[c];    if(x->ch[c] != NULL) x->ch[c]->fa = y;    x->fa = y->fa;    if(y->fa != NULL)        if(y->fa->ch[0] == y) y->fa->ch[0] = x;        else y->fa->ch[1] = x;    x->ch[c] = y; y->fa = x;    //UpDate(y);    if(y == root) root=x;}

这里写图片描述

注意:前方高能

伸展操作:
情况一:节点x的父节点y是根节点。这时,如果x是p的左孩子,我们进行一次Zig(右旋)操作;如果x 是p 的右孩子,则我们进行一次Zag(左旋)操作。经过旋转,x成为二叉查找树的根节点,调整结束。即:如果当前结点父结点即为根结点,那么我们只需要进行一次简单旋转即可完成任务,我们称这种旋转为单旋转。如图1所示:
这里写图片描述
情况二:(一般称作“一字型旋转”)节点x 的父节点p 不是根节点,p 的父节点为g,且x 与p 同时是各自父节点的左孩子或者同时是各自父节点的右孩子。这时,我们进行一次Zig-Zig操作或者Zag-Zag操作。即:设当前结点为x , x 的父结点为p ,p 的父结点为g ,如果p 和x 同为其父亲的左孩子或右孩子,那么我们先旋转p ,再旋转x 。如图2所示:
这里写图片描述
情况三:(一般称作“之字型旋转”)节点x的父节点p不是根节点,p的父节点为g,x与p中一个是其父节点的左孩子而另一个是其父节点的右孩子。这时,我们进行一次Zig-Zag操作或者Zag-Zig 操作。即:这时我们连续旋转两次X 。如图3所示:
这里写图片描述
说明:这种情况先将x转到它的父亲的位置上,再与g旋转

SPLAY伸展操作代码:

// node 为结点类型,其中ch[0]表示左结点指针,ch[1]表示右结点指针  // pre 表示指向父亲的指针  void Rotate(Node *&x,int c){//c==0 左旋 c==1右旋     Node *y=x->fa;    //PushDown(y);    //PushDown(x);    //PushDown(x->ch[c]);    y->ch[!c] = x->ch[c];    if(x->ch[c] != NULL) x->ch[c]->fa = y;    x->fa = y->fa;    if(y->fa != NULL)        if(y->fa->ch[0] == y) y->fa->ch[0] = x;        else y->fa->ch[1] = x;    x->ch[c] = y; y->fa = x;    //UpDate(y);    if(y == root) root=x;} void Splay(Node *&cur,Node *&f){//将cur转到f的位置     for(PushDown(cur); cur != f ;){        if(cur->fa == f)             if(f->ch[0] == cur) Rotate(cur,1);            else Rotate(cur,0);        else {            Node *y = cur->fa,*z = y->fa;            if(z->ch[0] == y)                if(y->ch[0] == cur)                    Rotate(y,1),Rotate(cur,1);//yi                else Rotate(cur,0),Rotate(cur,1);//zhi            else if(y->ch[1] == cur)                    Rotate(y,0),Rotate(cur,0);//yi                else Rotate(cur,1),Rotate(cur,0);//zhi            if(z==f) break;        }        //UpDate(cur);    }    //UpDate(cur);} 

如果说要把某个节点转到根的位置,你可以假设还有一个须根,当前树的根永远吊在须根上!!(个人心得、仅供参考)

Splay树的递归建立
当建树节点的值是有序的时候,可以利用递归方式建树,类似于线段树的建树方式,效率还是比较高的。

void build(node *fa,node *&cur,int l,int r){    if(l>r)return;    int mid=(l+r)>>1;    cur=newnode(sz[mid]);    cur->pre=fa;    build(cur,cur->ch[0],l,mid-1);    build(cur,cur->ch[1],mid+1,r);    update(cur);}

Splay的节点查找
首先按照二叉排序树的性质(左子树都比它小,右子树都比它大),然后将该节点伸展到根节点。。

//查找键值为X的节点的位置并将其旋转到根节点 node *bst_search(node *cur,int x){    if(!cur) return NULL;    if(cur->sh == x) return cur;    else if(x > cur->sh)        return bst_search(cur->ch[1],x);    else if(x < cur->sh)        return bst_search(cur->ch[0],x);}//bst_search 返回的是键值为x的元素的位置 node *search(node *cur,int x){    node *p=bst_search(cur,x);    //此时p即为元素x的位置    splay(p,cur->pre);    return p;}

找到处在中序遍历第k 个结点,并将其旋转到结点f 的下面:

void Select(int k, node *&f){    int tmp;    node *t;    for(t=root;;) { // 从根结点开始        Push_Down(t); // 由于要访问t 的子结点,将标记下传        tmp = t->ch[0]->size;//得到t 左子树的大小        if (k == tmp+1) break;//得出t 即为查找结点,退出循环        if (k <= tmp) // 第k 个结点在t 左边,向左走            t = t->ch[0];        else // 否则在右边,而且在右子树中,这个结点不再是第k 个            k -= tmp + 1, t = t->ch[1];    }    Push_Down(t);    splay(t,f); // 执行旋转}

Splay节点插入:
按照二叉排序树插入,然后将其旋转到根节点下
结构体里写上这个函数:

node *newnode(int x){    node *cur=new node;    cur->val = x;    cur->ch[0] = cur->ch[1] = cur->pre = NULL;}

然后是插入函数:

//插入值为x的一个新节点 void insert(node * cur,int x){    if(root==NULL){//cur不可改变,root要特判        root=newnode(x);        return ;    }    node *p;    while(cur!=NULL){        p=cur;        if(cur->val>x) cur = cur->ch[0];        else cur = cur->ch[1];    }    //在 while 过程当中 p始终保持是cur的父节点     cur=newnode(x);    if(cur->val < p->val) p->ch[0]=cur; //注意判读条件,不可用cur==p->ch[0]    else p->ch[1]=cur;    cur->pre = p;    splay(cur,NULL);}

Splay的合并:

将俩棵伸展树合并必须保证第二课伸展树的值都大于第一棵伸展树,只需要将第一棵伸展树的最大节点转到根的位置上,这时候该节点的有孩子为空(因为:该节点是最大的节点,如果他还有右孩子的话根据二叉排序树的定义他的右孩子比他大),直接将第二棵伸展树挂到第一棵伸展树的最大节点的右孩子上。。

node *merge(node *x,node *y){    if(!x) return y;    if(!y) return x;    while(x->ch[1]) x = x->ch[1];    splay(x,NULL);    x->ch[1] = y;    y->pre = x;    return x;}

Splay分离:
方法:查找给定值得节点并伸展到根,返回它的左右孩子

//Splay 分离void split(node *cur,int x,node *&x,node *&y){    node *t=search(cur,x);    x=cur->ch[0];x->pre=NULL;    y=cur->ch[1];y->pre=NULL;}

Splay节点删除:

//值为x的节点删除 node *del(node *cur,int x){    node *t=search(cur,x);    return merge(cur->ch[0],cur->ch[1]);}

上面的内容看看还行,代码就算了,代码是我粘的我们吕老师的,指针写的是又臭又长又丑,代码还的看我的。。

0 0
原创粉丝点击