伸展树Splay Tree

伸展树

与平衡树(AVL)一样,伸展树(splay tree)也是二叉树的一种,它能在O(log n)内完成插入、删除和查找操作。

伸展树的特点:
1. 假设当前节点X的关键值为key,则X左子树的节点y的键值key[y] < key. 右子树节点z的键值key[z] > key.
2. 当某个节点被访问时,伸展树会通过旋转使得该节点成为树根(root)。使得下次访问该节点时,能够迅速访问到节点。

伸展树的实现

1.节点定义

与二叉树的节点定义一样:

typedef int Type;
typedef struct SplayTreeNode
{
    Type key;  //关键字
    struct SplayTreeNode *left;  //左子树
    struct SplayTreeNode *right; //右子树
}Node, *SplayTree;

2.伸展树的遍历

作为二叉树的一种,同样具有前序遍历、中序遍历、后序遍历三种遍历树的方法,这里巩固一下三种方法的实现:

//前序遍历伸展树
void preorder_splaytree(SplayTree tree)
{
    if (tree)
    {
        printf("%d  ",tree->key);
        preorder_splaytree(tree->left);
        preorder_splaytree(tree->right);
    }
}

//中序遍历伸展树
void inorder_splaytree(SplayTree tree)
{
    if (tree)
    {
        inorder_splaytree(tree->left);
        printf("%d  ",tree->key);
        inorder_splaytree(tree->right);
    }
}

//后序遍历伸展树
void poster_splaytree(SplayTree tree)
{
    if (tree)
    {
        poster_splaytree(tree->left);
        poster_splaytree(tree->right);
        printf("%d  ", tree->key);
    }
}

3. 查找给的关键值key的节点

//(递归实现)查找伸展树中键值为key的节点
Node *splaytree_search(SplayTree x, Type key)
{
    if(x == NULL || x->key == key)
        return x;
    if(key < x->key)
        return splaytree_search(x->left, key);
    if(key > x->key)
        return splaytree_search(x->right, key);
}

//(非递归实现)查找伸展树中键值为key的节点
Node *iterative_splaytree_search(SplayTree x, Type key)
{
    while ((x!= NULL)&&(x->key != key))
    {
        if(key < x->key)
            x = x->left;
        else 
            x = x->right;
    }
    return x;
}

4.查找伸展树中的最小关键值、最大关键值的节点

//查找最小节点:返回tree为根节点的伸展树的最小节点
Node *splaytree_minimum(SplayTree tree)
{
    if (tree == NULL)
    {
        printf("splay tree is empty!\n");
        return NULL;
    }
    while (tree->left != NULL)
        tree = tree->left;
    return tree;    
}

//查找最大节点:返回tree为根节点的伸展树的最大节点
Node *splaytree_maximum(SplayTree tree)
{
    if (tree == NULL)
    {
        printf("splay tree is empty!\n");
        return NULL;
    }
    while (tree->right != NULL)
        tree = tree->right;
    return tree;
}

5.伸展树的旋转

旋转key对应的节点为根节点

注意:
(a)伸展树中存在键值为key的节点, 将该节点旋转为根节点

(b)伸展树中不存在键值为key的节点,并且key < tree->key.

b-1:键值为key的节点的前驱节点存在的话,将前驱节点旋转为根节点

b-2:键值为key的节点存在前驱节点的话,则意味着,key比树中任何键值都小,那么此时,将最小节点旋转为根节点

(c): 伸展树中不存在键值为key的节点,并且key > tree->key。

c-1:键值为key的节点的后继节点存在的话,将后继节点旋转为根节点

c-2:键值为key的后继节点存在的话,则意味着,key比树中任何键值都打,那么此时,将最大节点旋转为根节点。
Node *splaytree_splay(SplayTree tree, Type key)
{
    Node N, *l, *r, *c;
    if(tree == NULL)
        return NULL;
    N.left = N.right = NULL;
    l = r = &N;

    for (;;)
    {
        if (key < tree->key)
        {
            if(tree->left == NULL)
                break;
            if (key < tree->left->key)  //01,rotate right
            {
                c = tree->left;
                tree->left = c->right;
                c->right = tree;
                tree = c;
                if(tree->left == NULL)
                    break;
            }
            r->left = tree;     //02,link right
            r = tree;
            tree = tree->left;
        }
        else if(key > tree->key)
        {
            if(tree->right == NULL)
                break;
            if(key > tree->right->key)      //03,rotate left
            {
                c = tree->right;
                tree->right = c->left;
                c->left = tree;
                tree = c;
                if(tree->right == NULL)
                    break;
            }
            l->right = tree;        //04,link left
            l = tree;
            tree = tree->right;
        }
        else break;
    }

    l->right = tree->left;      //05, assemble
    r->left = tree->right;
    tree->left = N.right;
    tree->right = N.left;

    return tree;
}

6.插入节点

