伸展树详解及实现(C语言)

1.什么是伸展树

简单的说,伸展树是一棵二叉查找树,对每个节点的访问,都要将该节点通过旋转操作,把它翻转到根处。然后进行操作。下图是访问关键字1的过程(自底向上翻转)。可以看到伸展的效果可以让树的深度变小了,这是伸展树的好处所在。另外,当执行其他操作,如插入和删除,也要先进行类似的伸展。
伸展树详解及实现(C语言)_第1张图片

2.自顶向下伸展树
前面简单地说明自底向上的伸展方式,下面要详细介绍自顶向下的伸展方式。一般的自顶向下的访问树是可以通过迭代来实现,而自底向上访问树一般需要递归来实现。使用迭代可以使得程序更快,但是代码相对较难实现,不过此处伸展树的实现还是相对好理解的,好实现的。
有三种情况的旋转,我们称之为单旋转,一字型旋转和之字型旋转。在访问的任一时刻,我们都有一个当前节点X,它是其子树的根;在我们的图中它被表示成“中间”树。树L(左边树)存放在树T中小于X的节点,但不存储X的子树中的节点;类似的,树R(右边树)存储大于X的节点,但不存储X的子树的节点。初始时X我T的根,而L和R是空树。
如果旋转是一次单旋转,那么根在Y的树变成中间树的新根。X和子树B连接而成为R中最小项的左儿子;X的左儿子逻辑上成为NULL。结果,X成为R的新的最小项。特别要注意,为使单旋转情形适用,Y不一定必须是一片树叶。如果我们查找小于Y的一项,而Y没有左儿子,那么这种单旋转情形将是适用的。
伸展树详解及实现(C语言)_第2张图片
单旋转

类似的一字型(X,Y,Z成一字)的旋转如下:
伸展树详解及实现(C语言)_第3张图片
一字型旋转

下面是之字形(X,Y,Z成之字)旋转:
伸展树详解及实现(C语言)_第4张图片
之字形旋转

其中之字形的旋转可以采用一种简化的方式,如下图。此时Z不再是中间树,Y取而代之。简化后的之字形旋转变成和单旋转一样了,可以让变成更加简单。
伸展树详解及实现(C语言)_第5张图片
简化之字形旋转

一旦展开完成,最后一步就是把三棵树连接起来形成一棵树,如下图所示。
伸展树详解及实现(C语言)_第6张图片

作为例子,下面演示访问19的伸展过程。
伸展树详解及实现(C语言)_第7张图片
简化的之字形旋转

伸展树详解及实现(C语言)_第8张图片
之字形旋转
伸展树详解及实现(C语言)_第9张图片
单旋转
伸展树详解及实现(C语言)_第10张图片
整理
伸展树详解及实现(C语言)_第11张图片
3.实现
3.1 初始化、伸展、插入、删除
(1)初始化(Initialize)
使用标记NullNode表示一个NULL指针。
/* 初始化 */
PSplayTree Initialize(){
    if (NullNode == NULL){
        NullNode = malloc(sizeof(struct SplayTree));
        if (NullNode == NULL)
            exit(EXIT_FAILURE);
        NullNode->left = NullNode->right = NullNode;
    }
    
    return NullNode;
}


(2)伸展(Splay)
伸展的方法,使用一个结构体变量Header来保存左树和右树。其中Header.Left和Header.Right分别指向右树R和左树L,这不是错误,而是遵从指针的指向。
/* 根据关键值Item展开 */
static PSplayTree Splay(PSplayTree X, ElemType Item){
    /* 左边树与右边树 */
    static struct SplayTree header;
    /* 左边树最大值与右边树最小值 */
    static PSplayTree LeftTreeMax,RightTreeMin;
    
    header.left = header.right = NullNode;
    LeftTreeMax = RightTreeMin = &header;
    
    /* NullNode赋值为Item,当查找到Item值或者进行值树的底部时终止循环 */
    NullNode->elem = Item;
   
    while (Item != X->elem) {
        if (Item < X->elem){
            if (Item < X->left->elem)
                X = SingleRotateLeft(X);
            /* 到达底部 */
            if (X->left == NullNode)
                break;
            /* 链接右边 */
            RightTreeMin->left = X;
            RightTreeMin = X;
            X = X->left;
        }else{
            if (Item > X->right->elem)
                X = SingleRotateRight(X);
            if (X->right == NullNode)
                break;
            /* 链接左边 */
            LeftTreeMax->right = X;
            LeftTreeMax = X;
            X = X->right;
        }
    }
    
    /* 组装 */
    LeftTreeMax->right = X->left;
    RightTreeMin->left = X->right;
    X->left = header.right;
    X->right = header.left;
    
    
    return X;
}


