自顶而下伸展树 数据结构说解

自顶而下伸展树的定义

伸展树是一棵二叉搜索树,但不保存每个节点的平衡信息,也没有特定的规则来保证伸展树的平衡性,而是在维护操作中通过将待维护的节点伸展到根来维持平衡。由于笔者在本文中实现的是自顶而下伸展树,故树上的每个节点不保存其父节点,只保存左右子节点和权值以提高效率。

对于自顶而下伸展树的定义代码如下:

#define NIL 0
#define ST_ROOT STree[NIL].left
struct SPLAY_TREE_NODE
{
    int key,left,right;
}STree[MAX_N];

伸展树自顶而下的单元素伸展维护

若是伸展树自底而上的实现,则伸展操作无异于通过一系列的旋转操作,将待伸展节点旋转到根节点并返回。但是,由于每个节点未保存其父节点,故自顶而下的伸展不能也无需这样做。同样,由于自顶而下伸展树未保存父节点的原因,故笔者在其代码实现中也省去了不必要的旋转操作,而是使用一种称之为半伸展(semisplay)的方法保证伸展操作的正确性。

我们通过在向下寻找待伸展节点的过程中,将原树划分成三个部分,分别记为 LeftTree、MiddleTree 和 RightTree,其中,LeftTree 存储所有位于当前查找点上方,且权值小于待伸展节点权值的点所构成的重构树,MiddleTree 存储以当前查找点为根的子树,RightTree 存储所有位于当前查找点上方,且权值大于待伸展节点权值的点所构成的重构树。当当前查找点等于待伸展节点时,整个划分过程结束。

每次划分中,若待伸展节点位于当前查找点左子树,则将当前查找点及其右子树从 MiddleTree 并入 RightTree 中;若待伸展节点位于当前查找点右子树,则将当前查找点及其左子树从 MiddleTree 并入 LeftTree 中。在自顶而下构建 LeftTree 的过程中,我们可以发现,先并入节点的权值一定小于后并入节点的权值;同样,RightTree 中先并入节点的权值一定大于后并入节点的权值。

这样,我们记录下 LeftTree 中权值最大的点 LeftTreeMax,可知 LeftTreeMax 的右子节点一定为空(否则不满足中序遍历有序),而将待并入节点及其左子树并入 LeftTreeMax 的右子节点,更新 LeftTreeMax 完成 LeftTree 的重构(此时新 LeftTreeMax 即为待并入节点,其原右子树为新 MiddleTree,划分后右子节点为空);类似的,我们记录下 RightTree 中权值最小的点 RightTreeMin,可知 RightTreeMin 的左子节点一定为空(否则不满足中序遍历有序),而将待并入节点及其右子树并入 RightTreeMin 的左子节点,更新 RightTreeMin 完成 RightTree 的重构(此时新 RightTreeMin 即为待并入节点,其原左子树为新 MiddleTree,划分后左子节点为空)。每次将当前查找点下移,直至到达待伸展节点。

在最后的合并中,我们将待伸展节点的左子树并入 LeftTreeMax 的右子节点,而将待伸展节点的右子树并入 RightTreeMin 的左子节点,并将整个 LeftTree 并入待伸展节点的左子节点,将整个 RightTree 并入待伸展节点的右子节点,完成对整个树的重构。显然,重构后的树满足其中序遍历与原树的中序遍历相同,且待伸展节点位于重构树的根。这样即可无旋转地完成伸展树自顶而下的伸展操作。

对于伸展树自顶而下的单元素伸展维护代码如下:

int ST_Splay(int now,int key)
{
    int Header=ST_NewNode(),LeftTreeMax=Header,RightTreeMin=Header;
    while(key!=STree[now].key)
        if(key<STree[now].key)
        {
            if(STree[now].left==NIL)
                break;
            STree[RightTreeMin].left=now;
            RightTreeMin=now;
            now=STree[now].left;
        }
        else
        {
            if(STree[now].right==NIL)
                break;
            STree[LeftTreeMax].right=now;
            LeftTreeMax=now;
            now=STree[now].right;
        }
    STree[LeftTreeMax].right=STree[now].left;
    STree[RightTreeMin].left=STree[now].right;
    STree[now].left=STree[Header].right;
    STree[now].right=STree[Header].left;
    ST_DeleteNode(Header);
    return now;
}

伸展树自顶而下的单元素插入维护

单元素插入时,将与待插入元素权值最相近节点伸展到根,当待插入元素小于根权值时,将根的左子树并入待插入元素的左子节点,将根及其右子树并入待插入元素的右子节点,更新伸展树的根即可。若待插入元素大于根权值时同理。

对于伸展树自顶而下的单元素插入维护代码如下:

int ST_Memory[MAX_N],ST_MemTop=0;
int ST_NewNode()
{
    if(ST_Memory[0]>0)
        return ST_Memory[ST_Memory[0]--];
    return ++ST_MemTop;
}
void ST_Insert(int key)
{
    int now=ST_NewNode();
    STree[now].left=STree[now].right=NIL;
    STree[now].key=key;
    if(ST_ROOT!=NIL)
    {
        ST_ROOT=ST_Splay(ST_ROOT,key);
        if(key<STree[ST_ROOT].key)
            STree[now].left=STree[ST_ROOT].left,
            STree[now].right=ST_ROOT,
            STree[ST_ROOT].left=NIL;
        else
            STree[now].left=ST_ROOT,
            STree[now].right=STree[ST_ROOT].right,
            STree[ST_ROOT].right=NIL;
    }
    ST_ROOT=now;
}

伸展树自顶而下的单元素删除维护

单元素删除时,将待删除元素伸展到根,将根的右子树中与待删除元素权值最相近节点,即最小权值节点伸展到根的右子节点,此时根的右子节点必然无左子树,将根的左子树并入根的右子节点的左子节点中,删除根节点,并更新伸展树的根为原根的右子节点即可。

对于伸展树自顶而下的单元素删除维护代码如下:

void ST_DeleteNode(int pos)
{
    STree[pos].key=STree[pos].left=STree[pos].right=NIL;
    ST_Memory[++ST_Memory[0]]=pos;
}
void ST_Delete(int key)
{
    int del;
    if(ST_ROOT!=NIL)
    {
        ST_ROOT=ST_Splay(ST_ROOT,key);
        if(key==STree[ST_ROOT].key)
        {
            del=ST_ROOT;
            if(STree[ST_ROOT].left!=NIL)
            {
                if(STree[ST_ROOT].right!=NIL)
                    STree[ST_ROOT].left=ST_Splay(STree[ST_ROOT].left,key+1),
                    STree[STree[ST_ROOT].left].right=STree[ST_ROOT].right;
                ST_ROOT=STree[ST_ROOT].left;
            }
            else
                ST_ROOT=STree[ST_ROOT].right;
            ST_DeleteNode(del);
        }
    }
}

自顶而下伸展树的总结

虽然伸展树编程思想较为简单,总摊还时间复杂度较低,但不能保证单次操作时间复杂度,故在比较正式的 STL 模板中,二叉平衡树多用时间效率较为稳定的红黑树实现。相对于原版自底而上伸展树而言,笔者实现的无旋转的自顶而下伸展树相当于一个变种,在一字型情况的旋转中略有差异,而在之字形情况的旋转中等价。在 Wikipedia 有关于 Splay tree 中对自底而上伸展树有很好的介绍和代码实现,在这里笔者不在赘述。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值