伸展树

✍ dations ◷ 2025-11-03 18:12:39 #树结构,数据结构,计算机科学中未解决的问题

伸展树(英语:Splay Tree)是一种能够自我平衡的二叉查找树,它能在均摊 O ( log n ) {\displaystyle O(\log n)} 被访问过后,伸展操作会将移动到根节点。为了进行伸展操作,我们会进行一系列的旋转,每次旋转会使离根节点更近。通过每次访问节点后的伸展操作,最近访问的节点都会离根节点更近,且伸展树也会大致平衡,这样我们就可以得到期望均摊时间复杂度的下界——均摊 O ( log n ) {\displaystyle O(\log n)} 的儿子为是很重要的。如果为空,那么显然就是根节点了。

共有三种旋转操作,每种都有左旋(Zig)和右旋(Zag)两种情况。为了简单起见,对每种旋转操作只展示一种情况。这些旋转操作是:

Zig:当为根节点时进行。Zig通常只在伸展操作的最后一步进行。

Zig-zig和Zag-zag:当不为根节点且和都为左儿子或都为右儿子时进行。下图为和都为左儿子时的情况(即Zig-zig),需先将右旋到的位置,再将右旋到的位置。

Zig-zag和Zag-zig:当不为根节点且为左儿子而为右儿子时进行,反之亦然。下图为前述情况(即Zig-zag),需先将左旋到到的位置,再将右旋到的位置。

给出两棵树S和T,且S的所有元素都比T的元素要小。下面的步骤可以把它们连接成一棵树:

给出一棵树和一个元素,返回两棵树:一棵中所有的元素均小于等于x,另一棵中所有的元素大于。下面的步骤可以完成这个操作:

插入操作是一个比较复杂的过程,具体步骤如下:我们假定要插入的值为k。

如果当前树为空,则直接插入根。

如果当前节点的权值等于k则增加当前节点的大小并更新节点和父亲的信息,将当前节点进行splay操作。

否则按照二叉查找树的性质向下找,找到空节点就插入即可,当然在最后还要进行一次splay操作。

如同一般的查找树的查找方式。

以下是伸展树的C++实现(用指针实现)

#include <functional>#ifndef SPLAY_TREE#define SPLAY_TREEtemplate< typename T, typename Comp = std::less< T > >class splay_tree {private:  Comp comp;  unsigned long p_size;    struct node {    node *left, *right;    node *parent;    T key;    node( const T& init = T( ) ) : left( 0 ), right( 0 ), parent( 0 ), key( init ) { }  } *root;    void left_rotate( node *x ) {    node *y = x->right;    x->right = y->left;    if( y->left ) y->left->parent = x;    y->parent = x->parent;    if( x->parent ) {        if( x == x->parent->left ) x->parent->left = y;        else x->parent->right = y;    }    y->left = x;    x->parent = y;  }    void right_rotate( node *x ) {    node *y = x->left;    x->left = y->right;    if( y->right ) y->right->parent = x;    y->parent = x->parent;    if( x->parent ) {        if( x == x->parent->left ) x->parent->left = y;        else x->parent->right = y;    }    y->right = x;    x->parent = y;  }    void splay( node *x ) {    while( x->parent ) {      if( !x->parent->parent ) {        if( x->parent->left == x ) right_rotate( x->parent );        else left_rotate( x->parent );      } else if( x->parent->left == x && x->parent->parent->left == x->parent ) {        right_rotate( x->parent->parent );        right_rotate( x->parent );      } else if( x->parent->right == x && x->parent->parent->right == x->parent ) {        left_rotate( x->parent->parent );        left_rotate( x->parent );      } else if( x->parent->left == x && x->parent->parent->right == x->parent ) {        right_rotate( x->parent );        left_rotate( x->parent );      } else {        left_rotate( x->parent );        right_rotate( x->parent );      }    }  }    void replace( node *u, node *v ) {    if( !u->parent ) root = v;    else if( u == u->parent->left ) u->parent->left = v;    else u->parent->right = v;    if( v ) v->parent = u->parent;  }    node* subtree_minimum( node *u ) {    while( u->left ) u = u->left;    return u;  }    node* subtree_maximum( node *u ) {    while( u->right ) u = u->right;    return u;  }public:  splay_tree( ) : root( 0 ), p_size( 0 ) { }    void insert( const T &key ) {    node *z = root;    node *p = 0;        while( z ) {      p = z;      if( comp( z->key, key ) ) z = z->right;      else z = z->left;    }        z = new node( key );    z->parent = p;        if( !p ) root = z;    else if( comp( p->key, z->key ) ) p->right = z;    else p->left = z;        splay( z );    p_size++;  }    node* find( const T &key ) {    node *z = root;    while( z ) {      if( comp( z->key, key ) ) z = z->right;      else if( comp( key, z->key ) ) z = z->left;      else return z;    }    return 0;  }          void erase( const T &key ) {    node *z = find( key );    if( !z ) return;        splay( z );        if( !z->left ) replace( z, z->right );    else if( !z->right ) replace( z, z->left );    else {      node *y = subtree_minimum( z->right );      if( y->parent != z ) {        replace( y, y->right );        y->right = z->right;        y->right->parent = y;      }      replace( z, y );      y->left = z->left;      y->left->parent = y;    }        p_size--;  }    const T& minimum( ) { return subtree_minimum( root )->key; }  const T& maximum( ) { return subtree_maximum( root )->key; }    bool empty( ) const { return root == 0; }  unsigned long size( ) const { return p_size; }};#endif // SPLAY_TREE

