平衡树之替罪羊树

2022年01月14日,第一天

平衡树——替罪羊树(无旋)

核心操作:暴力重构,时间复杂度 O ( l o g   n ) O(log\ n) O(log n)

结点,替罪羊树的每个结点需要储存这几个信息:

  • 左右子树编号
  • 当前结点的值
  • 以当前结点为根的树的大小和实际的大小
  • 删除标记
struct node {
    int l, r, val;
    int size, fact;
    bool exist;
}tzy[N];
int cnt, root;
void newnode (int& now, int val) {
    now = ++ cnt;
    tzy[now].val = val;
    tzy[now].size = tzy[now].fact = 1;
    tzy[now].exist = true;
}
第一个操作:插入

替罪羊树的插入操作和二叉搜索树一模一样,只需要在最后加一句话判断树还是否平衡。

void insert (int& now, int val) {
    if (! now ) {
        newnode (now, val);
        check(root, now);
        return ;
    }
    tzy[now].size ++;
    tzy[now].fact ++;
    if (val < tzy[now].val) insert(tzy[now].l, val);
    else insert(tzy[now].r, val);
}
第二个操作:删除

替罪羊树的删除不是真正的删除,它会在要被删除的结点上打一个标记,也就是 删除标记。这被称作 惰性标记,而 实际大小 就是没有被打上删除标记的结点数量,同样删除后也要判断一下树是否平衡,是否要重构一下。

void del (int now, int val) {
    if (tzy[now].exist && tzy[now].val == val) {
        tzy[now].exist = false;
        tzy[now].fact --;
        check(root, now);
        return ;
    }
    tzy[now].fact --;
    if (val < tzy[now].val) del(tzy[now].l, val);
    else del(tzy[now].r, val);
}
第三个操作:检查并判断是否需要重构

我们在进行完插入和删除操作后要检查树是否需要重构,我们从根节点开始往刚刚操作的结点找,如果找到了一个需要重构的结点,那就暴力重构以它为根的子树,需要重构的条件是:

**(1) 以当前结点的左子树或者右子树的大小大于当前结点的大小乘以一个平衡因子 a l p h a alpha alpha **。

(2) 以当前结点为根的子树内被删除的结点数量大于树大小的 30 % 30\% 30%

注意:平衡因子 a l p h a alpha alpha 我们一般取 0.5 0.5 0.5 1 1 1 之间的数,通常我们取 0.75 0.75 0.75

const double alpha = 0.75; // 平衡因子
bool imbalence (int now ) {
    if (max(tzy[tzy[now].l].size, tzy[tzy[now].r].size) > tzy[now].size * alpha 
        || tzy[now].size - tzy[now].fact > tzy[now].size * 0.3) 
        return true;
    return false;
}
void check (int& now, int end) {
    if (now == end) return ;
    if (imbalence(now)) {
        rebuild(now);
        update(root, now);
        return ;
    }
    if (tzy[end].val < tzy[now].val) 
        check(tzy[now].l, end);
    else check(tzy[now].r, end); 
}
第四个操作:核心环节——重构

替罪羊树的重构是非常暴力的:首先把当前子树进行中序遍历拉成直线,然后分治拎起来

vector<int> v;
void inorder(int now) {
   	if (! now) return ;
    inorder(t[now].l);
    if (tzy[now].exist) 
        v.push_back(now);
    inorder(t[now].r);
}
void lift(int l, int r, int& now) {
    if (l == r) {
        now = v[l];
        tzy[now].l = tzy[now].r = 0;
        tzy[now].size = tzy[now].fact = 1;
        return ;
    }
    int mid = (l + r) >> 1;
    // 防止相等的元素在左边,统一相同的元素在右边
    while (mid && l < mid && tzy[v[mid]].val == tzy[v[mid - 1]].val) 
        mid --;
    now = v[mid];
    if (l < mid) lift(l, mid - 1, tzy[now].l);
    else tzy[now].l = 0;
    lift(mid + 1, r, tzy[now].r);
    tzy[now].size = tzy[tzy[now].l].size + tzy[tzy[now].r].size + 1;
    tzy[now].fact = tzy[tzy[now].r].size + tzy[tzy[noe].r].fact + 1;
}
void update(int now, int end) {
   	if (! now) return ;
    if (tzy[end].val < t[now].val) 
        update(tzy[now].l, end);
    else update(tzy[now].r, end);
    tzy[now].size = tzy[tzy[now].l].size + tzy[tzy[now].r].size + 1;
}
void rebuild(int& now) {
    v.clear();
    inorder(now);
    if (v.empty()) {
        now = 0;
        reutrn ;
    }
    lift(0, (int)v.size() - 1, now);
}
第五个操作:查询元素为 v a l val val 的排名

排名定义:比当前数小的数的个数 + 1 + 1 +1

int getrank(int val) {  // 非递归写法
    int now = root, rank = 1;
    while (now) {
        if (val <= tzy[now].val)
            now = tzy[now].l;
        else {
            rank += tzy[now].exist + tzy[tzy[now].l].fact;
            now = tzy[now].r;
        }rk
    }
    return rank;
}
第六个操作:查询排名为 r a n k rank rank 的元素
int getnum(int rank) {
    int now = root;
    while (now) {
        if (tzy[now].exist && tzy[tzy[now].l].fact + tzy[now].exist == rank)
            break;
        else if (tzy[tzy[now].l].fact >= rank) 
            now = tzy[now].l;
        else {
            rank -= tzy[tzy[now].l].fact + tzy[now].exist;
            now = tzy[now].r;
        }
    }
    return tzy[now].val;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值