平衡树学习笔记

news/2025/1/30 0:51:08/文章来源:https://www.cnblogs.com/cxjy0322/p/18695005

替罪羊树

  • \(\alpha\) 平衡因子 ( \(0.5 < \alpha < 1\))

  • 替罪羊树原理 : 当树不平衡时 , 中序遍历收集树上的所有节点,然后用二分的方式重构整棵树, 使其变成标准的平衡树. 查询代价由树高决定, 重构代价由重构节点个数和平衡因子决定

  • 节点信息:

    • val , 键值,代表节点储存的信息
    • count : 平衡树最好不要有键值相同的节点, 插入重复的值只需要把对应节点的count 值修改就可以了.
    • left , right , 左右孩子指针.
    • diff , 子树中含有的节点数的多少 (即不同的数的个数)
    • size , 记录树上总共收集了多少个变量
  • \(\alpha\) 因子 越接近 1 , 重构次数越少, 查询复杂度上升, \(\alpha\) 越接近 0.5 , 树高越小, 重构次数越多, 查询时间复杂度下降


class TZYTree{
public:
#define ls(p)   t[p].l
#define rs(p)   t[p].rstruct node{int l , r;  // 左右孩子指针, 如果没有孩子就为0int val;    // 代表当前节点的值int diff;   // 代表子树中有多少个节点int count;  // 代表当前节点有多少个数int size;   // 代表子树中有多少个数node() : l(0) , r(0), diff(0),count(0),size(0),val(0){}};const double alpha = 0.7;   // 平衡因子int cnt = 0;                // 代表有序表当前已使用的节点数int top = 0;                // 代表最上方不平衡的节点, 如果 top = 0 , 则说明不存在不平衡的节点int Head = 0;               // 记录替罪羊树的根节点的下标, 如果表中无元素则下标为 0 int father = 0;             // 记录最上方不平衡的节点的父节点, 重构之后可能需要修改指针, 如果是整棵树 , 那么 father = 0int side = 0;               // 记录不最上方的不平衡节点是它的父节点的左孩子还是右孩子 , 1为左孩子 , 0 为右孩子int pos = 0;                // 记录 order 表中最后一个数据的下标 , 即 1 - posstatic const int inf = INT_MAX;std::vector<node> t;std::vector<int> order;TZYTree (int n) {// n 代表有序表最大数量assign(n);}void assign(int n){t.assign( n + 2,node());order.assign(n+2,0);}// 新增一个节点int add(int x) { t[++cnt].val = x;t[cnt].size = t[cnt].diff =t[cnt].count = 1;return cnt;}void up( int p){t[p].size = t[ls(p)].size + t[rs(p)].size + t[p].count;t[p].diff = t[ls(p)].diff + t[rs(p)].diff + (t[p].count > 0);}// 收集为中序遍历void inorder(int p) {if (p !=0){inorder(ls(p));if(t[p].count >0 ){order[++pos] = p; // 注意这里只是中序遍历把节点下标上传}inorder(rs(p));}}// l 代表order 中的序号 , 中序遍历重构int build(int l , int r) {if(l > r) return 0;int mid = (l + r)/2;int head = order[mid];ls(head) = build(l,mid-1);rs(head) = build(mid+1,r);up(head);return head;}void rebuild() {if(top != 0) {// 即存在不平衡现象pos = 0;inorder(top);if (pos > 0) {if( father == 0) {Head = build(1,pos); //  头节点重连} else if(side == 1) {ls(father) = build(1,pos);} else {rs(father) = build(1,pos);}}}}bool balance(int p) {// 如果子树中的节点数大于 整颗树的节点数乘以 alpha 倍, 则重构return alpha * t[p].diff >= std::max(t[ls(p)].diff , t[rs(p)].diff);}void insert(int number) {top = father = side = 0;insert(Head,0,0,number);rebuild();}void insert(int p,int f,int s,int num) {if(p == 0) {if(f == 0) {Head = add(num);} else if(s == 1) {ls(f) = add(num);} else {rs(f) = add(num);}} else {// assert(p >t.size());if( t[p].val == num) {t[p].count++;} else if(t[p].val > num) {insert(ls(p),p,1,num);} else {insert(rs(p),p,2,num);}up(p);if(!balance(p)) {top = p;father = f;side = s;}}}void remove(int p, int f, int s,int num) {if(t[p].val == num) {t[p].count--;} else if(t[p].val > num) {remove(ls(p),p,1,num);} else {remove(rs(p),p,2,num);}up(p);if(!balance(p)) {father = f , side = s , top = p;}}void remove(int num) {if(rank(num) != rank(num+1)) {top = father = side = 0;remove(Head,0,0,num);}}int rank(int num) {return small(Head,num) + 1;}int small(int p , int num) {if(p == 0) {return 0;}if(t[p].val >= num) {return small(ls(p),num);} else {return t[ls(p)].size + small(rs(p),num) + t[p].count;}}int index(int x) {return index(Head,x);}int index(int p, int x) {if(t[ls(p)].size >= x) {return index(ls(p),x);} else if(t[ls(p)].size + t[p].count >= x) {return p;} else {return index(rs(p),x - t[ls(p)].size - t[p].count);}}// 查找小于某个数的最大值int pre(int x) {int kth = rank(x);if(kth == 1) {return -inf;} else {return index(kth-1);}}// 查找大于某个数的最小值int post(int x) {int kth = rank(x + 1);if(kth == t[Head].size + 1) { // 也就是没有大于某个数的值就放回 infreturn inf;} else {return index(kth);}}void clear(){Head = cnt = 0;t.clear();order.clear();}#undef ls   
#undef rs   
};

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

