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

news2024/9/21 20:33:57

平衡树

定义

平衡树是一种自平衡的二叉搜索树,它在进行插入和删除操作后能够自动调整其结构,以保持树的高度尽可能低,从而保证树的查找、插入和删除操作能够在对数时间内完成。最著名的平衡树有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成为一个更强大的数据结构。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1927602.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

10月23-25日|2024年武汉袋式除尘展重磅来袭

2024第六届&#xff08;武汉&#xff09;国际袋式除尘技术与设备展览会 时间&#xff1a;2024年10月23-25日 地点&#xff1a;武汉国际文化博览中心 展会介绍&#xff1a; 2024第6届&#xff08;武汉&#xff09;国际袋式除尘技术与设备展览会将于2024年10月23-25日在武汉文…

Linux进程理解

一、进程的理解 首先我们知道我们的操作其实都是在运行程序&#xff0c;不仅是在windows上打开软件还是在Linux上执行指令&#xff0c;而程序存在于磁盘上&#xff0c;程序的要想运行就要把程序的代码和数据从磁盘加载到内存&#xff0c;那么到这一步是创建了一个进程吗&#…

c#中的特性

在C#中&#xff0c;特性&#xff08;Attributes&#xff09;是一种向程序元素&#xff08;如类、方法、属性等&#xff09;添加元数据的方式。特性可以用来提供关于程序元素的附加信息&#xff0c;这些信息可以在编译时和运行时被访问。 特性主要有以下几个用途&#xff1a; 提…

北京交通大学《深度学习》专业课,实验2-前馈神经网络

1. 源代码 见资源“北京交通大学《深度学习》专业课&#xff0c;实验2-前馈神经网络” 2. 实验内容 &#xff08;1&#xff09;手动实现前馈神经网络解决上述回归、二分类、多分类任务 分析实验结果并绘制训练集和测试集的loss曲线 &#xff08;2&#xff09;利用to…

GUI界面开发之tkinter(一)

Tkinter是一个内置的Python库&#xff0c;用于创建图形用户界面&#xff08;GUI&#xff09;。它提供了一组工具和小部件&#xff0c;用于创建窗口、对话框、按钮、菜单和其他GUI元素。 在本篇文章中&#xff0c;主要介绍了窗口等知识点。 大家好&#xff01;我是码银&#x1…

STM32MP135裸机编程:烧录程序到EMMC的方法

0 前言 STM32MP135支持多种启动方式&#xff0c;包括SD卡、NAND Flash、EMMC等&#xff0c;基于STM32MP135裸机的SD卡烧录操作方法我们之前已经介绍过&#xff0c;现在介绍的STM32MP135烧录到EMMC的方法又和前面烧录到SD卡的操作有所不同。本文将介绍基于STM32MP135&#xff0…

缓存的击穿及解决方案

定义及图解 缓存击穿的意思是对于设置了过期时间的key&#xff0c;缓存在某个时间点过期的时 候&#xff0c;恰好这时间点对这个Key有大量的并发请求过来&#xff0c;这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存&#xff0c;这个时候大并发的请求可能会瞬间把…

Flutter 调用Google内购支付最新教程

前言: 各位同学大家好, 之前看到有人在群里问flutter 怎么调用Google支付, 今天就准备整理写一篇文章。 效果图 实现方式: 我们是通过flutter和安卓交互 然后在原生安卓里面加入了内购支付结算库的依赖 最后调起的Google 支付 安卓原生内购支付教程 flutter 端代码 我…

Go: IM系统开发及注意事项

概述 使用Go语言打造支持&#xff0c;同时十万人在线的IM系统系统单机支持十万人&#xff0c;如果分布式部署后&#xff0c;支持数百万都是可以的IM 系统&#xff0c; 即时通讯(Instant Messaging)&#xff0c;比如说我们的微信&#xff0c;QQ 等IM 系统&#xff0c;它具备非常…

软件测试服务公司分享:系统测试和验收测试有什么联系和区别?

软件系统测试是指对软件系统的各个模块、组件以及整个系统进行全面检查和验证的过程。它旨在检测系统的功能是否符合需求、是否能够正常运行以及是否存在潜在的问题。在软件开发的整个生命周期中&#xff0c;系统测试是一个非常重要且必不可少的环节。 软件验收测试是软件在开…

