目录
一、什么是伸展树?
80-20黄金法则显示,日常中我们80%的访问发生在20%的数据上。
如果我们能够把经常访问的节点推到靠近根节点的位置,那么就可以极大的提高访问速度。
这就是伸展树的出发点,并实现了以下方案:每次查找节点之后对树进行重构(旋转操作),把被查找的节点搬移到树根。
与二叉平衡树相比,伸展树不会保证树一直是平衡的,甚至可能是一个糟糕的单链表,但是它各种操作的平摊时间复杂度是O(log n)。
二、Splay操作的设计模式
伸展树的设计有两种设计模式,一种“自底向上”,一种“自顶向下”。
由于伸展树的操作需要访问其父结点和祖父结点,那么“自底向上”的设计就需要额外的指针指向结点的父结点,来为我们提供访问的方式,这就需要额外的空间了。
//自底向上
struct SplayNode{
int value;
SplayNode *parent; //额外指针指向父结点
SplayNode *left, *right;
SplayNode(int val):value(val), left(NULL), right(NULL){}
};
因此本文主要讨论“自顶向下”的模式,因此延展树的定义如下。
//自顶向下
struct SplayNode{
int value;
SplayNode *left, *right;
SplayNode(int val):value(val), left(NULL), right(NULL){}
};
class SplayTree{
public:
SplayTree(): root(NULL){}
SplayTree(vector<int> &arr);
void Splay(int val);
//....
private:
//....
SplayNode* root;
};
三、伸展树的旋转操作
假设X是一个非根点的被访问结点:
- 如果X的父结点是root,那我们可以直接旋转X和root。
- 如果X除了有父结点P,还有一个祖父结点G,那么就有两种情景(实际是四种,但是镜像),分别为Zig-Zag和Zig-Zig。
3.1 父结点是root
3.1.1 左旋
访问结点X在父结点的左边,旋转操作:P的左子树挂载X的右子树,然后P成为X的右子树。
SplayNode* SplayTree::LeftRotation(SplayNode* G){
SplayNode* X = G->left;
G->left = X->right;
X->right = G;
return X;
}
3.1.2 右旋
访问结点X在父结点P的右边,旋转操作:P的右子树挂载X的左子树,然后P成为X的左子树。