平衡树——AcWing 253. 普通平衡树

平衡树

定义

平衡树是一种自平衡的二叉搜索树,它在进行插入和删除操作后能够自动调整其结构,以保持树的高度尽可能低,从而保证树的查找、插入和删除操作能够在对数时间内完成。最著名的平衡树有AVL树和红黑树。

  • AVL树:是一种严格的平衡树,任何节点的两个子树的高度最多相差1。因此,AVL树是最严格的平衡树之一,保证了树的平衡性,但这也意味着在进行插入和删除操作时可能需要较多的旋转操作来维持平衡。

  • 红黑树:是一种比较宽松的平衡树,它保证了从根到任一叶节点的最长路径不超过最短路径的两倍,也就是说,树的高度至多是log(n+1),其中n是节点数。红黑树通过颜色标记节点,以及一系列的旋转和重新着色操作来保持平衡。

运用情况

  • 数据库索引
  • 文件系统的目录结构
  • 编译器符号表
  • 实现关联容器(如C++ STL中的mapset
  • 高效的优先级队列实现

注意事项

  1. 性能考量:虽然平衡树提供了O(log n)的时间复杂度,但频繁的旋转操作可能会导致较高的常数因子,影响实际性能。
  2. 内存使用:平衡树需要额外的空间来存储平衡信息(如颜色或高度),这会略微增加内存消耗。
  3. 实现复杂性:平衡树的插入和删除操作涉及到复杂的旋转和平衡调整,实现起来较为复杂。
  4. 非严格平衡:红黑树相比AVL树,虽然更宽松的平衡条件可能导致更高的树高,但在大多数情况下提供更好的平均性能。

解题思路

  1. 识别问题:确定问题是否需要在一个动态集合中进行快速查找、插入和删除操作,或者需要维护一个有序集合。
  2. 选择合适的平衡树:根据问题的具体要求选择AVL树或红黑树。如果极端平衡是关键,则选择AVL树;如果更关心操作的平均性能,则选择红黑树。
  3. 实现细节
    • 插入操作:从根节点开始,按照二叉搜索树规则向下遍历,直到找到合适的位置插入新节点。之后,根据所选平衡树的规则,进行必要的旋转和平衡调整。
    • 删除操作:首先找到并删除目标节点,然后可能需要进行一系列的旋转和平衡调整,以保持树的平衡。
    • 查找操作:从根节点开始,根据目标键值与当前节点键值的比较结果,递归地在左子树或右子树中查找。
  4. 测试与调试:编写测试用例,特别是针对边界条件和极端情况的测试,确保平衡树在各种操作下都能正确地调整其结构并保持平衡。

AcWing 253. 普通平衡树  

题目描述

253. 普通平衡树 - AcWing题库

运行代码

#include <iostream>
#include <cstdlib>
#define N 100010
#define inf (int)1e9

using namespace std;

int n, x, op;
int root, idx;

struct TreapNode {
    int l, r;
    int val, key;
    int size, cnt;

    TreapNode() : l(0), r(0), val(0), key(0), size(0), cnt(0) {}
} tr[N];

// 更新节点的子树大小
void updateSize(int p) {
    tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + tr[p].cnt;
}

// 获取新节点
int createNode(int key) {
    tr[++idx].key = key;
    tr[idx].val = rand();
    tr[idx].cnt = tr[idx].size = 1;
    return idx;
}

// 右旋操作
void rotateRight(int& p) {
    int q = tr[p].l;
    tr[p].l = tr[q].r;
    tr[q].r = p;
    p = q;
    updateSize(p);
    updateSize(tr[p].r);
}

// 左旋操作
void rotateLeft(int& p) {
    int q = tr[p].r;
    tr[p].r = tr[q].l;
    tr[q].l = p;
    p = q;
    updateSize(p);
    updateSize(tr[p].l);
}

// 构建初始的 Treap
void buildTreap() {
    createNode(-inf);
    createNode(inf);
    root = 1;
    tr[root].r = 2;
    updateSize(root);
    if (tr[1].val < tr[2].val) rotateRight(root);
}

// 插入节点
void insertNode(int& p, int key) {
    if (p == 0) {
        p = createNode(key);
        return;
    }

    if (tr[p].key == key) {
        tr[p].cnt++;
    } else if (tr[p].key > key) {
        insertNode(tr[p].l, key);
        if (tr[tr[p].l].val > tr[p].val) rotateRight(p);
    } else {
        insertNode(tr[p].r, key);
        if (tr[tr[p].r].val > tr[p].val) rotateLeft(p);
    }
    updateSize(p);
}

// 删除节点
void removeNode(int& p, int key) {
    if (p == 0) return;

    if (tr[p].key == key) {
        if (tr[p].cnt > 1) {
            tr[p].cnt--;
        } else if (tr[p].l || tr[p].r) {
            if (tr[p].r == 0 || tr[tr[p].l].val > tr[tr[p].r].val) {
                rotateRight(p);
                removeNode(tr[p].r, key);
            } else {
                rotateLeft(p);
                removeNode(tr[p].l, key);
            }
        } else {
            p = 0;
        }
    } else if (tr[p].key > key) {
        removeNode(tr[p].l, key);
    } else {
        removeNode(tr[p].r, key);
    }
    updateSize(p);
}

// 通过值获取排名
int getRankByValue(int p, int key) {
    if (p == 0) return 0;
    if (tr[p].key == key) return tr[tr[p].l].size + 1;
    if (tr[p].key > key) return getRankByValue(tr[p].l, key);
    return tr[tr[p].l].size + tr[p].cnt + getRankByValue(tr[p].r, key);
}

// 通过排名获取值
int getValueByRank(int p, int rank) {
    if (p == 0) return inf;
    if (tr[tr[p].l].size >= rank) return getValueByRank(tr[p].l, rank);
    if (tr[tr[p].l].size + tr[p].cnt >= rank) return tr[p].key;
    return getValueByRank(tr[p].r, rank - tr[tr[p].l].size - tr[p].cnt);
}

// 获取前驱
int getPredecessor(int p, int key) {
    if (p == 0) return -inf;
    if (tr[p].key >= key) return getPredecessor(tr[p].l, key);
    return max(tr[p].key, getPredecessor(tr[p].r, key));
}

// 获取后继
int getSuccessor(int p, int key) {
    if (p == 0) return inf;
    if (tr[p].key <= key) return getSuccessor(tr[p].r, key);
    return min(tr[p].key, getSuccessor(tr[p].l, key));
}

int main() {
    buildTreap();
    cin >> n;
    while (n--) {
        cin >> op >> x;
        if (op == 1) insertNode(root, x);
        else if (op == 2) removeNode(root, x);
        else if (op == 3) cout << getRankByValue(root, x) - 1 << endl;
        else if (op == 4) cout << getValueByRank(root, x + 1) << endl;
        else if (op == 5) cout << getPredecessor(root, x) << endl;
        else cout << getSuccessor(root, x) << endl;
    }
    return 0;
}

代码思路

  1. 定义TreapNode结构体

    • 每个节点包含左子树指针l和右子树指针r
    • val是节点的优先级,由随机数生成,用于平衡树。
    • key是节点存储的实际值。
    • size是节点子树的节点总数。
    • cnt是节点的重复计数,用于处理具有相同键值的元素。
  2. 辅助函数

    • updateSize用于更新节点的子树大小。
    • createNode用于创建新节点。
    • rotateRightrotateLeft分别执行右旋和左旋操作,以保持树的平衡。
    • buildTreap初始化Treap,插入最小值和最大值节点作为边界。
  3. 主要操作

    • insertNode用于插入新节点,根据优先级和键值进行插入和旋转操作。
    • removeNode用于删除节点,考虑到节点的重复计数和平衡调整。
    • getRankByValue返回给定键值的排名。
    • getValueByRank返回给定排名的键值。
    • getPredecessorgetSuccessor分别返回给定键值的前驱和后继。
  4. 主函数流程

    • 初始化Treap。
    • 循环读取操作指令和参数。
    • 根据操作类型执行相应的函数调用。
    • 输出结果。

改进思路

  1. 随机数生成: 当前的代码使用rand()函数生成随机数作为节点的优先级,但rand()在某些情况下可能不是最优选择,因为它需要调用系统函数且可能有周期性问题。可以改为使用更高效的伪随机数生成器,例如C++11中的std::mt19937

  2. 分离接口与实现: 将Treap的操作接口(如插入、删除、查询等)与具体实现分离,可以使代码更模块化,易于维护和扩展。例如,可以创建一个Treap类,将所有相关的数据成员和方法封装在一起。

  3. 异常处理: 添加适当的错误检查和异常处理机制,比如在插入不存在的节点时抛出异常,或者在操作失败时给出明确的错误信息。

  4. 代码简化: 有些操作可以通过简化逻辑来提高效率。例如,在删除节点时,如果一个节点只有单边子树,可以直接用子树替换该节点,避免不必要的旋转。

  5. 性能优化: 对于大量操作的场景,可以考虑使用懒惰更新技术来延迟更新size字段,直到真正需要它为止。这样可以减少每次操作时的计算量。

  6. 并行化: 如果Treap将用于多线程环境,可以考虑添加锁或其他同步机制来保证线程安全,或者设计一种无锁的并发Treap。

  7. 序列化与持久化: 实现Treap的序列化和反序列化功能,使其能够保存到磁盘或网络传输,以便于持久化存储和远程调用。

  8. 测试与调试: 增加单元测试来验证每个功能的正确性,以及压力测试来确保Treap在大数据集下的稳定性和性能。

  9. 文档与注释: 为代码添加详细的注释和文档,说明每个函数的目的、参数和返回值,这将帮助其他开发者理解和使用你的Treap实现。

  10. 功能扩展: 考虑增加更多的功能,如区间查询、区间修改等,这将使Treap成为一个更强大的数据结构。

  • 9
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
解释: 1. self.epsilon = 0.1 if e_greedy_increment is not None else self.epsilon_max: 这行代码是用来设置epsilon的值。epsilon是用于控制在强化学习中探索和利用之间的平衡。如果e_greedy_increment不为None,即存在增量值,那么epsilon的初始值为0.1;否则,epsilon的初始值为self.epsilon_max。 2. self.lr_decay_rate = 0.95和self.lr_decay_step = 10000: 这两行代码是用于定义学习率的衰减速率和衰减步数。学习率衰减是为了让模型在训练过程中逐渐降低学习率,以便更好地收敛到最优解。在这里,学习率以指数衰减的方式进行更新,每经过10000个步骤,学习率会以0.95的衰减速率进行衰减。 3. self.lr = tf.train.exponential_decay(self.learning_rate, self.global_step, self.lr_decay_step, self.lr_decay_rate, staircase=True): 这行代码定义了学习率的指数衰减方式。tf.train.exponential_decay函数用于计算学习率的衰减值。其中,learning_rate是初始学习率,global_step是当前训练步数,lr_decay_step是衰减步数,lr_decay_rate是衰减速率,staircase=True表示学习率以阶梯函数的形式进行衰减。 4. self.l_r = self.learning_rate: 这行代码将初始学习率赋值给l_r,可能用于后续的学习率更新。 5. self.gama = 3、self.tau = 0.5和self.r_base = [0]: 这三行代码定义了一些参数。gama是拉格朗日乘子,用于某些优化问题中的约束条件;tau是计算reward滑动平均的参数,用于平滑reward的变化;r_base是一个包含单个元素0的列表,可能用于存储reward的基准值。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

筱姌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值