时间效率分析

m次伸展操作的均摊时间效率 T a m o r t i z e d ( m ) = O ( m log n ) {\displaystyle T_{\mathrm {amortized} }(m)=O(m\log n)}

实际时间效率 T a c t u a l ( m ) = O ( m log n + n log n ) {\displaystyle T_{\mathrm {actual} }(m)=O(m\log n+n\log n)}

相关

  • 达斯蒂·斯普林菲尔德达斯蒂·斯普林菲尔德,OBE(英语:Dusty Springfield,1939年4月16日-1999年3月2日),本名玛丽·伊莎贝尔·凯瑟林·贝尔纳黛特·奥布赖恩(Mary Isabel Catherine Bernadette O'Brien),英
  • 萨克森小瑞士国家公园萨克森小瑞士(德语:Sächsische Schweiz)也称萨克森瑞士、萨克森施韦茨,是德国东部的一个山区,与捷克共和国境内的波西米亚瑞士共同组成易北砂岩山脉。该地是著名的旅游、攀岩胜
  • 味觉感受器味觉感受器是一种专司味觉的受体器官,包括诸如TAS2R16及TAS2R38等。味觉感受器被分成两大类:人类苦味感受器的基因分别被命名为TAS2R1至TAS2R64,然而其中有不少部分是空白的,包
  • 远近孝一远近孝一(1971年10月20日-)是日本的男性声优,所属东京俳优生活协同组合。于大阪府出生并在山口县成长,血型为B型。代表作有《勇者指令》(大堂寺炎)、《侦探学园Q》(天草流)、《超能勇
  • 雅库布·希尔基古斯雅库布·希尔基古斯(斯洛伐克语:;1989年2月2日-)是一位斯洛伐克足球运动员。在场上的位置是前锋。他现在效力于德国足球甲级联赛球队纽伦堡足球俱乐部。他也代表斯洛伐克国家足球
  • 克里斯托弗·科迪MLBCPBL克里斯托弗·科迪(英语:Christopher Patrick Cody,台译:寇迪C.C,1984年1月7日-),为美国棒球选手,于2006年选秀由底特律老虎于第8轮第232顺位选中,2014年起来台发展,曾效力中华职
  • 青山幸成青山幸成(1586年—1643年4月4日)是安土桃山时代至江户时代前期的武将、大名。远江国挂川藩(日语:掛川藩)藩主、摄津国尼崎藩初代藩主。郡上藩(日语:郡上藩)青山家(日语:青山家)初代。父
  • 阿史那贺鲁阿史那贺鲁(?-659年),西突厥汗国大将,室点密可汗五世孙,后自立为西突厥沙钵罗可汗(古突厥文:��������‬,拉丁转写:)。早年为西突厥叶护,在多罗斯川(今额尔齐斯河源头)一带游牧。646年,乙毗射匮就任
  • 日本元服礼元服礼是日本奈良时代以来男子成人礼意思,“元服”是冠礼的别称。“元”是头的意思,“服”是穿的动词意思。解释为“头戴帽子”,也有戴帽子和初次戴帽子的意思。公卿家女子成
  • 胡麻斑蝴蝶鱼胡麻斑蝴蝶鱼,又称密点蝴蝶鱼,俗名胡麻蝶,为辐鳍鱼纲鲈形目蝴蝶鱼科的其中一种。本鱼分布于印度太平洋区,包括东非、亚丁湾、马尔代夫、科摩罗、毛里求斯、塞舌尔群岛、斯里兰卡