伸展树
伸展树是自平衡的二叉查找树,伸展树不像AVL树,要时刻保持平衡。伸展树通过分摊保持查找的高效率。
伸展树的基本思想:当一个节点被访问后,它就要进过一系列AVL树旋转被放在根上。
数据局部性
可视化伸展树
http://www.link.cs.cmu.edu/splay/
这个网站是伸展树的创始人做到一个project。将伸展树可视化。学伸展树的时候可以看看。
实现
由于数据结构与算法分析:C语言描述上面只是介绍了伸展树,但是没有代码实现。所以这篇blog主要写我是怎么实现伸展树的。
zig/zag
伸展树里最重要的一点就是zig/zag了
zig
zig其实就是AVL的左旋
zag
zag其实就是AVL的右旋
zig/zag组合
有了zig,zag。就可以实现伸展树了。
伸展树通过zig,zag的组合来旋转。伸展式是通过两层旋转。为什么可以可见书上证明。
zig,zig组合
如果V是父节点的左儿子,V的父节点是V祖父节点的左儿子。如图:
这个时候要用zig,zig旋转,如图
zag,zag组合
如果V是父节点的右儿子,V的父节点是V祖父节点的右儿子。如图:
这个时候要用zag,zag旋转,如图
zig,zag组合
如果V是父节点的左儿子,V的父节点是V祖父节点的右儿子。如图:
这个时候要用zig,zag旋转,如图
zag,zig组合
如果V是父节点的右儿子,V的父节点是V祖父节点的左儿子。如图:
这个时候要用zig,zag旋转,如图
code:
上面的图终于搞完了。上代码:我没有使用书最后一章自顶而下的实现方法。而是自下而上。所以每个节点要附带父亲节点。所以这种实现方法只供学习练习。要真正使用还是实现自顶而下的伸展树。
Node class的定义:
class Node
{
friend class SplayTree;
public:
Node(int V) :val(V), left(nullptr), right(nullptr),parent(nullptr) { ; }
~Node()=default;
private:
int val;
Node *left, *right;
Node *parent;
};
由于其他操作和BST是一样的,所以我只给出搜索的代码:
搜索函数的基本框架就如下所示:
class SplayTree
{
public:
Node* search(int x) {
_root=_Find(_root, x);
return _root;
}
int Retrieve(Node* root) {
return root ==nullptr?-1:root->val;
private:
Node* _Find(Node *root,int x);
Node* splay(Node *root);
Node* zip(Node *);
Node* zap(Node *);
Node * _root;
};
_Find函数:找到该寻找的节点,并调用splay函数。
Node * SplayTree::_Find(Node *root,int x)
{
if (root == nullptr)
return nullptr;
if (x < root->val)
return _Find(root->left, x);
else if (x > root->val)
return _Find(root->right, x);
else
root = splay(root);
return root;
}
splay函数,将该节点旋转至root.
Node* f是父亲节点,g是祖父节点
Node * SplayTree::splay(Node *root)
{
Node* f, *g;
while ((f=root->parent) && (g=f->parent)) { //root有父节点和祖父节点 g
Node* gg = g->parent; // f
if (g->left == f) { // root
if (f->left == root) {
f = zip(g);
root = zip(f);
}
else { // g
g->left=zap(f); // f
root = zip(g); // root
}
}
else {
if (f->left == root) { // g
g->right = zip(f); // f
root = zap(g); // root
}
else { // g
f = zip(g); // f
root = zip(f); // root
}
}
if (gg == nullptr) //到树根了
root->parent = nullptr;
else if (g == gg->left)
gg->left = root;
else
gg->right = root;
}
if (f!=nullptr) {
if (f->left == root) {
root = zip(f);
}
else if(f->right == root){
root = zap(f);
}
}
root->parent = nullptr;
return root;
}
下面是zig和zap函数的实现,其实就是AVL里的左旋右旋差不多。
Node * SplayTree::zip(Node *root) // root
{ // v
Node* v = root->left; //
Node* parent = root->parent;
root->left = v->right;
v->right = root;
if(root->left !=nullptr)
root->left->parent = root;
root->parent = v;
v->parent = parent;
return v;
}
Node * SplayTree::zap(Node *root) // root
{ // v
Node* v = root->right;
Node* parent = root->parent;
root->right = v->left;
v->left = root;
if(root->right !=nullptr)
root->right->parent = root;
root->parent = v;
v->parent = parent;
return v;
}