vs code 启动react项目,执行npm start报错原因分析

1.执行 npm start错误信息&#xff1a;npm : 无法将“npm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写&#xff0c;如果包括路径&#xff0c;请确保路径正确&#xff0c;然后再试一次。 所在位置 行:1 字符: 1 npm start ~~~ CategoryInfo …

记录些Spring+题集(1)

接口防刷机制 接口被刷指的是同一接口被频繁调用&#xff0c;可能是由于以下原因导致&#xff1a; 恶意攻击&#xff1a;攻击者利用自动化脚本或工具对接口进行大量请求&#xff0c;以消耗系统资源、拖慢系统响应速度或达到其他恶意目的。误操作或程序错误&#xff1a;某些情…

【正点原子i.MX93开发板试用连载体验】录音小程序采集语料

本文最早发表于电子发烧友论坛&#xff1a;【新提醒】【正点原子i.MX93开发板试用连载体验】基于深度学习的语音本地控制 - 正点原子学习小组 - 电子技术论坛 - 广受欢迎的专业电子论坛! (elecfans.com) 接下来就是要尝试训练中文提示词。首先要进行语料采集&#xff0c;这是一…

C双指针元素去重

需求 在尾部插⼊、删除元素是⽐较⾼效的&#xff0c;时间复杂度 是 O(1)&#xff0c;但是如果在中间或者开头插⼊、删除元素&#xff0c;就会涉及数据的搬移&#xff0c;时间复杂度为 O(N)&#xff0c;效率较低。 代码 #include <stdio.h>// 相邻元素去重 int remove…

Linux-管道

管道&#xff1a;把一个命令的输出&#xff0c;通过管道连接&#xff0c;作为另一个命令的输入。管道的工作原理是通过一段共享内存来实现数据的传输&#xff0c;其中一个进程向管道写入数据&#xff0c;另一个进程则从管道的另一端读取数据。 1.可以通过ls -lh罗列出当前文件…

Smartbi体验中心升级新装,全产品线沉浸式体验

为了让用户更好地了解Smartbi产品在数据分析中的价值和能力&#xff0c;优化用户的产品体验&#xff0c;我们近期对Smartbi在线体验中心进行了全新改版和系统调优。本次更新内容包括全新首页、全新行业示例Demo、新增产品核心功能Demo&#xff0c;并优化了现有的Demo。让我们一…

直播预告|V学院|干货预警!2.5D、流光、动效?一次掌握!

随着可视化行业持续发展&#xff0c;可视化设计随之不断进化升级&#xff0c;对于设计师的视觉审美、设计能力和灵活运用等要求也随之进阶。可视化设计越来越卷了&#xff0c;设计师需要了解市场上的热点效果、优秀案例&#xff0c;持续学习&#xff0c;才能随行业发展不断进步…

C基础day9

一、思维导图 二、课后练习 1> 使用递归实现 求 n 的 k 次方 #include<myhead.h>int Pow(int n,int k) {if(k 0 ) //递归出口{return 1;}else{return n*Pow(n,k-1); //递归主体} }int main(int argc, const char *argv[]) {int n0,k0;printf("请输入n和k:&…

合合TextIn - 大模型加速器

TextIn是合合信息旗下的智能文档处理平台&#xff0c;在智能文字识别领域深耕17年&#xff0c;致力于图像处理、模式识别、神经网络、深度学习、STR、NLP、知识图谱等人工智能领域研究。凭借行业领先的技术实力&#xff0c;为扫描全能王、名片全能王等智能文字识别产品提供强大…

Jeecgboot vue3的选择部门组件JSelectDept如何实现只查询本级以及子级的部门

jeecgboot vue3的文档&#xff1a;地址 JSelectDept组件实现了弹窗然后选择部门返回的功能&#xff0c;但部门是所有数据&#xff0c;不符合需求&#xff0c;所以在原有代码上稍微改动了一下 组件属性值如下&#xff1a; 当serverTreeDatafalse的时候&#xff0c;从后端查询…