//将节点插入到伸展树中(不旋转),并返回根节点
Node *splaytree_insert(SplayTree tree, Node *z)
{
    Node *y = NULL;
    Node *x = tree;

    //查找z的插入位置
    while (x != NULL)
    {
        y = x;
        if(z->key < x->key)
            x = x->left;
        else if(z->key > x ->key)
            x = x->right;
        else
        {
            printf("节点已经存在!\n");
            free(z);
            return tree;
        }
    }

    if(y == NULL)
        tree = z;
    else if(z->key < y->key)
        y->left = z;
    else 
        y->right = z;
    return tree;
}

7.创建伸展树节点

//创建并返回伸展树节点
//key:键值,parent:父节点, left:左孩子,right:右孩子
static Node *create_spalytree_node(Type key, Node *left, Node *right)
{
    Node *p;
    p = (Node *)malloc(sizeof(Node));
    if(p == NULL)
    {
        printf("out of space!\n");
        return NULL;
    }

    p->key = key;
    p->left =   left;
    p->right = right;

    return p;
}

8.创建节点key,并旋转为根节点

//新建节点key,并将其插入到伸展树中,将插入节点旋转为根节点
Node *insert_splaytree(SplayTree tree, Type key)
{
    Node *z; //新建节点

    if((z = create_spalytree_node(key, NULL, NULL))== NULL) //新建节点失败,则返回
        return tree;

    //插入节点
    tree = splaytree_insert(tree, z);

    //将节点key旋转为根节点
    tree = splaytree_splay(tree, key);
}

9.删除key节点

//删除key节点,返回根节点(根节点是被删除节点的前驱节点)
Node *delete_splaytree(SplayTree tree, Type key)
{
    Node *x;
    if(tree == NULL)
        return NULL;
    //查找键值为key的节点,找不到的话直接返回
    if(splaytree_search(tree,key) == NULL)
        return tree;

    //将key对应的节点旋转为根节点
    tree = splaytree_splay(tree, key);

    if (tree->left)
    {
        //将tree的前驱节点旋转为根节点
        x = splaytree_splay(tree->left, key);

        //移除tree节点
        x->right = tree->right;
    }
    else 
        x = tree->right;

    free(tree);
    return x;
}

其实伸展树的大部分操作还是二叉树的操作,只是在访问节点时,将节点旋转至跟节点,留意该旋转操作。

完整的测试代码

#include <stdio.h>
#include <stdlib.h>
typedef int Type;

typedef struct SplayTreeNode
{
    Type key;  //关键字
    struct SplayTreeNode *left;  //左子树
    struct SplayTreeNode *right; //右子树
}Node, *SplayTree;

//前序遍历伸展树
void preorder_splaytree(SplayTree tree)
{
    if (tree)
    {
        printf("%d  ",tree->key);
        preorder_splaytree(tree->left);
        preorder_splaytree(tree->right);
    }
}

//中序遍历伸展树
void inorder_splaytree(SplayTree tree)
{
    if (tree)
    {
        inorder_splaytree(tree->left);
        printf("%d  ",tree->key);
        inorder_splaytree(tree->right);
    }
}

//后序遍历伸展树
void poster_splaytree(SplayTree tree)
{
    if (tree)
    {
        poster_splaytree(tree->left);
        poster_splaytree(tree->right);
        printf("%d  ", tree->key);
    }
}


//(递归实现)查找伸展树中键值为key的节点
Node *splaytree_search(SplayTree x, Type key)
{
    if(x == NULL || x->key == key)
        return x;
    if(key < x->key)
        return splaytree_search(x->left, key);
    if(key > x->key)
        return splaytree_search(x->right, key);
}

//(非递归实现)查找伸展树中键值为key的节点
Node *iterative_splaytree_search(SplayTree x, Type key)
{
    while ((x!= NULL)&&(x->key != key))
    {
        if(key < x->key)
            x = x->left;
        else 
            x = x->right;
    }
    return x;
}

//查找最小节点:返回tree为根节点的伸展树的最小节点
Node *splaytree_minimum(SplayTree tree)
{
    if (tree == NULL)
    {
        printf("splay tree is empty!\n");
        return NULL;
    }
    while (tree->left != NULL)
        tree = tree->left;
    return tree;    
}

//查找最大节点:返回tree为根节点的伸展树的最大节点
Node *splaytree_maximum(SplayTree tree)
{
    if (tree == NULL)
    {
        printf("splay tree is empty!\n");
        return NULL;
    }
    while (tree->right != NULL)
        tree = tree->right;
    return tree;
}


