伸展树

伸展树(Splay Tree)是AVL树不错的替代,它有以下几个特点:

(1)它是二叉查找树的改进,所以具有二叉查找树的有序性。
(2)对伸展树的操作的平摊复杂度是O(log2n)。
(3)伸展树的空间要求、编程难度非常低。

提到伸展树,就不得不提到AVL树和Read-Black树,虽然这两种树能够保证各种操作在最坏情况下都为logN,

但是两都实现都比较复杂。而在实际情况中,90%的访问发生在10%的数据上。因此,我们可以重构树的

结构,使得被经常访问的节点朝树根的方向移动。尽管这会引入额外的操作,但是经常被访问的节点被移

动到了靠近根的位置,因此,对于这部分节点,我们可以很快的访问。这样,就能使得平摊复杂度为logN。

1、自底向上的伸展树
伸展操作Splay(x,S)是在保持伸展树有序性的前提下,通过一系列旋转操作将伸展树S中的元素x调整至树

的根部的操作。
在旋转的过程中,要分三种情况分别处理:
(1)Zig 或 Zag
(2)Zig-Zig 或 Zag-Zag
(3)Zig-Zag 或 Zag-Zig
1.1、Zig或Zag操作
节点x的父节点y是根节点。

1.2、Zig-Zig或Zag-Zag操作
节点x的父节点y不是根节点,且x与y同时是各自父节点的左孩子或者同时是各自父节点的右孩子。

1.3、Zig-Zag或Zag-Zig操作
节点x的父节点y不是根节点,x与y中一个是其父节点的左孩子而另一个是其父节点的右孩子。

2、自顶向下的伸展树
    在自底向上的伸展树中,我们需要求一个节点的父节点和祖父节点,因此这种伸展树难以实现。因此,

我们可以构建自顶向下的伸展树。
    当我们沿着树向下搜索某个节点X的时候,我们将搜索路径上的节点及其子树移走。我们构建两棵临时

的树──左树和右树。没有被移走的节点构成的树称作中树。在伸展操作的过程中:
(1)当前节点X是中树的根。
(2)左树L保存小于X的节点。
(3)右树R保存大于X的节点。
开始时候,X是树T的根,左右树L和R都是空的。和前面的自下而上相同,自上而下也分三种情况:
2.1、Zig操作

如上图,在搜索到X的时候,所查找的节点比X小,将Y旋转到中树的树根。旋转之后,X及其右子树被移

动到右树上。很显然,右树上的节点都大于所要查找的节点。注意X被放置在右树的最小的位置,也就

是X及其子树比原先的右树中所有的节点都要小。这是由于越是在路径前面被移动到右树的节点,其值

越大。

2.2、Zig-Zig操作

这种情况下,所查找的节点在Z的子树中,也就是,所查找的节点比X和Y都小。所以要将X,Y及其右

子树都移动到右树中。首先是Y绕X右旋,然后Z绕Y右旋,最后将Z的右子树(此时Z的右子节点为Y)

移动到右树中。

2.3、Zig-Zag操作

这种情况中,首先将Y右旋到根。这和Zig的情况是一样的,然后变成上图右边所示的形状。此时,就

与Zag(与Zig相反)的情况一样了。

最后,在查找到节点后,将三棵树合并。如图:


2.4、示例:
下面是一个查找节点19的例子。在例子中,树中并没有节点19,最后,距离节点最近的节点18被旋转到了

根作为新的根。节点20也是距离节点19最近的节点,但是节点20没有成为新根,这和节点20在原来树中

的位置有关系。

3、实现
3.1、splay操作

