该暴力时就暴力: 替罪羊树
替罪羊树(Scapegoat_Tree)
定义
一种平衡树, 能达到宗法树和 Treap 的常数, 不用旋转维护自己的平衡, 逻辑和宗法树类似, 在左右子树极度不平衡时维护它的平衡性, 只不过宗法树是旋转, 比较温和, 而替罪羊树采用的是重构左右子树这种比较激进的策略.
所以替罪羊树不需要旋转.
和旋转不同的是, 一次重构可以使它的整个子树的每个节点的两个儿子平衡, 所以就不需要像宗法树一样, 每次遇到左右不平衡的节点都维护, 只要在这次操作经过的所有节点中, 找到深度最小的那个不平衡的节点, 然后重构它的子树即可.
这就是替罪羊树得名的原因: 即使有很多点需要重构, 选深度最小的点作为替罪羊, 只重构替罪羊的子树. 原因也很简单, 即使从下到上的所有节点都重构, 结果也和只重构那个最靠上的一样. (虽然依次重构这些点不会使复杂度更劣, 但是会得到两倍的常数).
struct Node {
Node *LS, *RS;
int Value, Size, Cnt, RealSize;
}N[100005], *PntStk[100005], *CntN(N), *Root(N), *TmpN, *TmpNF, *TmpF;
重构
字面意思, 重新建树, 如果子树规模为
操作逻辑很简单,
实现起来一般是将树上的节点按中序遍历压入一个栈, 在重构的时候, 使用栈里的节点的内存, 这样就能减少垃圾节点的数量并且不会申请新的内存.
Node *Build(unsigned L, unsigned R) {
if(L == R) {
PntStk[Hd]->Size = PntStk[Hd]->Cnt = CntStack[L], PntStk[Hd]->LS = PntStk[Hd]->RS = NULL, PntStk[Hd]->Value = Stack[L], PntStk[Hd]->RealSize = 1;
return PntStk[Hd--];
}
register unsigned Mid((L + R) >> 1);
register Node *x(PntStk[Hd--]);
x->RealSize = 1, x->Value = Stack[Mid], x->Size = x->Cnt = CntStack[Mid];
if(L < Mid) x->LS = Build(L, Mid - 1), x->RealSize += x->LS->RealSize, x->Size += x->LS->Size;
else x->LS = NULL;
x->RS = Build(Mid + 1, R);
x->RealSize += x->RS->RealSize, x->Size += x->RS->Size;
return x;
}
inline void DFS(Node *x) {
if(x->LS) DFS(x->LS);
if(x->Cnt) PntStk[++Hd] = x, CntStack[Hd] = x->Cnt, Stack[Hd] = x->Value;
if(x->RS) DFS(x->RS);
}
Node *Rebuild(Node *x) {
Hd = 0, DFS(x);
return Build(1, Hd);
}
删除
先说删除, 是因为替罪羊树的删除不同于基于旋转的平衡树, 因为不能旋转, 因此不能合并, 所以一个点的删除不能仅仅将它的两个子树合并后插入这个点原来的位置.
为了避免破坏树的结构, 我们用
当然, 为了防止有那种疯狂删除节点的数据, 我们需要在有效元素明显小于节点数的时候重构这棵子树. 实现起来就是维护两个
对于
对于
注意这里的几个
void Delete(Node *x) {
register Node *TmpofTmp(TmpF);
register char TmpofTmpTg(TmpTg);
TmpF = x;
if(x->Value == B) {if(x->Cnt) --(x->Cnt), --(x->Size);}
else if(x->Value > B) {if (x->LS) TmpTg = 0, x->Size -= x->LS->Size, Delete(x->LS), x->Size += x->LS->Size;}
else if (x->RS) TmpTg = 1, x->Size -= x->RS->Size, Delete(x->RS), x->Size += x->RS->Size;
x->RealSize = 1;
if(x->LS) x->RealSize += x->LS->RealSize;
if(x->RS) x->RealSize += x->RS->RealSize;
if(x->RealSize > 3 && x->Size) {
if((!(x->LS)) || (!(x->RS))) {TmpN = x, TmpNF = TmpofTmp, TmpNT = TmpofTmpTg;return;}
if((x->LS->Size * 2 < x->RS->Size) || (x->RS->Size * 2 < x->LS->Size)) {TmpN = x, TmpNF = TmpofTmp, TmpNT = TmpofTmpTg;return;}
if(x->RealSize * 3 > x->Size * 4) TmpN = x, TmpNF = TmpofTmp, TmpNT = TmpofTmpTg;
}
}
插入
插入操作和删除的不同是: 一定成功. 不像删除操作可能没有对应元素导致无法删除.
所以对于任何递归到的节点,
而
void Insert(Node *x) {
++(x->Size);
register Node *TmpofTmp(TmpF);
register char TmpofTmpTg(TmpTg);
TmpF = x;
if(x->Value == B) {++(x->Cnt);}
else {
if(x->Value < B) {
if(!(x->RS)) ++(x->RealSize), x->RS = ++CntN, x->RS->Value = B, x->RS->Cnt = x->RS->RealSize = x->RS->Size = 1;
else TmpTg = 1, x->RealSize -= x->RS->RealSize, Insert(x->RS), x->RealSize += x->RS->RealSize;
} else {
if(!(x->LS)) ++(x->RealSize), x->LS = ++CntN, x->LS->Value = B, x->LS->Cnt = x->LS->RealSize = x->LS->Size = 1;
else TmpTg = 0, x->RealSize -= x->LS->RealSize, Insert(x->LS), x->RealSize += x->LS->RealSize;
}
}
x->RealSize = 1;
if(x->LS) x->RealSize += x->LS->RealSize;
if(x->RS) x->RealSize += x->RS->RealSize;
if(x->RealSize > 3 && x->Size) {
if((!(x->LS)) || (!(x->RS))) {TmpN = x, TmpNF = TmpofTmp, TmpNT = TmpofTmpTg;return;}
if((x->LS->Size * 2 < x->RS->Size) || (x->RS->Size * 2 < x->LS->Size)) TmpN = x, TmpNF = TmpofTmp, TmpNT = TmpofTmpTg;
if(x->RealSize * 3 > x->Size * 4) TmpN = x, TmpNF = TmpofTmp, TmpNT = TmpofTmpTg;
}
}
查排名
查排名和一般的 BST 是一样的, 没有什么特点.
唯一的细节是小心当前节点的
void Rank (Node *x) {
if(x->LS) if(x->Value > B) return Rank(x->LS); else Ans += x->LS->Size;
if(x->Cnt) if(x->Value < B) Ans += x->Cnt;
if(x->RS) return Rank(x->RS);
}
查第 k 大
这时的
void Find(Node *x) {
if(x->LS) if(x->LS->Size >= B) return Find(x->LS); else B -= x->LS->Size;
if(x->Cnt) if(x->Cnt >= B) {Ans = x->Value; return;} else B -= x->Cnt;
if(x->RS) return Find(x->RS);
}
查前驱
未曾设想的道路: 查询
查后继
想都不敢想的道路: 查询
主函数
这里主要是注意用
unsigned Hd(0), m, n, A;
int Ans, B, Stack[100005], CntStack[100005];
char TmpNT(0), TmpTg(0);
int main() {
n = RD(), N[0].Value = 0x3f3f3f3f, N[0].Size = 1;
for (register unsigned i(1); i <= n; ++i) {
A = RD(), B = RDsg();
switch(A) {
case 1:{
TmpN = NULL, Insert(Root);
if(TmpN) {
if(TmpN == Root) {Root = Rebuild(TmpN);break;}
if(TmpNT) TmpNF->RS = Rebuild(TmpN);
else TmpNF->LS = Rebuild(TmpN);
}
break;
}
case 2:{
TmpN = NULL, Delete(Root);
if(TmpN) {
if(TmpN == Root) {Root = Rebuild(TmpN);break;}
if(TmpNT) TmpNF->RS = Rebuild(TmpN);
else TmpNF->LS = Rebuild(TmpN);
}
break;
}
case 3:{Ans = 1, Rank(Root);break;}
case 4:{Find(Root);break;}
case 5:{Ans = 0, Rank(Root), B = Ans, Find(Root);break;}
case 6:{++B, Ans = 1, Rank(Root), B = Ans, Find(Root);break;}
}
if(A >= 3) printf("%d\n", Ans);
}
return Wild_Donkey;
}
复杂度
首先假设不会有元素相同, 因为有元素相同只能在元素数量相同的情况下使节点更少, 显然不会使复杂度更劣, 所以考虑更坏的情况, 即元素各不相同.
然后假设对极度不平衡的定义是左子树比右子树的两倍还要大, 或右子树比左子树的两倍还要大.
对于一棵子树需要重构的, 子树节点规模为
另一种重构的情况是一棵子树的元素数量小于节点数的
这两种情况的分析证明了在