算法简介



伸展树


Splaying是Splay Tree中的基本操作,为了让被查询的条目更接近树根,Splay Tree使用了树的旋转操作,同时保证二叉排序树的性质不变。

Splaying的操作受以下三种因素影响:

节点x是父节点p的左孩子还是右孩子

节点p是不是根节点,如果不是

节点p是父节点g的左孩子还是右孩子


同时有三种基本操作:

Zig Step

当p为根节点时,进行zip step操作。 当x是p的左孩子时,对x右旋; 当x是p的右孩子时,对x左旋。

Zig-Zig Step

当p不是根节点,且x和p同为左孩子或右孩子时进行Zig-Zig操作。 当x和p同为左孩子时,依次将p和x右旋; 当x和p同为右孩子时,依次将p和x左旋。

Zig-Zag Step

当p不是根节点,且x和p不同为左孩子或右孩子时,进行Zig-Zag操作。 当p为左孩子,x为右孩子时,将x左旋后再右旋。 当p为右孩子,x为左孩子时,将x右旋后再左旋。


Splay Tree可以方便的解决一些区间问题,根据不同形状二叉树先序遍历结果不变的特性,可以将区间按顺序建二叉查找树。 每次自下而上的一套splay都可以将x移动到根节点的位置,利用这个特性,可以方便的利用Lazy的思想进行区间操作。 对于每个节点记录size,代表子树中节点的数目,这样就可以很方便地查找区间中的第k小或第k大元素。 对于一段要处理的区间[x, y],首先splay x-1到root,再splay y+1到root的右孩子,这时root的右孩子的左孩子对应子树就是整个区间。 这样,大部分区间问题都可以很方便的解决, 操作同样也适用于一个或多个条目的添加或删除,和区间的移动。


1.1	节点的定义
template < class T>
class SplayTreeNode{
    public:
        T key;                // 关键字(键值)
        SplayTreeNode *left;    // 左孩子
        SplayTreeNode *right;    // 右孩子


        SplayTreeNode():left(NULL),right(NULL) {}

        SplayTreeNode(T value, SplayTreeNode *l, SplayTreeNode *r):
            key(value), left(l),right(r) {}
};
1.2 伸展树:
template < class T>
class SplayTree {
    private:
        SplayTreeNode< T> *mRoot;    // 根结点
    public:
        SplayTree();
        ~SplayTree();
        // 前序遍历"伸展树"
        void preOrder();
        // 中序遍历"伸展树"
        void inOrder();
        // 后序遍历"伸展树"
        void postOrder();
        // (递归实现)查找"伸展树"中键值为key的节点
        SplayTreeNode< T>* search(T key);
        // (非递归实现)查找"伸展树"中键值为key的节点
        SplayTreeNode< T>* iterativeSearch(T key);
        // 查找最小结点:返回最小结点的键值。
        T minimum();
        // 查找最大结点:返回最大结点的键值。
        T maximum();
        // 旋转key对应的节点为根节点,并返回值为根节点。
        void splay(T key);
        // 将结点(key为节点键值)插入到伸展树中
        void insert(T key);
        // 删除结点(key为节点键值)
        void remove(T key);
        // 销毁伸展树
        void destroy();
        // 打印伸展树
        void print();
    private:
        // 前序遍历"伸展树"
        void preOrder(SplayTreeNode< T>* tree) const;
        // 中序遍历"伸展树"
        void inOrder(SplayTreeNode< T>* tree) const;
        // 后序遍历"伸展树"
        void postOrder(SplayTreeNode< T>* tree) const;
        // (递归实现)查找"伸展树x"中键值为key的节点
        SplayTreeNode< T>* search(SplayTreeNode< T>* x, T key) const;
        // (非递归实现)查找"伸展树x"中键值为key的节点
        SplayTreeNode< T>* iterativeSearch(SplayTreeNode< T>* x, T key) const;
        // 查找最小结点:返回tree为根结点的伸展树的最小结点。
        SplayTreeNode< T>* minimum(SplayTreeNode< T>* tree);
        // 查找最大结点:返回tree为根结点的伸展树的最大结点。
        SplayTreeNode< T>* maximum(SplayTreeNode< T>* tree);
        // 旋转key对应的节点为根节点,并返回值为根节点。
        SplayTreeNode< T>* splay(SplayTreeNode< T>* tree, T key);

        // 将结点(z)插入到伸展树(tree)中
        SplayTreeNode< T>* insert(SplayTreeNode< T>* &tree, SplayTreeNode< T>* z);