代码tree_node * splay (int i, tree_node * t) {
    tree_node N, *l, *r, *y;
    if (t == NULL) 
        return t;
    N.left = N.right = NULL;
    l = r = &N;

    for (;;)
    {
        if (i < t->item) 
        {
            if (t->left == NULL) 
                break;
            if (i < t->left->item) 
            {
                y = t->left;                           /* rotate right */
                t->left = y->right;
                y->right = t;
                t = y;
                if (t->left == NULL) 
                    break;
            }
            r->left = t;                               /* link right */
            r = t;
            t = t->left;
        } else if (i > t->item)
        {
            if (t->right == NULL) 
                break;
            if (i > t->right->item) 
            {
                y = t->right;                          /* rotate left */
                t->right = y->left;
                y->left = t;
                t = y;
                if (t->right == NULL) 
                    break;
            }
            l->right = t;                              /* link left */
            l = t;
            t = t->right;
        } else {
            break;
        }
    }
    l->right = t->left;                                /* assemble */
    r->left = t->right;
    t->left = N.right;
    t->right = N.left;
    return t;
}

Rotate right(查找10):

Link right:

Assemble:

Rotate left(查找20):

Link left:

3.2、插入操作

代码  /*
  **将i插入树t中,返回树的根结点(item值==i)
  */
  tree_node* ST_insert(int i, tree_node *t) {
      /* Insert i into the tree t, unless it's already there.    */
      /* Return a pointer to the resulting tree.                 */
      tree_node* node;
      
      node = (tree_node *) malloc (sizeof (tree_node));
     if (node == NULL){
         printf("Ran out of space\n");
         exit(1);
     }
     node->item = i;
     if (t == NULL) {
         node->left = node->right = NULL;
         size = 1;
         return node;
     }
     t = splay(i,t);
     if (i < t->item) {  //令t为i的右子树
         node->left = t->left;
         node->right = t;
         t->left = NULL;
         size ++;
         return node;
     } else if (i > t->item) { //令t为i的左子树
         node->right = t->right;
         node->left = t;
         t->right = NULL;
         size++;
         return node;
     } else { 
         free(node); //i值已经存在于树t中
         return t;
     }
 }

3.3、删除操作

代码  /*
 **从树中删除i,返回树的根结点
 */
 tree_node* ST_delete(int i, tree_node* t) {
     /* Deletes i from the tree if it's there.               */
     /* Return a pointer to the resulting tree.              */
     tree_node* x;
     if (t==NULL) 
         return NULL;
     t = splay(i,t);
     if (i == t->item) {               /* found it */
         if (t->left == NULL) { //左子树为空,则x指向右子树即可
           x = t->right;
         } else {
             x = splay(i, t->left); //查找左子树中最大结点max,令右子树为max的右子树
             x->right = t->right;
         }
         size--;
         free(t);
         return x;
     }
     return t;                         /* It wasn't there */
 }

完整代码:

代码#include <stdio.h>
#include <stdlib.h>

int     size; //结点数量

#define        NUM        20

typedef struct tree_node{
    struct tree_node*    left;
    struct tree_node*    right;
    int        item;
}tree_node;

