伸展树
概念
英文名: SplayTree,首先属于二叉搜索树, 一种自调节树。适用于二八法则场景。
伸展 :将一个元素移动至根部,称为一次伸展。
注意:伸展树不一定是二叉平衡树。
操作
- 插入
如果结点不存在则插入新结点,如果存在则返回查找结果。插入返回时将结点伸展到根。 - 查找
如果结点不存在,将与查找结点相邻的结点调整到根。 - 删除
找到删除结点,将结点调整到根,删除结点,将删除结点的最大下界结点伸展到根。
逻辑与实现
为了方便描述,我们规定 p, q, r 分别为爷爷,父亲,儿子三个结点的指针。旋转对爷爷进行操作。
- 单一旋转
一次旋转交换两个结点的位置, 这里只需要考虑爷爷和父亲两个结点。旋转目的将父亲调整至根。
函数的参数指向爷爷
。- LRot(zag)
void LRot(BTNode *&p){ //这里需要传递引用,因为要修改p的实际的值 BTNode *r = p->rChild; p->rChild = r->lChild; r->lChild = p; p = r; }
- RRot(zig)
void RRot(BTNode *&p){ //左旋的对称操作 BTNode *r = p->lChild; p->lChild = r->rChild; r->rChild = p; p = r; }
- LRot(zag)
观察容易发现记忆规律:RRot,LRot代码呈现出
顶针
句式
-
双重旋转
要考虑爷爷,父亲,儿子三个结点。旋转目的是将儿子调整置根。注意旋转顺序。
zigzig / zagzag称为一字旋转, zigzag/zagzig 称为之子旋转。- zigzig
顾名思义:两次zig旋转: RRot( p ), RRot( q )。 - zagzag
顾名思义:两次zag旋转:LRot( p ), LRot( r )。 - zigzag
not
顾名思义:先zag旋转父亲,再zig旋转爷爷。 - zagzig
not
顾名思义:先zag旋转父亲,再zig旋转爷爷。
有点反人性,这里
之字旋转
的名称不代表旋转的先后顺序。(其它图后期看心情吧,没带鼠标) - zigzig
-
Insert操作的代码
(其他操作大同小异)
代码巨长,但是基本对称,逻辑理清楚即可,伸展是通过递归自底向上伸展的。
代码中p代表当前结点,r代表p的孩子。(本质上爷爷父亲,父亲儿子还是父与子的关系)
ResultCode Insert(BTNode *& p, int x){
ResultCode result = Success;
//讨论爷爷
if(p == NULL){
p = new BTNode;
p->element = x;
return result;
}
if(p->element == x){
result = Duplicate;
return result;
}
if(x < p->element){
//讨论父亲
BTNode *r = p->lChild;
if(r == NULL){
r = new BTNode;
r->element = x;
r->rChild = p;
p = r;
return result;
}
if (r->element == x){
result = Duplicate;
RRot(p); // zig
return result;
}
//下面通过递归讨论儿子
if(x < r->element){
result = Insert(r->lChild, x); // zigzig
RRot(p);
}
else {
result = Insert(r->rChild, x); //zigzag
LRot(r);
p->lChild = r;
}
RRot(p);
}
else{ //跟上面代码对称
BTNode *r = p->rChild;
if(r == NULL){
r = new BTNode;
r->element = x;
r->lChild = p;
p = r;
return result;
}
if(r->element == x){ //zag
result = Duplicate;
LRot(p);
return result;
}
if(x > r->element){ //zagzag
result = Insert(r->rChild, x);
LRot(p);
}
else{ //zagzig
result = Insert(r->lChild, x);
RRot(r);
p->rChild = r;
}
LRot(p);
}
return result;
}
- 对于删除操作为啥将最大下界结点伸展到根?
根据二叉搜索树的性质,小于根结点的最大下界结点一定在根结点左子树的最右侧(可以方便的找到),而且其右孩子必为空。因为此时删除结点已经伸展到根,此时可以直接将根结点的右子树接到该结点的右侧,并将根结点的左子树接到该结点的左侧(在操作前摘下该结点)。如此操作后,得到的仍然是一棵二叉搜索树。
关于二八法则
有人说获得80%的知识只需要花费20%的精力,这样你100%的精力可以获取总和为400%的知识。
而不是100%的精力获得不一定100%的单一知识。
这对时间、精力有限的我们也是一种启迪。
伸展树应用于二八场景:指的是一件事情发生后有极大的概率再次发生。
比如百度某样知识,点击次数多的优先排在前方等等。