BZOJ3224 Tyvj 1728 普通平衡树 (替罪羊树)

本文介绍了替罪羊树(scapegoat tree)的数据结构及其应用。替罪羊树是一种自平衡二叉搜索树,通过在删除操作中进行局部重构来维持平衡。文中详细解释了替罪羊树的插入、删除及查找操作,并给出了具体的实现代码。

题目链接

思路:
                   学习一下替罪羊树。 替罪羊树就是确定一个 α α 因子,插入和常规平衡树插入无太大区别, 删除的时候节点还在, 只是做个标记已经删除过,然后查找和普通平衡树也没太大区别, 构建的时候当当前节点 x x 的子树大小 * α 某一个孩子子树的大小的时候, 暴力重建 x x 子树就是了。α的值一般取 0.7 0.7

#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 3e5 + 10;
const double alpha = 0.7;
using namespace std;

int stk[maxn], top, n, m, root;
int ls[maxn], rs[maxn], siz[maxn], travel[maxn];
int cnt[maxn], v[maxn], t1, goat;
bool exist[maxn];

int get_rank(int x) {
    int now = root, ans = 1;
    while(now) {
        if(v[now] >= x) now = ls[now];
        else {
            ans += siz[ls[now]] + exist[now];
            now = rs[now];
        }
    }
    return ans;
}

void dfs(int rt) {
    if(!rt) return ;
    dfs(ls[rt]);
    if(exist[rt]) travel[++t1] = rt;
    else stk[++top] = rt;
    dfs(rs[rt]);
}

void build(int &rt, int l, int r) {
    int mid = (l + r) >> 1;
    rt = travel[mid];
    if(l == r) {
        siz[rt] = cnt[rt] = 1;
        ls[rt] = rs[rt] = 0;
        return ;
    }
    if(l < mid) build(ls[rt], l, mid - 1);
    else ls[rt] = 0;
    if(mid < r) build(rs[rt], mid + 1, r);
    else rs[rt] = 0;
    siz[rt] = siz[ls[rt]] + siz[rs[rt]] + 1;
    cnt[rt] = cnt[ls[rt]] + cnt[rs[rt]] + 1;
}

void rebuild(int &rt) {
    t1 = 0; dfs(rt);
    if(t1) build(rt, 1, t1);
    else rt = 0;
}

void __insert(int &rt, int x) {
    if(rt == 0) {
        rt = stk[top--]; v[rt] = x;
        siz[rt] = cnt[rt] = exist[rt] = 1;
        ls[rt] = rs[rt] = 0;
        return ;
    } else {
        siz[rt]++; cnt[rt]++;
        if(x >= v[rt]) __insert(rs[rt], x);
        else __insert(ls[rt], x);
        if(siz[rt] * alpha < max(siz[ls[rt]], siz[rs[rt]])) rebuild(rt);
    }
}

int get_kth(int x) {
    int now = root;
    while(now) {
        if(exist[now] && siz[ls[now]] + 1 == x) return v[now];
        if(x <= siz[ls[now]]) now = ls[now];
        else {
            x -= exist[now] + siz[ls[now]];
            now = rs[now];
        }
    }
}

void __delete(int &rt, int rk) {
    if(exist[rt] && rk == siz[ls[rt]] + 1) {
        exist[rt] = false;
        siz[rt]--; return ;
    }
    if(!rt) return ;
    siz[rt]--;
    if(rk <= siz[ls[rt]] + exist[rt]) __delete(ls[rt], rk);
    else __delete(rs[rt], rk - exist[rt] - siz[ls[rt]]);
}


int main() {
    scanf("%d", &n); root = 0;
    for(int i = maxn - 2; i >= 1; i--) stk[++top] = i;
    for(int i = 1; i <= n; i++) {
        int opt, x;
        scanf("%d %d", &opt, &x);
        if(opt == 1) __insert(root, x);
        if(opt == 2) __delete(root, get_rank(x));
        if(opt == 3) printf("%d\n", get_rank(x));
        if(opt == 4) printf("%d\n", get_kth(x));
        if(opt == 5) printf("%d\n", get_kth(get_rank(x) - 1));
        if(opt == 6) printf("%d\n", get_kth(get_rank(x + 1)));
    }
    return 0;
}
### 替罪羊树的数据结构解释 替罪羊树是一种自平衡二叉搜索树,其设计目的是为了优化最坏情况下的性能。通过引入一个称为 **平衡因子 α** 的参数来控制子树的平衡状态[^1]。 当一棵以 \( x \) 为根的子树满足下列条件之一时,则认为该子树是不平衡的: - 左子树的节点数大于等于 \( |T_x| * α \),其中 \( T_x \) 表示以 \( x \) 为根的整棵树; - 或者右子树的节点数同样超过上述阈值; 一旦检测到某个子树失衡,整个子树将会被重新构建成为完全平衡的状态,从而恢复整体结构的良好特性。 #### 平衡因子的选择 通常情况下,默认设置 \( α=0.75 \)。然而,在具体应用场景下可以根据需求调整此比例,范围大致位于\[0.5, 1\]区间内。较小的\( α\)意味着更严格的平衡约束,进而可能导致更高的维护成本;反之较大的\( α\)则允许一定程度上的倾斜,减少不必要的重构操作次数。 ### 实现方式概述 相比于其他类型的自适应BSTs(如AVL或红黑树),替罪羊树并不依赖于每次插入/删除后的即时修正机制。相反地,它采取了一种延迟策略:只有当发现某部分严重偏离理想形态时才会触发全局性的重组动作。 ```cpp // C++代码片段展示如何检查并修复不平衡状况 void fixImbalance(Node* node){ int totalSize = sizeOfSubtree(node); if (node->left && getSize(node->left) >= alpha * totalSize || node->right&&getSize(node->right)>=alpha*totalSize){ rebuildTreeFromNode(node); // 对当前结点及其后代执行全量重排 } } ``` ### 性能分析 由于采用了按需重建而非频繁微调的方式处理失衡现象,因此替罪羊树能够在大多数时候保持较低的操作复杂度。特别是在面对大量随机输入序列时表现出色,因为此时很少会出现极端分布模式迫使系统进入耗时较长的大规模结构调整过程。 不过值得注意的是,尽管平均意义上具备良好的渐近时间界O(log n),但在某些特定场景下仍可能出现退化情形—即连续多次更新引发集中式的再分配事件链反应,造成瞬态性能波动。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值