tree_node* splay (int i, tree_node* t) {
    tree_node N, *l, *r, *y;

    if (t == NULL) 
        return t;

    N.left = N.right = NULL;
     l = r = &N;
 
     for (;;)
     {
         if (i < t->item) 
         {
             if (t->left == NULL) 
                 break;
             if (i < t->left->item) 
             {
                 y = t->left;                           /* rotate right */
                 t->left = y->right;
                 y->right = t;
                 t = y;
                 if (t->left == NULL) 
                     break;
             }
             r->left = t;                               /* link right */
            r = t;
             t = t->left;
         } else if (i > t->item)
         {
             if (t->right == NULL) 
                 break;
             if (i > t->right->item) 
             {
                 y = t->right;                          /* rotate left */
                 t->right = y->left;
                 y->left = t;
                 t = y;
                 if (t->right == NULL) 
                     break;
             }
             l->right = t;                              /* link left */
             l = t;
             t = t->right;
         } else {
             break;
         }
     }
     l->right = t->left;                                /* assemble */
     r->left = t->right;
     t->left = N.right;
     t->right = N.left;
     return t;
 }
 
 /*
 **将i插入树t中,返回树的根结点(item值==i)
 */
 tree_node* ST_insert(int i, tree_node *t) {
     /* Insert i into the tree t, unless it's already there.    */
     /* Return a pointer to the resulting tree.                 */
     tree_node* node;
     
     node = (tree_node *) malloc (sizeof (tree_node));
     if (node == NULL){
         printf("Ran out of space\n");
         exit(1);
     }
     node->item = i;
     if (t == NULL) {
         node->left = node->right = NULL;
         size = 1;
         return node;
     }
     t = splay(i,t);
     if (i < t->item) {  //令t为i的右子树
         node->left = t->left;
         node->right = t;
         t->left = NULL;
         size ++;
         return node;
     } else if (i > t->item) { //令t为i的左子树
         node->right = t->right;
         node->left = t;
         t->right = NULL;
         size++;
         return node;
    } else { 
         free(node); //i值已经存在于树t中
         return t;
     }
 }
 
 
 /*
 **从树中删除i,返回树的根结点
 */
 tree_node* ST_delete(int i, tree_node* t) {
     /* Deletes i from the tree if it's there.               */
     /* Return a pointer to the resulting tree.              */
     tree_node* x;
     if (t==NULL) 
         return NULL;
     t = splay(i,t);
     if (i == t->item) {               /* found it */
         if (t->left == NULL) { //左子树为空,则x指向右子树即可
             x = t->right;
         } else {
             x = splay(i, t->left); //查找左子树中最大结点max,令右子树为max的右子树
             x->right = t->right;
         }
         size--;
         free(t);
         return x;
     }
     return t;                         /* It wasn't there */
 }
 
 void ST_inoder_traverse(tree_node*    node)
 {
     if(node != NULL)
     {
         ST_inoder_traverse(node->left);
         printf("%d ", node->item);
         ST_inoder_traverse(node->right);
     }
 }
 
 void ST_pre_traverse(tree_node*    node)
 {
     if(node != NULL)
    {
         printf("%d ", node->item);
         ST_pre_traverse(node->left);
         ST_pre_traverse(node->right);
     }
 }
 
 
 void main() {
     /* A sample use of these functions.  Start with the empty tree,         */
     /* insert some stuff into it, and then delete it                        */
     tree_node* root;
     int i;
 
     root = NULL;              /* the empty tree */
     size = 0;
 
     for(i = 0; i < NUM; i++)
         root = ST_insert(rand()%NUM, root);
 
     ST_pre_traverse(root);
     printf("\n");
     ST_inoder_traverse(root);
 
     for(i = 0; i < NUM; i++)
         root = ST_delete(i, root);
 
     printf("\nsize = %d\n", size);
 }

总结一下刚学习的平衡树(splay树):

平衡树的由来:

伸展树是平衡二叉树的一种。普通的二叉搜索树在一些特殊数据下会退化成链状,不能保证均

摊O(nlogn)的复杂度,平衡二叉树则通过一些条件下的旋转操作保证了O(nlogn)的复杂度;而

Splay则通过其特殊的伸展(Splay)操作保证了均摊复杂度为O(nlogn).

-----------------------------------------------------------------------------------------------

精髓:

1) 旋转操作

2) 伸展操作

3) 进阶操作(插入,删除,求第k大(小)元素)

1).旋转操作的重要性: 旋转操作保证了splay的均摊复杂度O(nlogn).

分为:

ZIG(x):右旋.

ZAG(y):左旋.

-----------------------------------------------------------------------------------------------

右旋:

右旋的条件: 所旋转的结点x是其父亲结点y的左儿子.

所需修改:

1) X父亲结点y的左子树指针: 修改为X结点的右子树.

2) X结点的父亲指针:修改为y结点的父亲.

3) X结点的右子树指针:修改为y结点。

4) 附加域的修改,cnt[x](以x为根子树的结点数目)