//旋转key对应的节点为根节点
//注意:(a)伸展树中存在键值为key的节点, 将该节点旋转为根节点
//(b)伸展树中不存在键值为key的节点,并且key < tree->key.
//      b-1:键值为key的节点的前驱节点存在的话,将前驱节点旋转为根节点
//      b-2:键值为key的节点存在前驱节点的话,则意味着,key比树中任何键值都小,那么此时,将最小节点旋转为根节点
//(c): 伸展树中不存在键值为key的节点,并且key > tree->key。
//      c-1:键值为key的节点的后继节点存在的话,将后继节点旋转为根节点
//      c-2:键值为key的后继节点存在的话,则意味着,key比树中任何键值都打,那么此时,将最大节点旋转为根节点。
Node *splaytree_splay(SplayTree tree, Type key)
{
    Node N, *l, *r, *c;
    if(tree == NULL)
        return NULL;
    N.left = N.right = NULL;
    l = r = &N;

    for (;;)
    {
        if (key < tree->key)
        {
            if(tree->left == NULL)
                break;
            if (key < tree->left->key)  //01,rotate right
            {
                c = tree->left;
                tree->left = c->right;
                c->right = tree;
                tree = c;
                if(tree->left == NULL)
                    break;
            }
            r->left = tree;     //02,link right
            r = tree;
            tree = tree->left;
        }
        else if(key > tree->key)
        {
            if(tree->right == NULL)
                break;
            if(key > tree->right->key)      //03,rotate left
            {
                c = tree->right;
                tree->right = c->left;
                c->left = tree;
                tree = c;
                if(tree->right == NULL)
                    break;
            }
            l->right = tree;        //04,link left
            l = tree;
            tree = tree->right;
        }
        else break;
    }

    l->right = tree->left;      //05, assemble
    r->left = tree->right;
    tree->left = N.right;
    tree->right = N.left;

    return tree;
}

//将节点插入到伸展树中(不旋转),并返回根节点
Node *splaytree_insert(SplayTree tree, Node *z)
{
    Node *y = NULL;
    Node *x = tree;

    //查找z的插入位置
    while (x != NULL)
    {
        y = x;
        if(z->key < x->key)
            x = x->left;
        else if(z->key > x ->key)
            x = x->right;
        else
        {
            printf("节点已经存在!\n");
            free(z);
            return tree;
        }
    }

    if(y == NULL)
        tree = z;
    else if(z->key < y->key)
        y->left = z;
    else 
        y->right = z;
    return tree;
}

//创建并返回伸展树节点
//key:键值,parent:父节点, left:左孩子,right:右孩子
static Node *create_spalytree_node(Type key, Node *left, Node *right)
{
    Node *p;
    p = (Node *)malloc(sizeof(Node));
    if(p == NULL)
    {
        printf("out of space!\n");
        return NULL;
    }

    p->key = key;
    p->left =   left;
    p->right = right;

    return p;
}


//新建节点key,并将其插入到伸展树中,将插入节点旋转为根节点
Node *insert_splaytree(SplayTree tree, Type key)
{
    Node *z; //新建节点

    if((z = create_spalytree_node(key, NULL, NULL))== NULL) //新建节点失败,则返回
        return tree;

    //插入节点
    tree = splaytree_insert(tree, z);

    //将节点key旋转为根节点
    tree = splaytree_splay(tree, key);
}


//删除key节点,返回根节点(根节点是被删除节点的前驱节点)
Node *delete_splaytree(SplayTree tree, Type key)
{
    Node *x;
    if(tree == NULL)
        return NULL;
    //查找键值为key的节点,找不到的话直接返回
    if(splaytree_search(tree,key) == NULL)
        return tree;

    //将key对应的节点旋转为根节点
    tree = splaytree_splay(tree, key);

    if (tree->left)
    {
        //将tree的前驱节点旋转为根节点
        x = splaytree_splay(tree->left, key);

        //移除tree节点
        x->right = tree->right;
    }
    else 
        x = tree->right;

    free(tree);
    return x;
}

//销毁伸展树
void destroy_splaytree(SplayTree tree)
{
    if(tree == NULL)
        return ;
    if(tree->left != NULL)
        destroy_splaytree(tree->left);
    if(tree->right != NULL)
        destroy_splaytree(tree->right);
    free(tree);
}

//打印伸展树
void print_splaytree(SplayTree tree, Type key, int direction)
{
    if (tree)
    {
        if(direction == 0) //tree是根节点
            printf("%2d is root\n", tree->key);
        else 
            printf("%2d is %2d's %6s child\n", tree->key, key, direction == 1?"right" :"left");

        print_splaytree(tree->left, tree->key, -1);
        print_splaytree(tree->right, tree->key, 1);
    }
}



 //测试
 int main()
 {
     int a[] = {10, 50, 40, 30, 20, 60};
     int i;

     SplayTree root = NULL;
     printf("===依次添加===\n");
     for (i = 0; i < 6; i ++)
     {
         printf("%d  ", a[i]);
         root = insert_splaytree(root, a[i]);
     }

     printf("\n 前序遍历\n");
     preorder_splaytree(root);

     printf("\n中序遍历\n");
     inorder_splaytree(root);

     printf("\n后续遍历\n");
     poster_splaytree(root);

     printf("\n最大值:%d\n",splaytree_maximum(root)->key);

     printf("最小值:%d\n",splaytree_minimum(root)->key);

     printf("树的详细信息:\n");
     print_splaytree(root, root->key, 0);

     i = 30;
     printf("旋转节点为%d为根节点\n", i);
     root = splaytree_splay(root, i);
     printf("树的详细信息\n");
     print_splaytree(root, root->key, 0);

     destroy_splaytree(root);

     system("pause");
     return 0;
 }

这里写图片描述

reference:http://www.cnblogs.com/skywang12345/p/3604238.html?utm_source=tuicool

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值