zoukankan      html  css  js  c++  java
  • 伸展树

    伸展树(Splay Tree)树平衡二叉查找树的一种,具有二叉查找树的所有性质。在性能上又比普通的二叉查找树有所改进:普通的二叉查找树在最坏情况下的查找操作的时间复杂度为O(n)(当二叉树退化成一条链的时候),而伸展树在任何情况下的平摊时间复杂度均为 O(log2n).

    特性

    1. 和普通的二叉查找树相比,具有任何情况下、任何操作的平摊O(log2n)的复杂度,时间性能上更好
    2. 和一般的平衡二叉树比如 红黑树、AVL树相比,维护更少的节点额外信息,空间性能更优,同时编程复杂度更低
    3. 在很多情况下,对于查找操作,后面的查询和之前的查询有很大的相关性。这样每次查询操作将被查到的节点旋转到树的根节点位置,这样下次查询操作可以很快的完成
    4. 可以完成对区间的查询、修改、删除等操作,可以实现线段树和树状数组的所有功能 
       

    旋转

        伸展树实现O(log2n)量级的平摊复杂度依靠每次对伸展树进行查询、修改、删除操作之后,都进行旋转操作 Splay(x, root),该操作将节点x旋转到树的根部。 
        伸展树的旋转有六种类型,如果去掉镜像的重复,则为三种:zig(zag)、zig-zig(zag-zag)、zig-zag(zag-zig)。

    1 自底向上的方式进行旋转

        这种方式需要每个节点存放其父节点的指

    1.1 zig旋转

    zig旋转 
        如图所示,x节点的父节点为y,x为y的左子节点,且y节点为根。则只需要对x节点进行一次右旋(zig操作),使之成为y的父节点,就可以使x成为伸展树的根节点。

    1.2 zig-zig旋转

    zig-zig旋转 
        如上图所示,x节点的父节点y,y的父节点z,三者在一字型链上。此时,先对y节点和z节点进行zig旋转,然后再对x节点和y节点进行zig旋转,最后变为右图所示,x成为y和z的祖先节点。

    1.3 zig-zag旋转

    zig-zag旋转 
        如上图所示,x节点的父节点y,y的父节点z,三者在之字型链上。此时,先对x节点和y节点进行zig旋转,然后再对x节点和y节点进行zag旋转,最后变为右图所示,x成为y和z的祖先节点。

    2 自顶向下的方式进行旋转

        这种方式不需要节点存储其父节点的指针。当我们沿着树向下搜索某个节点x时,将搜索路径上的节点及其子树移走。构建两棵临时的树——左树和右树。没有被移走的节点构成的树称为中树。

    (1) 当前节点x是中树的根 
    (2) 左树L保存小于x的节点 
    (3) 右树R保存大于x的节点

        开始时候,x是树T的根,左树L和右树R都为空。三种旋转操作:

    2.1 zig旋转

    zig旋转 
        如图所示,x节点的子节点y就是我们要找的节点,则只需要对y节点进行一次右旋(zig操作),使之成为x的父节点,就可以使y成为伸展树的根节点。将y作为中树的根,同时,x节点移动到右树R中,显然右树上的节点都大于所要查找的节点。

    2.2 zig-zig旋转

    zig-zig旋转 
        如上图所示,x节点的左子节点y,y的左子节点z,三者在一字型链上,且要查找的节点位于z节点为根的子树中。此时,对x节点和y节点进行zig,然后对z和y进行zig,使z成为中树的根,同时将y及其子树挂载到右树R上。

    2.3 zig-zag旋转

    zig-zag旋转 
        如上图所示,x节点的左子节点y,y的右子节点z,三者在之字型链上,且需要查找的元素位于以z为根的子树上。此时,先对x节点和y节点进行zig旋转,将x及其右子树挂载到右树R上,此时y成为中树的根节点;然后再对z节点和y节点进行zag旋转,使得z成为中树的根节点。

    2.4 合并

    合并 
        最后,找到节点或者遇到空节点之后,需要对左、中、右树进行合并。如图所示,将左树挂载到中树的最左下方(满足遍历顺序要求),将右树挂载到中树的最右下方(满足遍历顺序要求)。

     

    父节点向左到左子节点-> zig

    父节点向右到右子节点->zag

    举例说明旋转操作

    Original

    zig-zag (double rotation)

    zig-zig

    zig (single rotation at root)

    伸展树的基本操作

        利用Splay操作,可以在伸展树上进行如下操作: 
    (1) Find(x, S) 判断x是否在伸展树S表示的有序集中 
        首先按照普通的二叉查找树查找算法进行查找,如果找到元素x,则执行Splay(x, S)操作将x旋转到树根的位置。

    (2) Insert(x, S) 将元素x插入到树中 
        首先按照普通的二叉查找树插入算法进行插入,然后执行Splay(x, S)

    (3) Delete(x, S) 将元素x从伸展树S所表示的有序集中删除 
        首先按照普通的二叉查找树查找算法找到x的位置。如果x没有孩子或只有一个孩子,则直接将x删除,并通过Splay操作,将x节点的父节点调整到伸展树的根节点处。否则,向下查找x的后继节点y,用y替代x的位置,然后执行Splay(y, S),将y调整为伸展树的根

    (4) Join(S1, S2) 将两个伸展树S1, S2合并为一个伸展树。其中S1的所有元素小于S2中的所有元素。 
    join 
        首先按照普通的二叉查找树查找算法找到S1中最大元素x,然后执行Splay(x, S1)将x旋转到S1的根部,此时S1中的所有元素必然在x的左子树上,x的右子树为空,则可以将S2挂载到x的右子树位置。

    (5) Split(x, S) 以x为界,将伸展树S分离为两棵伸展树S1和S2,其中S1中所有元素都小于x,S2中所有元素都大于x。 
    Split

        首先执行Find(x, S),将元素x调整为伸展树的根节点,则x的左子树就是S1,右子树就是S2.

    伸展树Splay(x,S)实现(c++)

    1 自底向上的旋转方式
    struct TreeNode{
        int data;
        TreeNode* left;
        TreeNode* right;
        TreeNode* parent;
        TreeNode(int d) :
            data(d), left(NULL), right(NULL), parent(NULL){};
    };
    
    TreeNode* gTreeRoot;
    void Rotate(TreeNode* x, bool left_rotate){ //旋转x节点(将x节点 按照 left_rotate 指示 绕着其父节点y 进行左旋或者右旋
        TreeNode* y = x->parent;
        if (y == NULL){
            return;
        }
    
        if (left_rotate){
            y->right = x->left;
            if (!x->left){
                x->left->parent = y;
            }
        }
        else{
            y->left = x->right;
            if (!x->right){
                x->right->parent = y;
            }
        }
    
        x->parent = y->parent;
        if (!y->parent){
            if (y == y->parent->left){
                y->parent->left = x;
            }
            else{
                y->parent->right = x;
            }
        }
    
        if (y == gTreeRoot){ //全局的根节点
            gTreeRoot = x;
        }
    }
    
    //将节点x通过不断的Rotate操作,直到x成为f的子节点
    void Splay(TreeNode* x, TreeNode* f){
        TreeNode* y = x->parent, *z = NULL;
        while (y != f){
            z = y->parent;
            if (z == f){
                Rotate(x, x == y->right);
            }
            else{
                if (!(x == y->left ^ y == z->left)){ //一字型 旋转 zig-zig
                    Rotate(y, y == z->right);
                    Rotate(x, x == y->right);
                }
                else{ //之字型旋转 zig-zag
                    Rotate(x, x == y->right);
    
                    //注意,上一步的rotate操作,x的地址没有发生改变,但是x地址指向的结构体中的各个域被修改为经过旋转之后的结构
                    //所有,这里直接使用x即可
                    Rotate(x, x == z->right);                 
                }
            }
        }
    }

     2 自顶向下的方式旋转

    struct TreeNode{
        int data;
        TreeNode* left;
        TreeNode* right;
        TreeNode(int d = 0) :
            data(d), left(NULL), right(NULL){};
    };
    
    TreeNode* Splay(int i, TreeNode* t){
        TreeNode N, *l, *r, *y; // l, t, r 分别为左树、中树、右树
        if (t == NULL){
            return;
        }
        l = r = &N;            
        while (true){
            if (i < t->data){
                if (t->left == NULL){ //碰到空节点,结束
                    break;
                }
                if (i < t->left->data){    //需要进行右旋
                    y = t->left;
                    t->left = y->right;
                    y->right = t;
                    t = y;
                    if (t->left == NULL){
                        break;
                    }
                }
                r->left = t;    //挂载到右树,最小的位置
                r = t;
                t = t->left; //将 z 升为中树的根节点
            }
            else if (i > t->data){
                if (t->right == NULL){
                    break;
                }
                if (i > t->right->data){ //需要进行左旋
                    y = t->right;
                    t->right = y->left;
                    y->left = t;
                }
                l->right = t;    //挂载到左树,最大的位置
                l = t;
                t = t->right;  //将z升为中树的根节点
            }
            else{
                break;
            }
        }
        l->right = t->left;  //将左、中、右树进行合并
        r->left = t->right;
        t->left = N.right;
        t->right = N.left;
        return t;
    }
  • 相关阅读:
    写给实习生的第一天
    写给实习生的第一天
    写给实习生的第一天
    老师不能把你怎样,但外面的世界可以!
    老师不能把你怎样,但外面的世界可以!
    adjA=(detA)A-1
    如果它仅对输入0才得到输出0
    isotropic trace
    detAB=detAdetB
    解释 纯量矩阵
  • 原文地址:https://www.cnblogs.com/gtarcoder/p/4706088.html
Copyright © 2011-2022 走看看