7.3 Splay树

参考博客: https://blog.csdn.net/hellochenlu/article/details/53022709
参考博客: https://blog.csdn.net/amoscykl/article/details/81589827

本章问题

1.splay逐层伸展到根和双层伸展有什么区别呢?

假设节点的个数为n,则旋转操作的总次数的为(n-1)+{(n-1)+(n-2)+(n-3)+…+1}=(n2+n-2)/2= Ω \Omega Ω(n2)
逐层伸展,对于规模为任意n的伸展树,只要按照关键码单调的次序,周期性的进行反复的查找,则无论总的访问次数m>>n有多大,就分摊的意义而言,每次访问都需要 Ω \Omega Ω(n)的时间
双层伸展优点
折叠效果:一旦访问坏节点,对应路径的长度将随即减半。
最坏情况不致持续发生!
单趟伸展操作,分摊O(logn)时间!

2.splay为什么要伸展到根,这样做有什么好处

因为数据局部性原理,这样可以加快后续的操作
数据局部性原理:
1.刚刚被访问的节点,即有可能在不久之后再次被访问到
2.将被访问的下一节点,极有可能就处于不久的之前被访问过的某个节点的附近

3.Splay双层伸展的具体描述

参考博客:https://blog.csdn.net/hellochenlu/article/details/53022709

双层伸展
构思精髓:向上追溯两层,而非一层
反复考察祖孙三代:g = parent§,p = parent(v),v
根据它们的相对位置,经两次旋转使得 v上升两层,成为(子)树根

节点v的位置的四种可能性7.3 Splay树

zig-zag / zag-zig
与AVL树双旋完全等效
与逐层伸展别无二致。7.3 Splay树

颠倒次序后,局部的细微差异,将彻底地改变整体…7.3 Splay树

折叠效果:一旦访问坏节点,对应路径的长度将随即减半。
最坏情况不致持续发生!
单趟伸展操作,分摊O(logn)时间!7.3 Splay树

要是v只有父亲,没有祖父呢?
此时必有parent(v) == root(T),且
每轮调整中,这种情况至多(在最后)出现一次
视具体形态,做单词旋转:zig®或者zag®7.3 Splay树

4.Splay树中查找算法的作用

将最后一个被访问的节点伸展至根,此时仍然还是二叉搜索树,此时仍满足特点
是一棵空树或具有以下几种性质的树:
1.若左子树不空,则左子树上所有结点的值均小于它的根结点的值
2.若右子树不空,则右子树上所有结点的值均大于它的根结点的值
3.左、右子树也分别为二叉排序树
4.没有权值相等的结点。

5.Splay中插入算法怎样完成局部重构

在查找接口Splay::search()已集成了splay()伸展功能,故查找返回后,树根节点要么等于查找目标(查找成功),要么就是_hot,而且恰为拟插入节点的直接前驱或直接后继(查找失败)7.3 Splay树
如上图所示,为将关键码e插至伸展树T中,首先调用伸展树查找接口Splay::search(e),查找该关键码(图(a))。于是,其中最后被访问的节点t,将通过伸展被提升为树根,其左、右子树分别记作T L 和T R (图(b))。
接下来,根据e与t的大小关系(不妨排除二者相等的情况),以t为界将T分裂为两棵子树。比如,不失一般性地设e大于t。于是,可切断t与其右孩子之间的联系(图©),再将以e为关键码的新节点v作为树根,并以t作为其左孩子,以T R 作为其右子树(图(d))。
v小于t的情况与此完全对称。

6.Splay删除节点时若左右子树都存在,为什么要暂时切断左子树

为从伸展树中删除节点,固然也可以调用二叉搜索树标准的节点删除算法BST::remove(),再通过双层伸展,将该节点此前的父节点提升至树根。然而,同样鉴于Splay::search()已集成了splay()伸展功能,且在成功返回后,树根节点恰好就是待删除节点。因此,亦不妨改用如下方法实现Splay::remove()接口。
7.3 Splay树

如图所示,为从伸展树T中删除关键码为e的节点,首先亦调用接口Splay::search(e),查找该关键码,且不妨设命中节点为v(图(a))。于是,v将随即通过伸展被提升为树根,其左、右子树分别记作T L 和T R (图(b))。接下来,将v摘除(图©)。然后,在T R 中再次查找关键码e。尽管这一查找注定失败,却可以将T R 中的最小节点m,伸展提升为该子树的根。
得益于二叉搜索树的顺序性,此时节点m的左子树必然为空;同时,T L 中所有节点都应小于m(图(d))。于是,只需将T L 作为左子树与m相互联接,即可得到一棵完整的二叉搜索树(图(e))。如此不仅删除了v,而且既然新树根m在原树中是v的直接后继,故数据局部性也得到了利用。

