伸展树的概念及C++实现

伸展树的概念

伸展树是一种二叉树,它能够保证M次操作最多花费O(M log N)的时间,即摊还代价(即平分到每一次操作的代价)为O(log N)。

前面的博客讲过了的AVL树:

浅析AVL树–AVL树的概念及单旋转
浅析AVL树–AVL树的双旋转
浅析AVL树–AVL树的C++实现

AVL树能够保证每一次操作后树都会平衡(即任意结点左右子树最多差1)

在《数据结构与算法分析》一书中有这么一段话:

对于二叉查找树来说,每次操作的最坏情形时间O(N)并不坏,只要它相对不常发生就行。

这也是伸展树为什么存在的原因—-伸展树的基本想法是,每次访问一个元素,都要通过一系列的AVL旋转操作将其旋转到根部位置 — 方便下次访问(很有可能下次还是访问的同一个元素)。

我们很容易想到这样一个策略:访问到元素X时,不停地将X与其父节点进行旋转,直至X旋转到根节点的位置,但是这样的策略有问题—如下图,按顺序插入6,5,4,3,2,1,再倒序访问各个元素:


可以发现,在访问结点1的时候会将结点2推向树尾的位置,访问在访问结点2的时候又会将结点3推向树尾的位置。。。。如此按顺序访问1,2,3,4,5,6的时间复杂度会是O(N^2^)。因此这个策略不够好。

伸展树的展开操作类似于上面的策略,但是会尽量避免上述情况的发生。

伸展树的展开分为以下三种情况(细分之下可以分为6中小情况):

I . 当前结点X的父节点就是树根,那么只需将X与父节点做一次单旋转即可。根据X是父节点的左儿子还是右儿子可以细分为单左旋转和单右旋转。

II . 当前结点X呈现出“之”字形(ZigZag),如下图所示,这时候对根据X属于哪一种情况对X进行LR双旋转或者RL双旋转就可以。

III . 当前结点呈现”一”字形(ZigZig),这种情况下,需要对3个结点进行旋转:

首先是伸展树的类定义:

//File:SplayTree.h
class SplayTree {
public:
    typedef struct __node{
        struct __node *left,*right,*parent;
        int data;
    } TreeNode;
    typedef TreeNode * Position;
    void insert(int data);
    Position find(int data);
    Position visit(int data);
    SplayTree();

public:
    Position headnode;
    Position doSingleL(Position p);  //以p->left为旋转中心将p右旋
    Position doSingleR(Position p);  //以p->right为旋转中心将p左旋
    Position doRL(Position p);
    Position doLR(Position p);
    Position doZigZigL(Position p);
    Position doZigZigR(Position p);
};

然后是伸展树的具体实现:

//File:SplayTree.cpp
#include "SplayTree.h"

SplayTree::SplayTree():headnode(nullptr) {

}

void SplayTree::insert(int data) {
    Position* curr = &headnode;
    Position lastPatent = nullptr;
    bool lastIsLeft=false;
    while(*curr && (*curr)->data!=data)
    {
        if(data < (*curr)->data)
        {
            lastPatent = *curr;
            curr = &((*curr) -> left);
            lastIsLeft = true;
        }
        else
        {
            lastPatent = *curr;
            curr = &((*curr) -> right);
            lastIsLeft = false;
        }
    }
    if(!*curr)
    {
        *curr = new TreeNode;
        (*curr) -> data = data;
        (*curr) -> left = (*curr) -> right = nullptr;
        if(lastPatent)
        {
            if(lastIsLeft)
                lastPatent -> left = *curr;
            else
                lastPatent -> right = *curr;
        }

        (*curr)->parent = lastPatent;

    }
}

SplayTree::Position SplayTree::find(int data) {
    Position curr = headnode;

    while(curr && curr->data!=data)
    {
        if(data < curr->data)
            curr = curr -> left;
        else
            curr = curr -> right;
    }

    return curr;
}
SplayTree::Position SplayTree::visit(int data) {
    Position curr = find(data);

    while(curr!=headnode)
    {
        if(curr && headnode->left == curr)
            curr = doSingleL(headnode);
        else if (curr && headnode->right == curr)
            curr = doSingleR(headnode);
        else if(curr->parent->left == curr && curr->parent->parent->right == curr->parent)
            curr = doRL(curr->parent->parent);
        else if(curr->parent->right == curr && curr->parent->parent->left == curr->parent)
            curr = doLR(curr->parent->parent);
        else if (curr->parent->left == curr && curr->parent->parent->left == curr->parent)
            curr = doZigZigL(curr->parent->parent);
        else
            curr = doZigZigR(curr->parent->parent);
    }

    return curr;
}
//调用条件:L型子树
//INPUT: 需要进行单右旋的结点子树
//OUTPUT: 旋转完成后的子树根节点
//NEED TO DO:旋转、更新parent的left指针、更新原根节点的parnet指针、更新现根节点的parent指针、
//从q->left挪到p->right结点的parent
SplayTree::Position SplayTree::doSingleL(SplayTree::Position p) {
    Position parent = p -> parent;
    Position q = p -> left;

    p->left = q->right;
    if(p->left)
        p->left->parent=p;
    q->right = p;

    p->parent = q;
    q->parent=parent;
    if(parent && parent->left == p)
        parent->left = q;
    else if (parent && parent->right == p)
        parent->right = q;
    else if(!parent)
        headnode = q;
    return q;
}
//调用条件:R型子树
//INPUT: 需要进行单左旋的结点子树
//OUTPUT: 旋转完成后的子树根节点
//NEED TO DO:旋转、更新parent的left指针、更新原根节点的parnet指针、更新现根节点的parent指针、
//从q->left挪到p->right结点的parent
SplayTree::Position SplayTree::doSingleR(SplayTree::Position p) {
    Position parent = p -> parent;
    Position q = p -> right;

    p->right = q->left;
    if(p->right)
        p->right->parent=p;
    q->left = p;

    p->parent = q;
    q->parent=parent;
    if(parent && parent->left == p)
        parent->left = q;
    else if (parent && parent->right == p)
        parent->right = q;
    else if(!parent)
        headnode = q;
    return q;
}
SplayTree::Position SplayTree::doRL(SplayTree::Position p) {
    p->right = doSingleL(p->right);
    return doSingleR(p);
}
SplayTree::Position SplayTree::doLR(SplayTree::Position p) {
    p->left = doSingleR(p->left);
    return doSingleL(p);
}
SplayTree::Position SplayTree::doZigZigL(SplayTree::Position p) {
    p = doSingleL(p);
    p = doSingleL(p);
    return p;
}
SplayTree::Position SplayTree::doZigZigR(SplayTree::Position p) {
    p = doSingleR(p);
    p = doSingleR(p);
    return p;
}

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

Protected with IP Blacklist CloudIP Blacklist Cloud

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据