cnt[y] = cnt[y] – cnt son[y][1] ] + cnt[ son[x][2] ];

cnt[x] = cnt[x] – cnt[ son[x][2] ] + cnt[y];

其中son[y][1]是y结点的左子树指针,son[x][2]是x结点右子树指针.

-----------------------------------------------------------------------------------------------

左旋:

左旋的条件: 所旋转的结点x是其父亲结点y的右儿子.

所需修改:

1) x父亲结点y的有子树指针: 修改为X结点的左子树.

2) X结点的父亲指针:修改为y结点的父亲

3) X结点的左子树指针:修改为y结点。

5) 附加域的修改,cnt[x](以x为根子树的结点数目)

cnt[y] = cnt[y] – cnt son[y][2] ] + cnt[ son[x][1] ];

cnt[x] = cnt[x] – cnt[ son[x][1] ] + cnt[y];

4) 其中son[y][2]是y结点的右子树指针,son[x][1]是x结点左子树指针

分析得出:

左旋和右旋是对称的,它们旋转以后得到的二叉树是二叉排序树。

-----------------------------------------------------------------------------------------------

伸展操作:

对于splay(x, y):表示旋转x,使得y的左儿子(datax[x] < data[y])或者右儿子(data[x] > data[y])是

x,其中y是以y为根的splay树的树根.

伸展操作是Splay的精髓,也是Splay维护平衡的关键(如果没有Splay操作,伸展树就无法保证O(nlogn)

的均摊复杂度)。伸展操作仅仅用到了两种Rotate的简单组合——组合出的四种新操作称作ZIG-ZAG,

ZAG-ZIG,ZIG-ZIG与ZAG-ZAG,加上原先的ZIG和ZAG,总共六种操作

牢记左旋和右旋的条件.

重新定义x, y, z.

X是需要旋转的结点,y是x的父亲,z是y的父亲.

如果x的父亲结点已经是树根了, 根据条件旋转x.

否则

如果x,y,z在一条直线上,先根据条件旋转x,接着根据条件再次旋转x。

如果x, y, z在一条折线上,先根据条件旋转y,接着根据条件旋转x。

-----------------------------------------------------------------------------------------------

插入操作:

insert(w, j):表示在j结点位置插入关键域w.

首先要找到插入j结点的父亲结点的位置i.还要保证结点j不在splay树中。

对于Splay来说,插入操作和其它平衡二叉树没有什么太大的区别,唯一的不同就在于插入操作之后

需要将刚插入的结点伸展到根最后将j结点调整到树根.

删除操作:

Splay有一种很特别的删除操作,但Splay也支持普通平衡二叉树的删除操作,因为Splay没有严格维护

平衡的条件,所以可以对它进行任何其它平衡二叉树上的操作。但是,Splay的特殊删除操作更美妙,

更简便,更易于理解。

如果忽略一些繁琐的边界条件,那么删除结点x的整个过程可以被描述为这样: 定义y为x的前驱结点。

实际应用中还要考虑这样几种情况:

1.    x没有左右子树,说明splay中只有x,将x删除即可.

2.    x没有左子树,有右子树,将树根修改为x的右子树,然后删除x.

3.    首先,splay(y,x)

然后,修改y的右子树指针为x的右子树

如果x的右子树不为空,将其父亲指针修改为y。

将树根修改为y,并且将x从splay中删除,更新y结点的其它域.

-----------------------------------------------------------------------------------------------

求第k大(小)元素:

求第k小元素:从根结点开始递归进行在当前子树查找第k小操作:如果当前结点的左子树大小大于等

于k,那么在左子树查找第k小的数;如果k大于左子树大小加1,那么在右子树查找第k-(左子树大小

+1)小的数;否则k一定等于左子树大小加1,此时返回当前结点编号


转自:http://hi.baidu.com/ercongmuming/item/859b7accaff753374594162e

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值