(3)插入
一个新的指针被分配,且如果T是空的,那么建立一棵单节点树。否则,我们围绕Item进行展开T。如果新根的数据等于Item,不用插入。如果T的新根大于Item的值,那么T的新根和它的右子树变成NewNode的右子树。而T的左子树则成为NewNode的左子树。如果T的新根包含小于Item的值,那么执行类似的逻辑。在这两种情况下,NewNode均成为新根。
/* 插入 */
PSplayTree Insert(PSplayTree T,ElemType Item){
    PSplayTree NewNode;
    NewNode = malloc(sizeof(struct SplayTree));
    if (NewNode == NULL)
        exit(EXIT_FAILURE);
    NewNode->elem = Item;
    
    if (T == NullNode) {
        NewNode->left = NewNode->right = NullNode;
        T = NewNode;
    }else{
        T = Splay(T, Item);
        
        if (Item < T->elem){
            NewNode->left = T->left;
            NewNode->right = T;
            T->left = NullNode;
            T = NewNode;
        }else if (Item > T->elem){
            NewNode->right = T->right;
            NewNode->left = T;
            T->right = NullNode;
            T = NewNode;
        }else if(Item == T->elem){
            /* 重复值不做插入 */
            free(NewNode);
        }

    }
    
    
    return T;
    
}


(4)删除
通过访问要被删除的节点实行删除。这种操作把节点推到根上。如果删除该节点得到左树L和右树R。我们找到左树L中最大的元素,那么这个元素成为左树L的根,此时左树L没有右子树,就把右树R作为L的右子树。
/* 删除 */
PSplayTree Remove(PSplayTree T,ElemType Item){
    PSplayTree NewTree;
    if (T != NullNode){
        T = Splay(T, Item);
        if (Item == T->elem){
            /* 找到该项 */
            if (T->left == NullNode){
                /* 左子树是空,右子树成为新树 */
                NewTree = T->right;
                
            }else{
                /* 左子树成为新树根 对新树展开,由于Item大于新树的任何节点。所以展开的过程会沿着有路径进行 */
                /* 一直到最右边的树叶节点,所以展开之后得到的树,其右子树肯定是空树 */
                NewTree = T->left;
                NewTree = Splay(NewTree, Item);
                NewTree->right = T->right;
            }
            free(T);
            T = NewTree;
        }
    }
    
    return T;
}


3.2 源代码
3.2.1 头文件
//
//  SplayTree.h
//  SplayTree
//
//  Created by Wuyixin on 2017/6/30.
//  Copyright © 2017年 Coding365. All rights reserved.
//

#ifndef SplayTree_h
#define SplayTree_h

#include 
#include 
typedef struct SplayTree *PSplayTree;
typedef int ElemType;

struct SplayTree {
    ElemType elem;
    PSplayTree left;
    PSplayTree right;
};

static PSplayTree NullNode;

/* 初始化 */
PSplayTree Initialize();
/* 插入 */
PSplayTree Insert(PSplayTree T,ElemType Item);
/* 删除 */
PSplayTree Remove(PSplayTree T,ElemType Item);
/* 遍历 */
void Travel(PSplayTree T);

#endif /* SplayTree_h */


3.2.2 实现文件
//
//  SplayTree.c
//  SplayTree
//
//  Created by Wuyixin on 2017/6/30.
//  Copyright © 2017年 Coding365. All rights reserved.
//

#include "SplayTree.h"

/* 根据关键值Item展开 */
static PSplayTree Splay(PSplayTree T, ElemType Item);
/* 左旋转 */
static PSplayTree SingleRotateLeft(PSplayTree T);
/* 右旋转 */
static PSplayTree SingleRotateRight(PSplayTree T);