        // 删除伸展树(tree)中的结点(键值为key),并返回被删除的结点
        SplayTreeNode< T>* remove(SplayTreeNode< T>* &tree, T key);

        // 销毁伸展树
        void destroy(SplayTreeNode< T>* &tree);

        // 打印伸展树
        void print(SplayTreeNode< T>* tree, T key, int direction);
};
2 旋转
/*
 * 旋转key对应的节点为根节点,并返回值为根节点。
 * 注意:
 *   (a):伸展树中存在"键值为key的节点"。
 *          将"键值为key的节点"旋转为根节点。
 *   (b):伸展树中不存在"键值为key的节点",并且key < tree->key。
 *      b-1 "键值为key的节点"的前驱节点存在的话,将"键值为key的节点"的前驱节点旋转为根节点。
 *      b-2 "键值为key的节点"的前驱节点存在的话,则意味着,key比树中任何键值都小,那么此时,将最小节点旋转为根节点。
 *   (c):伸展树中不存在"键值为key的节点",并且key > tree->key。
 *      c-1 "键值为key的节点"的后继节点存在的话,将"键值为key的节点"的后继节点旋转为根节点。
 *      c-2 "键值为key的节点"的后继节点不存在的话,则意味着,key比树中任何键值都大,那么此时,将最大节点旋转为根节点。
 */


template < class T>
SplayTreeNode< T>* SplayTree< T>::splay(SplayTreeNode< T>* tree, T key)
{
    SplayTreeNode< T> N, *l, *r, *c;

    if (tree == NULL)
        return tree;

    N.left = N.right = NULL;
    l = r = & N;
    for (;;)
    {
        if (key < tree->key)
        {
            if (tree->left == NULL)
                break;
            if (key < tree->left->key)
            {
                c = tree->left;                           /* rotate right */
                tree->left = c->right;
                c->right = tree;
                tree = c;
                if (tree->left == NULL)
                    break;
            }
            r->left = tree;                     /* link right */
            r = tree;
            tree = tree->left;
        }
        else if (key > tree->key)
        {
            if (tree->right == NULL)
                break;
            if (key > tree->right->key)
            {
                c = tree->right;           	  /* rotate left */
                tree->right = c->left;
                c->left = tree;
                tree = c;
                if (tree->right == NULL)
                    break;
            }
            l->right = tree;         		/* link left */
            l = tree;
            tree = tree->right;
        }
        else
        {
            break;
        }
    }

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

    return tree;
}

template < class T>
void SplayTree< T>::splay(T key)
{
    mRoot = splay(mRoot, key);
}
3 节点的插入
/*
 * 将结点插入到伸展树中,并返回根节点
 *
 * 参数说明:
 *     tree 伸展树的根结点
 *     key 插入的结点的键值
 * 返回值:
 *     根节点
 */
template < class T>
SplayTreeNode< T>* SplayTree< T>::insert(SplayTreeNode< T>* &tree, SplayTreeNode< T>* z)
{
    SplayTreeNode< T> *y = NULL;
    SplayTreeNode< T> *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
        {
            cout << "不允许插入相同节点(" << z->key << ")!" << endl;
            delete z;
            return tree;
        }
    }

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

    return tree;
}

template < class T>
void SplayTree< T>::insert(T key)
{
    SplayTreeNode< T> *z=NULL;

    // 如果新建结点失败,则返回。
    if ((z=new SplayTreeNode< T>(key,NULL,NULL)) == NULL)
        return ;

    // 插入节点
    mRoot = insert(mRoot, z);
    // 将节点(key)旋转为根节点
    mRoot = splay(mRoot, key);
}
4 节点的删除
/*
 * 删除结点(节点的键值为key),返回根节点
 *
 * 参数说明:
 *     tree 伸展树的根结点
 *     key 待删除结点的键值
 * 返回值:
 *     根节点
 */
template < class T>
SplayTreeNode< T>* SplayTree< T>::remove(SplayTreeNode< T>* &tree, T key)
{
    SplayTreeNode< T> *x;

    if (tree == NULL)
        return NULL;

    // 查找键值为key的节点,找不到的话直接返回。
    if (search(tree, key) == NULL)
        return tree;

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

    if (tree->left != NULL)
    {
        // 将"tree的前驱节点"旋转为根节点
        x = splay(tree->left, key);
        // 移除tree节点
        x->right = tree->right;
    }
    else
        x = tree->right;

    delete tree;

    return x;

}

template < class T>
void SplayTree< T>::remove(T key)
{
    mRoot = remove(mRoot, key);
}








可视化