虚拟记账系统之三种结算模式

虚拟记账系统作为近年来支付领域的创新产品,正成为企业资金管理和支付结算的重要工具。本文从支付断直连的背景出发,详细介绍了虚拟记账系统的三种结算模式:收单结算、归集直清和归集调拨,并深入探讨了这些模式在直播电商、企业资金管理等场景中的应用。从这篇文章开始,我…

RocketMQ实战—2.RocketMQ集群生产部署

大纲 1.什么是消息中间件 2.消息中间件的技术选型 3.RocketMQ的架构原理和使用方式 4.消息中间件路由中心的架构原理 5.Broker的主从架构原理 6.高可用的消息中间件生产部署架构 7.部署一个小规模的RocketMQ集群进行压测 8.如何对RocketMQ集群进行可视化的监控和管理 9.进行OS内…

Web 前端开发

通俗来说, Web 前端开发就是开发前端网页程序. 前端网页程序主要负责的就是将数据以好看的形式呈现出来. 网页有哪些部分组成: 文字、图片、音频、视频、超链接、表格... 前端的代码是如何转换成用户眼中的网页的: 通过浏览器转化 (解析和渲染) 成用户看到的网页. 浏览器中对代…

【 Github 】 如何获取 Github 上最新的 release 文件

前言 在 Github 上,我们经常会看到一些开源项目会发布 release,这些 release 文件通常是一些二进制文件或者压缩包,我们可以通过以下步骤获取到这些文件的最新版本。 1. 打开 Github 项目页面,查看最新的 release 版本 首先,我们需要打开 Github 项目页面,例如:https://…

【开源】gocron:一款开源可视化定时任务管理系统

在数字化转型的浪潮中,定时任务管理系统作为保障业务连续性和自动化运维的重要工具,扮演着举足轻重的角色。然而,传统的Linux-crontab等工具因其复杂的配置和有限的灵活性,难以满足现代企业和开发者对高效、用户友好任务调度的需求。正是在这样的背景下,Gocron应运而生,它…

【数据库】DrawDB:超好用的,免费数据库设计工具

引言 在软件开发过程中,数据库设计是一个至关重要的环节。 无论是关系型数据库还是非关系型数据库,良好的数据库设计都能显著提升系统的性能和可维护性。 然而,数据库设计往往伴随着复杂的表结构和关系,如何清晰地表达这些设计成为了开发者们的一大挑战。 DrawDB 应运而生,…

研发的立足之本到底是啥?

0 你的问题,我知道! 本文深入T型图“竖线”的立足之本:专业技术 + 技术赋能业务能力。研发在学习投入精力最多,也误区最多。 某粉丝感发展遇到瓶颈,项目都会做,但觉无提升,想跳槽。于是,梳理过往经历。 他觉得业务小,阻其技术发展。但细问,这系统用户量百万级,一点不…

Midscene.js:重新定义UI自动化的新时代工具

前言 Midscene.js 是一个创新的、面向开发者的 UI 自动化解决方案,并通过人工智能技术简化自动化脚本的编写与维护。 它提供了三种核心方法——交互(.ai, .aiAction)、提取(.aiQuery)和断言(.aiAssert),使开发者能够以自然语言描述步骤并执行复杂的UI操作。 它不仅支持…

Ant Design X:用最少的代码快速构建 AI 聊天界面

概述 随着人工智能(AI)技术的迅猛发展,尤其是通用人工智能(AGI)的进步,人机交互的方式正在经历深刻的变革。传统的图形用户界面(GUI)逐渐被更加自然、人性化的交互模式所取代。 为了应对这一变化,Ant Design X 团队提出了一种名为 RICH 的新设计范式,并开发了一系列专…

【Java应用】 Stream 流如何助力大数据处理

如果你会任意一门语言的stream流,没道理不会大数据开发。 俗话说男追女隔座山,女追男隔层纱。 如果说零基础学大数据,感觉前面是一座山,那么只要你会java或者任意一门语言的stream流,那大数据就只隔了一层纱。 本文以java stream流计算为例,讲解一些基础的spark操作。另一…

VaultWarden:用私有密码管理器保卫自己的密码

正值春节之际, 介绍一个非常实用的工具——VaultWarden,它是一个自托管的密码管理器,非常适合需要增强隐私保护的用户。如果你与笔者一样正在寻找一个简洁且安全的方式来管理密码,VaultWarden 无疑是一个不错的选择。 现在,我们将通过 Docker 来配置 VaultWarden,使其成为…