题解 | 普通平衡树

alt

题干分析

题设要求我们动态维护一个有序数据结构,并进行若干次6种操作。

算法思路

本题有两种解决思路,一种是放弃使用树状结构,直接使用STL库提供的二分查找算法与vector容器实现。

另一种思路是维护树状的二叉查找树进行实现,同时为了提高查找效率,我们得防止我们构建的树过于不平衡,也就是说我们需要动态维护一个平衡二叉搜索树。这里个人做法是实现一颗替罪羊树,设定一个不平衡率,只要子树达到该不平衡率我们就直接重建一颗相对平衡的子树。同时针对删除,我们采取懒删除方式标记删除节点,当树发生重建时再真正删除。

实现代码

  • 使用STL库+vector容器实现:
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    vector<int> v;
    int n; cin >> n;
    for (int i = 0; i < n; ++i) {
        int op, x;
        cin >> op >> x;
        switch (op) {
            case 1:
                v.insert(ranges::lower_bound(v, x), x);
                break;
            case 2:
                v.erase(ranges::lower_bound(v, x));
                break;
            case 3:
                cout << ranges::lower_bound(v, x) - v.begin() + 1 << endl;
                break;
            case 4:
                cout << v[x - 1] << endl;
                break;
            case 5:
                cout << v[ranges::lower_bound(v, x) - v.begin() - 1] << endl;
                break;
            case 6:
                cout << v[ranges::upper_bound(v, x) - v.begin()] << endl;
                break;
            default: ;
        }
    }

    return 0;
}//*/
  • 维护替罪羊树的实现:
#include <bits/stdc++.h>
using namespace std;

// 替罪羊树
class ScapegoatTree {
    const int N; // 最高节点数
    const double alpha; // 不平衡率

    struct Node {
        int leftSon, rightSon; // 记录下标,为0表示空
        int val;
        int total; // 当前子树占用空间数量,包括懒删除节点
        int size; // 子树实际存在节点数
        bool del; // 删除标记
    };

    vector<Node> t; // 扁平存储
    vector<int> order; // 记录子树销毁后的节点大小顺序(下标)
    int cnt = 0; // 销毁子树的节点数
    stack<int> treeStack; // 使用栈回收和分配可用节点(下标)
    int root = 0; // 根节点(下标)

    // 中序遍历摧毁子树
    void inOrder(const int cur) {
        if (cur) {
            inOrder(t[cur].leftSon);
            if (t[cur].del) order[++cnt] = cur;
            else treeStack.push(cur);
            inOrder(t[cur].rightSon);
        }
    }

    // 重置节点参数
    void initNode(const int idx) {
        t[idx].leftSon = t[idx].rightSon = 0;
        t[idx].size = t[idx].total = 1;
        t[idx].del = true;
    }

    // 更新节点大小数据
    void update(const int idx) {
        t[idx].size = t[t[idx].leftSon].size + t[t[idx].rightSon].size + 1;
        t[idx].total = t[t[idx].leftSon].total + t[t[idx].rightSon].total + 1;
    }

    // 重建
    void build(int left, int right, int &cur) {
        int mid = (left + right) >> 1;
        cur = order[mid];
        // 叶子节点
        if (left == right) {
            initNode(cur);
            return;
        }
        // 重构左子树
        if (left < mid) build(left, mid - 1, t[cur].leftSon);
        if (left == mid) t[cur].leftSon = 0; // 左子树为空
        // 重构右子树
        build(mid + 1, right, t[cur].rightSon);
        // 更新大小
        update(cur);
    }

    // 启动重建
    void rebuild(int &tar) {
        cnt = 0;
        inOrder(tar);
        if (cnt) build(1, cnt, tar);
        else tar = 0;
    }

    // 判断是否平衡
    [[nodiscard]] bool isUnbalance(const int tar) const {
        return static_cast<double>(t[tar].size) * alpha <=
               static_cast<double>(max(t[t[tar].leftSon].size, t[t[tar].rightSon].size));
    }

    // 插入x
    void insert(int &u, const int x) {
        if (!u) {
            // u为空节点
            u = treeStack.top();
            treeStack.pop();
            t[u].val = x;
            initNode(u);
            return;
        }
        t[u].size++;
        t[u].total++;
        if (t[u].val >= x) insert(t[u].leftSon, x); // 插左树
        else insert(t[u].rightSon, x); // 插右树
        if (isUnbalance(u)) rebuild(u); // 不平衡,重建子树
    }

    // x的排名
    int rank(const int u, const int x) {
        if (u == 0) return 0;
        if (x > t[u].val) {
            const int tmp = t[t[u].leftSon].size + (t[u].del ? 1 : 0);
            return tmp + rank(t[u].rightSon, x);
        }
        return rank(t[u].leftSon, x);
    }