7.3.1 Splay的ADT接口

操作 功能
splay (BinNodePosi(T) v) 将节点伸展到根
search( const T& e ) 查找节点
insert ( const T& e ) 插入节点
remove( const T& e ) 删除节点

7.3.2 Splay的类模板

#include "BST.h"
#include <iostream>
template <typename T> class Splay : public BST<T> {
    protected:
        BinNodePosi(T) splay (BinNodePosi(T) v);//将节点伸展到根
    public:
        BinNodePosi(T) & search( const T& e );//查找
        BinNodePosi(T) insert( const T& e );//插入
        bool remove ( const T& e );//删除
};

7.3.4 Splay的伸展算法

template <typename NodePosi> inline
void attachAsLChild ( NodePosi p, NodePosi lc ) { p->lc = lc; if(lc) lc->parent = p; }
//节点*p与*lc建立父(左)子关系
template <typename NodePosi> inline
void attachAsRChild ( NodePosi p, NodePosi rc ) { p->rc = rc; if(rc) rc->parent = p; }
//节点*p与*rc建立父(右)子关系

//伸展算法的实现
template <typename T>
BinNodePosi(T) Splay<T>::splay ( BinNodePosi(T) v ){
    if( !v ) return nullptr; BinNodePosi(T) p; BinNodePosi(T) g;
    while( ( p = v->parent ) && ( g = p->parent )){//祖父与父亲都安在,双层伸展
        BinNodePosi(T) gg = g->parent;
        if( v->data < p->data)//如果节点v是p的左子树
            if( p->data < g->data){//zig-zig 
                attachAsLChild ( g, p->rc ); attachAsLChild( p, v->rc );
                attachAsRChild ( p, g ); attachAsRChild( v, p );
                std::cout << "here0" << p->data << g->data<< std::endl;
            }else{//zig-zag
                std::cout << "here1" <<std::endl;
                attachAsLChild ( p, v->rc ); attachAsRChild( g, v->lc );
                attachAsLChild ( v, g ); attachAsRChild( v, p );
            }
        else 
            if( p->data >= g->data){//zag-zag
                std::cout << "here2" <<std::endl;
                attachAsRChild ( g, p->lc ); attachAsRChild( p, v->lc );
                attachAsLChild ( p, g ); attachAsLChild( v, p );
            }else{//zag-zig
                std::cout << "here3" <<std::endl;
                attachAsRChild ( p, v->lc ); attachAsLChild( g, v->rc );
                attachAsRChild ( v, g ); attachAsLChild( v, p );
        }
        if ( !gg ) v->parent = NULL;
        else
            ( g == gg->lc ) ? attachAsLChild( gg, v ) : attachAsRChild( gg, v );
        BST<T>::updateHeight ( g ); BST<T>::updateHeight( p ); BST<T>::updateHeight( v );
    }
    if ( p = v->parent ){//只有父亲安在
        if (  v->data < p->data) { attachAsLChild( p, v->rc ) ; attachAsRChild( v, p ); }
        else                     { attachAsRChild( p, v->lc ) ; attachAsLChild( v, p ); }
        BST<T>::updateHeight( p ); BST<T>::updateHeight( v );
    }
    v->parent = NULL; return v;
}

7.3.5 查找

//查找
template <typename T> BinNodePosi(T) & Splay<T>::search( const T& e ){
    BinNodePosi(T) p = searchIn( this->_root, e, this->_hot = nullptr );
    this->_root = splay ( p ? p : this->_hot );
    return this->_root;
}

7.3.6 插入

//插入
template <typename T> BinNodePosi(T) Splay<T>::insert ( const T& e ){
    if ( !this->_root ) { this->_size ++; return this->_root = new BinNode<T> ( e ); }//处理原树为空的退化情况
    if ( e == search( e )->data ) return this->_root;//已经有这个数的情况
    this->_size++; BinNodePosi(T) t = this->_root;
    if ( this->_root->data < e ){
        t -> parent = this->_root = new BinNode<T> ( e, nullptr, t, t->rc );
        if ( HasRChild ( *t )) { t->rc->parent = this->_root; t->rc = nullptr; }
    }else{
        t->parent = this->_root = new BinNode<T> ( e, nullptr, t->lc, t );
        if ( HasLChild ( *t )) { t->lc->parent = this->_root; t->lc = nullptr; }
    }
    BST<T>::updateHeightAbove ( t );
    return this->_root;
}