/* 根据关键值Item展开 */
static PSplayTree Splay(PSplayTree X, ElemType Item){
    /* 左边树与右边树 */
    static struct SplayTree header;
    /* 左边树最大值与右边树最小值 */
    static PSplayTree LeftTreeMax,RightTreeMin;
    
    header.left = header.right = NullNode;
    LeftTreeMax = RightTreeMin = &header;
    
    /* NullNode赋值为Item,当查找到Item值或者进行值树的底部时终止循环 */
    NullNode->elem = Item;
   
    while (Item != X->elem) {
        if (Item < X->elem){
            if (Item < X->left->elem)
                X = SingleRotateLeft(X);
            /* 到达底部 */
            if (X->left == NullNode)
                break;
            /* 链接右边 */
            RightTreeMin->left = X;
            RightTreeMin = X;
            X = X->left;
        }else{
            if (Item > X->right->elem)
                X = SingleRotateRight(X);
            if (X->right == NullNode)
                break;
            /* 链接左边 */
            LeftTreeMax->right = X;
            LeftTreeMax = X;
            X = X->right;
        }
    }
    
    /* 组装 */
    LeftTreeMax->right = X->left;
    RightTreeMin->left = X->right;
    X->left = header.right;
    X->right = header.left;
    
    
    return X;
}

/* 左旋转 */
static PSplayTree SingleRotateLeft(PSplayTree T){
    PSplayTree T2 = T->left;
    T->left = T2->right;
    T2->right = T;
    
    return T2;
}
/* 右旋转 */
static PSplayTree SingleRotateRight(PSplayTree T){
    PSplayTree T2 = T->right;
    T->right = T2->left;
    T2->left = T;
    return T2;
}

/* 初始化 */
PSplayTree Initialize(){
    if (NullNode == NULL){
        NullNode = malloc(sizeof(struct SplayTree));
        if (NullNode == NULL)
            exit(EXIT_FAILURE);
        NullNode->left = NullNode->right = NullNode;
    }
    
    return NullNode;
}
/* 插入 */
PSplayTree Insert(PSplayTree T,ElemType Item){
    PSplayTree NewNode;
    NewNode = malloc(sizeof(struct SplayTree));
    if (NewNode == NULL)
        exit(EXIT_FAILURE);
    NewNode->elem = Item;
    
    if (T == NullNode) {
        NewNode->left = NewNode->right = NullNode;
        T = NewNode;
    }else{
        T = Splay(T, Item);
        
        if (Item < T->elem){
            NewNode->left = T->left;
            NewNode->right = T;
            T->left = NullNode;
            T = NewNode;
        }else if (Item > T->elem){
            NewNode->right = T->right;
            NewNode->left = T;
            T->right = NullNode;
            T = NewNode;
        }else if(Item == T->elem){
            /* 重复值不做插入 */
            free(NewNode);
        }

    }
    
    
    return T;
    
}
/* 删除 */
PSplayTree Remove(PSplayTree T,ElemType Item){
    PSplayTree NewTree;
    if (T != NullNode){
        T = Splay(T, Item);
        if (Item == T->elem){
            /* 找到该项 */
            if (T->left == NullNode){
                /* 左子树是空,右子树成为新树 */
                NewTree = T->right;
                
            }else{
                /* 左子树成为新树根 对新树展开,由于Item大于新树的任何节点。所以展开的过程会沿着有路径进行 */
                /* 一直到最右边的树叶节点,所以展开之后得到的树,其右子树肯定是空树 */
                NewTree = T->left;
                NewTree = Splay(NewTree, Item);
                NewTree->right = T->right;
            }
            free(T);
            T = NewTree;
        }
    }
    
    return T;
}


void Travel(PSplayTree T){
    if (T != NullNode){
        Travel(T->left);
        printf("%d ",T->elem);
        Travel(T->right);
    }
}




3.2.3 调用
//
//  main.c
//  SplayTree
//
//  Created by Wuyixin on 2017/6/30.
//  Copyright © 2017年 Coding365. All rights reserved.
//

#include 
#include "SplayTree.h"
int main(int argc, const char * argv[]) {
    
    PSplayTree T = Initialize();
    T = Insert(T, 1);
    T = Insert(T, 2);
    T = Insert(T, 3);
    T = Insert(T, 4);
    T = Insert(T, 5);
    T = Insert(T, 6);
    T = Remove(T, 1);
    T = Remove(T, 2);
    T = Remove(T, 1);
    Travel(T);
    return 0;
}



你可能感兴趣的:(数据结构与算法分析(C语言))