    // 删除排名k的数
    void delK(const int &u, const int k) {
        t[u].size--;
        if (t[u].del && t[t[u].leftSon].size + 1 == k) {
            t[u].del = false;
            return;
        }
        // 左子树上
        if (t[t[u].leftSon].size + (t[u].del ? 1 : 0) >= k) delK(t[u].leftSon, k);
            // 右子树上
        else delK(t[u].rightSon, k - t[t[u].leftSon].size - (t[u].del ? 1 : 0));
    }

public:
    explicit ScapegoatTree(const int N_ = 1e6 + 10, const double alpha_ = 0.75) : N(N_), alpha(alpha_) {
        t.resize(N);
        order.resize(N);
        // 记录所有可用的t
        for (int i = N - 1; i >= 1; --i) treeStack.push(i);
    }

    // 对外插入接口
    void Insert(const int x) { insert(root, x); }

    // 对外排名接口
    int Rank(const int x) { return rank(root, x); }

    // 排名第k大的数
    [[nodiscard]] int kth(int k) const {
        int u = root;
        while (u) {
            if (t[u].del && t[t[u].leftSon].size + 1 == k) return t[u].val;
            if (t[t[u].leftSon].size >= k) u = t[u].leftSon;
            else {
                k -= t[t[u].leftSon].size + (t[u].del ? 1 : 0);
                u = t[u].rightSon;
            }
        }
        return t[u].val;
    }

    // 对外删除第k个元素接口
    void DelK(const int k) { delK(root, k); }

    // 删除值为x的数
    void del(const int x) {
        delK(root, rank(root, x) + 1);
        // 已删除节点过多,重构整个树
        if (t[root].total * alpha >= t[root].size) rebuild(root);
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    ScapegoatTree tree;
    int n;
    cin >> n;
    for (int i = 0; i < n; ++i) {
        int op, x;
        cin >> op >> x;
        switch (op) {
            case 1:
                tree.Insert(x);
                break;
            case 2:
                tree.del(x);
                break;
            case 3:
                cout << tree.Rank(x) + 1 << endl;
                break;
            case 4:
                cout << tree.kth(x) << endl;
                break;
            case 5:
                cout << tree.kth(tree.Rank(x)) << endl;
                break;
            case 6:
                cout << tree.kth(tree.Rank(x + 1) + 1) << endl;
                break;
            default: ;
        }
    }

    return 0;
}

复杂度分析

  • 时间复杂度:

1类操作针对vector容器实现为,平衡树实现均摊为

2类操作针对vector容器实现也为,平衡树实现均摊为

3类操作二者均为

4类操作针对vector容器实现为,平衡树实现均摊为

5类操作二者均为

6类操作二者均为

  • 空间复杂度:二者均为
全部评论

相关推荐

2026年1月21日下午,这是目前经过的最难受的一次面试,也是面试官较为抽象,比较难评。我原本就有点黑节子(看到网上之前有人说负面评论被影响),这次本来就没有投递,hr看到以前的简历来找的我面试,想着面着试试手毕竟实习结束后也很久没有面试了。开场:面试官貌似不知道这是啥岗位,问我是校招还是实习。。。。你们招的人你不知道吗?我请问了。自我介绍:开局就疯狂打断。刚说完名字和学校就打断了,说后面再说,开始面试把(在这里演我了,打断别人真的很没有素质的ok,在这里我就不想面了)开始问实习经历:我介绍了在前司做的事情,面试官听了一边说嗯。然后问我背景是啥,我说完了,又问我你刚刚说做了啥事情来着。。。。。。。。。&nbsp;又介绍了一遍,开始上压力,问怎么做的,然后给我引起了另外一个他觉得相关的领域,我先说了这个领域不太熟悉,他说没事,现在想嘛。然后把我整个在实习中的问题带去不熟悉的地方,开始不断问还可以如何优化系统,大概想了几种答案。回复:可以但是不够好,最后貌似也没有给我答案。开始问数据库,也是抽象完了,问数据库为啥要用,索引实现原理(这个我会,不多说),然后问为啥索引要用这个原理,以一种从来没听过的方式问我,其实我答案在前面说了,但是又反复问,给我整不会了,全程完全脱离简历问。最后说答案我其实都知道,但是我知识有点散,要穿起来。回答:谢谢面试官,我会注意的。手撕无,问智力题。引导我回答,这点点个赞,but时间到了,让我自己想想,没给反问机会,直接断面试。体验:奇差无比,而且hr面试前跟我说了好几次,这个不是传统后端,是后端+ai&nbsp;agent。本人有大量ai&nbsp;agent工程和科研经验,所以才答应面试看看。结果啥都没问,问的也是抽象。貌似或者说根本没提前看过我简历,面试来也是迟到。并且感觉刚经历高强度工作很疲累。黑完了。
查看9道真题和解析
点赞 评论 收藏
分享
01-27 22:50
武汉大学 Java
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务