7.3.7 删除

template <typename T> bool Splay<T>::remove( const T& e ){
    if (!this->_root || e != search(e)->data)  return false; 
    auto t = this->_root;
    if (!HasLChild( *(this->_root))) { // 若无左子树,直接删除
        this->_root = this->_root->rc; // 以右子树为树根
        if (this->_root) { this->_root->parent = nullptr; } // 更新右子树父节点为null
    } else if (!HasRChild( *(this->_root))) { // 若无右子树,直接删除
        this->_root = this->_root->lc; // 以左子树为树根
        if (this->_root) { this->_root->parent = nullptr; } // 更新左子树父节点为null
    } else { // 左、右子树同时存在
        // 暂时切除左子树
        auto ltree = this->_root->lc;
        ltree->parent = nullptr;
        this->_root->lc = nullptr;

        // 只保留右子树
        this->_root = this->_root->rc;
        this->_root->parent = nullptr;
        
        // 以原树根为目标,做一次(必定失败的)查找
        search(t->data);

        // 至此,右子树中最小节点必伸展至根,且(因无雷同节点)其左子树必空,于是只需将原左子树接回原位即可
        this->_root->lc = ltree;
        ltree->parent = this->_root;
    }

    // 释放节点,更新规模
    dtl::release(t->data); dtl::release(t); 
    this->_size--;

    // 此后,若树非空,则树根的高度需要更新
    if (this->_root) {
        this->updateHeight(this->_root);
    }

    return true;
}

7.3.8 Splay.h

#include "BST.h"
#include <iostream>
template <typename T> class Splay : public BST<T> {
    protected:
        BinNodePosi(T) splay (BinNodePosi(T) v);//将节点伸展到根
    public:
        BinNodePosi(T) & search( const T& e );//查找
        BinNodePosi(T) insert( const T& e );//插入
        bool remove ( const T& e );//删除
};
//查找
template <typename T> BinNodePosi(T) & Splay<T>::search( const T& e ){
    BinNodePosi(T) p = searchIn( this->_root, e, this->_hot = nullptr );
    this->_root = splay ( p ? p : this->_hot );
    return this->_root;
}
//插入
template <typename T> BinNodePosi(T) Splay<T>::insert ( const T& e ){
    if ( !this->_root ) { this->_size ++; return this->_root = new BinNode<T> ( e ); }//处理原树为空的退化情况
    if ( e == search( e )->data ) return this->_root;//已经有这个数的情况
    this->_size++; BinNodePosi(T) t = this->_root;
    if ( this->_root->data < e ){
        t -> parent = this->_root = new BinNode<T> ( e, nullptr, t, t->rc );
        if ( HasRChild ( *t )) { t->rc->parent = this->_root; t->rc = nullptr; }
    }else{
        t->parent = this->_root = new BinNode<T> ( e, nullptr, t->lc, t );
        if ( HasLChild ( *t )) { t->lc->parent = this->_root; t->lc = nullptr; }
    }
    BST<T>::updateHeightAbove ( t );
    return this->_root;
}
//删除
template <typename T> bool Splay<T>::remove( const T& e ){
    if (!this->_root || e != search(e)->data)  return false; 
    auto t = this->_root;
    if (!HasLChild( *(this->_root))) { // 若无左子树,直接删除
        this->_root = this->_root->rc; // 以右子树为树根
        if (this->_root) { this->_root->parent = nullptr; } // 更新右子树父节点为null
    } else if (!HasRChild( *(this->_root))) { // 若无右子树,直接删除
        this->_root = this->_root->lc; // 以左子树为树根
        if (this->_root) { this->_root->parent = nullptr; } // 更新左子树父节点为null
    } else { // 左、右子树同时存在
        // 暂时切除左子树
        auto ltree = this->_root->lc;
        ltree->parent = nullptr;
        this->_root->lc = nullptr;

        // 只保留右子树
        this->_root = this->_root->rc;
        this->_root->parent = nullptr;
        
        // 以原树根为目标,做一次(必定失败的)查找
        search(t->data);

        // 至此,右子树中最小节点必伸展至根,且(因无雷同节点)其左子树必空,于是只需将原左子树接回原位即可
        this->_root->lc = ltree;
        ltree->parent = this->_root;
    }

    // 释放节点,更新规模
    dtl::release(t->data); dtl::release(t); 
    this->_size--;

    // 此后,若树非空,则树根的高度需要更新
    if (this->_root) {
        this->updateHeight(this->_root);
    }

    return true;
}

template <typename NodePosi> inline
void attachAsLChild ( NodePosi p, NodePosi lc ) { p->lc = lc; if(lc) lc->parent = p; }//节点*p与*lc建立父(左)子关系
template <typename NodePosi> inline
void attachAsRChild ( NodePosi p, NodePosi rc ) { p->rc = rc; if(rc) rc->parent = p; }//节点*p与*rc建立父(右)子关系

//伸展算法的实现
template <typename T>
BinNodePosi(T) Splay<T>::splay ( BinNodePosi(T) v ){
    if( !v ) return nullptr; BinNodePosi(T) p; BinNodePosi(T) g;
    while( ( p = v->parent ) && ( g = p->parent )){//祖父与父亲都安在,双层伸展
        BinNodePosi(T) gg = g->parent;
        if( v->data < p->data)
            if( p->data < g->data){//zig-zig 
                attachAsLChild ( g, p->rc ); attachAsLChild( p, v->rc );
                attachAsRChild ( p, g ); attachAsRChild( v, p );
                std::cout << "here0" << p->data << g->data<< std::endl;
            }else{//zig-zag
                std::cout << "here1" <<std::endl;
                attachAsLChild ( p, v->rc ); attachAsRChild( g, v->lc );
                attachAsLChild ( v, g ); attachAsRChild( v, p );
            }
        else 
            if( p->data >= g->data){//zag-zag
                std::cout << "here2" <<std::endl;
                attachAsRChild ( g, p->lc ); attachAsRChild( p, v->lc );
                attachAsLChild ( p, g ); attachAsLChild( v, p );
            }else{//zag-zig
                std::cout << "here3" <<std::endl;
                attachAsRChild ( p, v->lc ); attachAsLChild( g, v->rc );
                attachAsRChild ( v, g ); attachAsLChild( v, p );
        }
        if ( !gg ) v->parent = NULL;
        else
            ( g == gg->lc ) ? attachAsLChild( gg, v ) : attachAsRChild( gg, v );
        BST<T>::updateHeight ( g ); BST<T>::updateHeight( p ); BST<T>::updateHeight( v );
    }
    if ( p = v->parent ){//只有父亲安在
        if (  v->data < p->data) { attachAsLChild( p, v->rc ) ; attachAsRChild( v, p ); }
        else                     { attachAsRChild( p, v->lc ) ; attachAsLChild( v, p ); }
        BST<T>::updateHeight( p ); BST<T>::updateHeight( v );
    }
    v->parent = NULL; return v;
}

7.3.9 Splay测试

#include "splay.h"
#include <iostream>
using namespace std;

template<typename T> void returnValue(T& a)
{
    cout << "return_value: " << a << endl;
}
int main(){
    Splay<int> bt_test;

	bt_test.insertAsRoot(7);
	BinNodePosi(int) root = bt_test.root();
	int i;
	const int a[7] = { 0, 6, 5, 4, 3, 2, 1};
	for( i = 1; i < 7; ++i ){
		bt_test.insertAsLC( root, 7-i );
		root = root->lc;
	}

	//测试用例
	/* 								7
							6
						5
					4
				3
			2
		1
	*/
	bt_test.search(1);//先序遍历的搜索结果为1642357,与推算结果符合
	/* 	
			1						
							6			
						4		7
				2			5
			 		3
	*/
	bt_test.insert(0);//先序遍历的搜索结果为01642357,与推算结果符合
	/* 	
	0
			1						
							6			
						4		7
				2			5
			 		3
	*/
	//bt_test.remove(59);//删除
	bt_test.remove(1);//先序遍历的搜索结果为2043657,与推算结果符合
	/* 	
		2
	0				4						
				3			6			
						5		7
							
			 		
	*/
	void (* visit)(int& ) = &returnValue;
	bt_test.traverse(bt_test.root(),visit);
	return 0;
    
}
上一篇:94年女程序员的日常饮食,虽然工资1万多,但是三餐还都自己做


下一篇:【算法】高精度算法(加减乘除)