P5076 【深基16.例7】普通二叉树(简化版)题解

题目

您需要写一种数据结构,来维护一些数(都是绝对值10^{9}以内的数)的集合,最开始时集合是空的。其中需要提供以下操作,操作次数q不超过10^{4}

  1. 定义数x的排名为集合中小于x的数的个数+1。查询数x的排名。注意x不一定在集合里。
  2. 查询排名为x(x≥1) 的数。保证集合里至少有x个数。
  3. 求x的前驱(前驱定义为小于x,且最大的数)。若不存在则输出−2147483647。
  4. 求x的后继(后继定义为大于x,且最小的数)。若不存在则输出2147483647。
  5. 插入一个数x,本题的数据保证插入前x不在集合中。

保证执行1,3,4操作时,集合中有至少一个元素。

输入输出格式

输入格式

第一行是一个整数q,表示操作次数。

接下来q行,每行两个整数op,x,分别表示操作序号以及操作的参数x。

输出格式

输出有若干行。对于操作1,2,3,4,输出一个整数,表示该操作的结果。

输入输出样例

输入样例

7
5 1
5 3
5 5
1 3
2 2
3 3
4 3

输出样例

2
3
1
5

解析1

BST,二叉搜索树,又叫二叉排序树,是一棵空树或具有以下几种性质的树:

  1. 若左子树不空,则左子树上所有结点的值均小于它的根结点的值

  2. 若右子树不空,则右子树上所有结点的值均大于它的根结点的值

  3. 左、右子树也分别为二叉排序树

  4. 没有权值相等的结点。

第4条在数据中遇到多个相等的数我们可以多加一个计数器,就是当前这个值出现了几遍。

那么我们的每一个节点都包含以下几个信息:

  1. 当前节点的权值,也就是序列里的数

  2. 左孩子的下标和右孩子的下标,如果没有则为0

  3. 计数器,代表当前的值出现了几遍

  4. 子树大小和自己的大小的和

节点是这样的:

struct node{
	int val,ls,rs,cnt,siz;
}tree[500010];

其中val是权值,ls /rs是左/右孩子的下标,cnt是当前的权值出现了几次,siz 是子树大小和自己的大小的和。

以下均以递归方式呈现。


插入:

x是当前节点的下标,v是要插入的值。要在树上插入一个v的值,就要找到一个合适v的位置,如果本身树的节点内有代表v的值的节点,就把该节点的计数器加1 ,否则一直向下寻找,直到找到叶子节点,这个时候就可以从这个叶子节点连出一个儿子,代表v的节点。具体向下寻找该走左儿子还是右儿子是根据二叉搜索树的性质来的。

void add(int x,int v)
{
	tree[x].siz++;
	//如果查到这个节点,说明这个节点的子树里面肯定是有v的,所以siz++
	if(tree[x].val==v){
		//如果恰好有重复的数,就把cnt++,退出即可,因为我们要满足第四条性质
		tree[x].cnt++;
		return ;
	}
	if(tree[x].val>v){//如果v<tree[x].val,说明v实在x的左子树里
		if(tree[x].ls!=0)
		  add(tree[x].ls,v);//如果x有左子树,就去x的左子树
		else{//如果不是,v就是x的左子树的权值
			cont++;//cont是目前BST一共有几个节点
			tree[cont].val=v;
			tree[cont].siz=tree[cont].cnt=1;
			tree[x].ls=cont;
		}
	}
	else{//右子树同理
		if(tree[x].rs!=0)
		  add(tree[x].rs,v);
		else{
			cont++;
			tree[cont].val=v;
			tree[cont].siz=tree[cont].cnt=1;
			tree[x].rs=cont;
		}
	}
}

找前驱:

x是当前的节点的下标,val是要找前驱的值,ans是目前找到的比val小的数的最大值。

找前驱的方法也是不断的在树上向下爬找具体节点,具体爬的方法可以参考代码注释部分。

int queryfr(int x, int val, int ans) {
	if (tree[x].val>=val)
	{//如果当前值大于val,就说明查的数大了,所以要往左子树找
		if (tree[x].ls==0)//如果没有左子树就直接返回找到的ans
			return ans;
		else//如果不是的话,去查左子树
			return queryfr(tree[x].ls,val,ans);
	}
	else
	{//如果当前值小于val,就说明我们找比val小的了
		if (tree[x].rs==0)//如果没有右孩子,就返回tree[x].val,因为走到这一步时,我们后找到的一定比先找到的大(参考第二条性质)
			return (tree[x].val<val) ? tree[x].val : ans
		//如果有右孩子,,我们还要找这个节点的右子树,因为万一右子树有比当前节点还大并且小于要找的val的话,ans需要更新
		if (tree[x].cnt!=0)//如果当前节数的个数不为0,ans就可以更新为tree[x].val
			return queryfr(tree[x].rs,val,tree[x].val);
		else//反之ans不需要更新
			return queryfr(tree[x].rs,val,ans);
	}
}

找后继

与找前驱同理,只不过反过来了,在这里我就不多赘述了。

int queryne(int x, int val, int ans) {
	if (tree[x].val<=val)
	{
		if (tree[x].rs==0)
			return ans;
		else
			return queryne(tree[x].rs,val,ans);
	}
	else
	{
		if (tree[x].ls==0)
			return (tree[x].val>val)? tree[x].val : ans;
		if (tree[x].cnt!=0)
			return queryne(tree[x].ls,val,tree[x].val);
		else
			return queryne(tree[x].ls,val,ans);
	}
}

按值找排名:

这里我们就要用到 siz了,排名就是比这个值要小的数的个数再+1,所以我们按值找排名,就可以看做找比这个值小的数的个数,最后加上1即可。

int queryval(int x,int val)
{
	if(x==0) return 0;//没有排名 
	if(val==tree[x].val) return tree[tree[x].ls].siz;
	//如果当前节点值=val,则我们加上现在比val小的数的个数,也就是它左子树的大小 
	if(val<tree[x].val) return queryval(tree[x].ls,val);
	//如果当前节点值比val大了,我们就去它的左子树找val,因为左子树的节点值一定是小的 
	return queryval(tree[x].rs,val)+tree[tree[x].ls].siz+tree[x].cnt;
	//如果当前节点值比val小了,我们就去它的右子树找val,同时加上左子树的大小和这个节点的值出现次数 
	//因为这个节点的值小于val,这个节点的左子树的各个节点的值一定也小于val 
}
//注:这里最终返回的是排名-1,也就是比val小的数的个数,在输出的时候记得+1

按排名找值:

因为性质1和性质2,我们发现排名为n的数在BST上是第n靠左的数。或者说排名为n的数的节点在BST中,它的左子树的siz与它的各个祖先的左子树的siz相加恰好=n (这里相加是要减去重复部分)。

所以问题又转化成上一段或者说的后面的部分

rk是要找的排名

int queryrk(int x,int rk)
{
	if(x==0) return INF; 
	if(tree[tree[x].ls].siz>=rk)//如果左子树大小>=rk了,就说明答案在左子树里 
		return queryrk(tree[x].ls,rk);//查左子树 
	if(tree[tree[x].ls].siz+tree[x].cnt>=rk)//如果左子树大小加上当前的数的多少恰好>=k,说明我们找到答案了 
		return tree[x].val;//直接返回权值 
	return queryrk(tree[x].rs,rk-tree[tree[x].ls].siz-tree[x].cnt);
	//否则就查右子树,同时减去当前节点的次数与左子树的大小 
}

删除:

具体就是利用二叉搜索树的性质在树上向下爬找到具体节点,把计数器-1。与上文同理。

完整代码

#include<iostream>
using namespace std;
const int INF=0x7fffffff;
int cont;
struct node{
	int val,siz,cnt,ls,rs;
}tree[1000010];
void add(int x,int v){
	tree[x].siz++;
	if(tree[x].val==v){
		tree[x].cnt++;
		return;
	}
	if(tree[x].val>v){
		if(tree[x].ls!=0){
			add(tree[x].ls,v);
		}
		else{
			cont++;
			tree[cont].val=v;
			tree[cont].siz=tree[cont].cnt=1;
			tree[x].ls=cont;
		}
	}
	else{
		if(tree[x].rs!=0){
			add(tree[x].rs,v);
		}
		else{
			cont++;
			tree[cont].val=v;
			tree[cont].siz=tree[cont].cnt=1;
			tree[x].rs=cont;
		}
	}
}
int queryfr(int x,int val,int ans){
	if(tree[x].val>=val){
		if(tree[x].ls==0){
			return ans;
		}
		else{
			return queryfr(tree[x].ls,val,ans);
		}
	}
	else{
		if(tree[x].rs==0){
			return tree[x].val;
		}
		return queryfr(tree[x].rs,val,tree[x].val);
	}
}
int queryne(int x,int val,int ans){
	if(tree[x].val<=val){
		if(tree[x].rs==0){
			return ans;
		}
		else{
			return queryne(tree[x].rs,val,ans);
		}
	}
	else{
		if(tree[x].ls==0){
			return tree[x].val;
		}
		return queryne(tree[x].ls,val,tree[x].val);
	}
}
int queryrk(int x,int rk){
	if(x==0){
		return INF;
	}
	if(tree[tree[x].ls].siz>=rk){
		return queryrk(tree[x].ls,rk);
	}
	if(tree[tree[x].ls].siz+tree[x].cnt>=rk){
		return tree[x].val;
	}
	return queryrk(tree[x].rs,rk-tree[tree[x].ls].siz-tree[x].cnt);
}
int queryval(int x,int val){
	if(x==0){
		return 0;
	}
	if(val==tree[x].val){
		return tree[tree[x].ls].siz;
	}
	if(val<tree[x].val){
		return queryval(tree[x].ls,val);
	}
	return queryval(tree[x].rs,val)+tree[tree[x].ls].siz+tree[x].cnt;
}
int main(){
	int n,opt,xx;
	cin>>n;
	while(n--){
		cin>>opt>>xx;
		if(opt==1){
			cout<<queryval(1,xx)+1<<endl;
		}
		else if(opt==2){
			cout<<queryrk(1,xx)<<endl;
		}
		else if(opt==3){
			cout<<queryfr(1,xx,-INF)<<endl;
		}
		else if(opt==4){
			cout<<queryne(1,xx,INF)<<endl;
		}
		else{
			if(cont==0){
				cont++;
				tree[cont].cnt=tree[cont].siz=1;
				tree[cont].val=xx;
			}
			else{
				add(1,xx);
			}
		}
	}
	return 0;
}

解析2

使用multiset,它是C++STL里的一种容器。头文件 #include<set>

multiset性质:

  • 里面的元素按顺序排列,默认升序。
  • 不去重(这点和set是不同的)。

常用方法

multiset<int>q;
//定义一个multiset,尖括号里写类型
//如果是自定义类型,需要重载小于号 

q.insert(x);
//插入一个数 x 

q.clear();
//清空 

q.erase(x);
//删除容器中的所有值为 x 的数 

q.erase(it);
//删除容器中迭代器it指向的元素 

q.empty();
//返回bool值,如果容器为空返回true,否则返回false 

q.size()
//返回元素个数

q.begin();
//返回首个元素的迭代器 

q.end();
//返回最后一个元素的下一个位置的迭代器 

q.count(x);
//返回容器中 x 的个数 

q.find(x);
//返回容器中第一个x的位置(迭代器),如果没有就返回q.end() 

q.lower_bound(x);
//返回容器中第一个大于等于x的数的迭代器 

q.upper_bound(x);
//返回容器中第一个大于x的数的迭代器 

分析题目

1. 查询 x 数的排名

用lower_bound方法,找到第一个x的位置。

然后从begin开始往后遍历容器,只要达到这个位置,就输出当前下标即可。

2.查询排名为 x 的数

遍历容器,只要当前排名到达x,就输出当前值。(因为multiset容器无法进行随机访问)

3.求 x 的前驱(前驱定义为小于 x,且最大的数)

前驱,也就是x的前一个。用lower_bound方法找到第一个x的位置,然后输出上一个就OK了。

4.求 x 的后继(后继定义为大于 x,且最小的数)

后继,也就是第一个大于x的数。用upper_bound方法,直接找到这个值。

5.插入一个数 x

直接用insert方法插入即可。

完整代码

#include<iostream>
#include<cmath>
#include<algorithm>
#include<set>
using namespace std;
multiset<int>q;
int n,t,x,order;
int main()
{
    q.insert(-0x7fffffff);
    q.insert(0x7fffffff);
    //提前放入这两个数,避免错误
    cin>>n;
    while(n--)
    {
        cin>>t>>x;
        if(t==1)
        {
            multiset<int>::iterator it=q.lower_bound(x);
            order=0;
            for(multiset<int>::iterator i=q.begin();i!=it;i++,order++);
            cout<<order<<endl;
        }
        else if(t==2)
        {
            order=-1;
            for(multiset<int>::iterator it=q.begin();it!=q.end();it++)
            {
                order++;
                if(order==x)
                    cout<<*it<<endl;
            }
        }
        else if(t==3)
        {
            multiset<int>::iterator it=q.lower_bound(x);
            cout<<*--it<<endl;
        }
        else if(t==4)
        {
            cout<<*q.upper_bound(x)<<endl;
        }
        else
        {
            q.insert(x);
        }
    }
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【ICM】好奇心机制

文章目录 样本经验处理降低图片像素和通道构建连续状态捕捉动作经验回放类 各部分的模型编码器模型反向模型正向模型DQN模型ICM 的 反向传播 概念补充强化学习组成元素按照学习目标来分按照策略更新方式区分强化学习on-line 与 off-line经验回放 全部代码 样本经验处理 降低图…

什么是物联网?物联网如何工作?

物联网到底是什么&#xff1f; 物联网(Internet of Things&#xff0c;IoT)的概念最早于1999年被提出&#xff0c;官方解释为“万物相连的互联网”&#xff0c;是在互联网基础上延伸和扩展&#xff0c;将各种信息传感设备与网络结合起来而形成的一个巨大网络&#xff0c;可以实…

无法启动报,To install it, you can run: npm install --save @/components/iFrame/index

运行的过程中后台报错 npm install --save /components/iFrame/index&#xff0c;以为是安装三方依赖错误&#xff0c;经过多次重装node_modules依然没有用。 没办法&#xff0c;只能在项目中搜索 components/iFrame/index这个文件。。突然醒悟。。。 有时候&#xff0c;犯迷…

MySQL面试题【全面】2024

基础内容 1、MySQL的架构分层 &#xff08;1&#xff09;Serve层&#xff1a;负责建立连接、分析和执行 SQL。 MySQL 大多数的核心功能模块都在这实现&#xff0c;主要包括连接器&#xff0c;查询缓存、解析器、预处理器、优化器、执行器等。另外&#xff0c;所有的内置函数&…

详解C#之WinForm版利用RichTextBox 制作文本编辑器【附源码】

在Windows应用程序开发中&#xff0c;刚刚介绍了WPF版的利用RichTextBox实现文本编辑器&#xff0c;今天继续推出WinForm版的利用RichTextBox实现文本编辑器。本文利用一个简单的小例子&#xff0c;简述如何在WinForm开发中&#xff0c;利用RichTextBox开发文本编辑器&#xff…

Spring中@import注解终极揭秘!

技术概念 它能干啥 Import注解在Spring框架中主要用于解决模块化和配置管理方面的技术问题&#xff0c;它可以帮助开发者实现以下几个目标&#xff1a; 模块化配置&#xff1a;在大型项目中&#xff0c;通常需要将配置信息分散到多个配置类中&#xff0c;以便更好地组织和管…

C++面试干货---带你梳理常考的面试题(二)

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 1.struct 和 class 区别 1.默认访问权限&#xff1a;struct中的成员默认为public&#xff0c;而class中的成员默认为priv…

力扣404 左叶子之和 Java版本

文章目录 题目描述解题思路代码 题目描述 给定二叉树的根节点 root &#xff0c;返回所有左叶子之和。 示例 1&#xff1a; 输入: root [3,9,20,null,null,15,7] 输出: 24 解释: 在这个二叉树中&#xff0c;有两个左叶子&#xff0c;分别是 9 和 15&#xff0c;所以返回 2…

二手手机管理系统|基于Springboot的二手手机管理系统设计与实现(源码+数据库+文档)

二手手机管理系统目录 目录 基于Springboot的二手手机管理系统设计与实现 一、前言 二、系统设计 三、系统功能设计 1、用户管理功能的实现界面 2、用户中心管理功能的实现界面 3、新闻信息管理功能的实现界面 4、商品收藏管理功能的实现界面 5、订单管理功能的实现界…

2024年3月6日 十二生肖 今日运势

小运播报&#xff1a;2024年3月6日&#xff0c;星期三&#xff0c;农历正月廿六 &#xff08;甲辰年丁卯月己巳日&#xff09;&#xff0c;法定工作日。 红榜生肖&#xff1a;牛、猴、鸡 需要注意&#xff1a;鼠、虎、猪 喜神方位&#xff1a;东北方 财神方位&#xff1a;正…

【pyinstaller打包记录】Windows系统打包exe后,onnxruntime报警告(Init provider bridge failed)

简介 PyInstaller 是一个用于将 Python 程序打包成可执行文件&#xff08;可执行程序&#xff09;的工具。它能够将 Python 代码和其相关的依赖项&#xff08;包括 Python 解释器、依赖的模块、库文件等&#xff09;打包成一个独立的可执行文件&#xff0c;方便在不同环境中运行…

【Java设计模式】五、建造者模式

文章目录 1、建造者模式2、案例&#xff1a;共享单车的创建3、其他用途 1、建造者模式 某个对象的构建复杂将复杂的对象的创建 和 属性赋值所分离&#xff0c;使得同样的构建过程可以创建不同的表示建造的过程和细节调用者不需要知道&#xff0c;只需要通过构建者去进行操作 …

【SpringBoot3.x教程 01】SpringBoot简介及工程搭建

前言&#xff1a;什么是SpringBoot&#xff1f; SpringBoot是一个开源的Java基础框架&#xff0c;它被设计来简化Spring应用的初始搭建以及开发过程。这个框架利用了“约定优于配置”的理念&#xff0c;提供了一系列大型项目中常用的默认配置&#xff0c;让开发者可以快速启动和…

kafka查看消息两种方式(命令行和软件)+另附发送消息方式

1、命令行方式 ①找到kafka安装文件夹 ②执行命令 #指定offset为指定时间作为消息起始位置 kafka-consumer-groups.sh \ --bootstrap-server 20.2.246.116:9092 \ --group group_1 \ --topic lanxin_qiao \ --reset-offsets \ --to-datetime 2023-07-19T01:00:00.000 \ -exe…

解决Maven项目中的依赖冲突

1. 排查依赖冲突 在IDEA中下载插件 Maven Helper用于排查依赖版本冲突。 打开项目的pom.xml文件&#xff0c;点击下方的【Dependency Analyzer】按钮切换到依赖解析页面。 2. 解决版本依赖 在依赖解析页面进行依赖冲突排查操作&#xff1a; 点击 【Exclude】 后会在爆红处所对…

Java多线程实现发布和订阅

目录 简介 步骤 1: 定义消息类 步骤 2: 创建发布者 步骤 3: 创建订阅者 步骤 4: 实现发布-订阅模型 前言-与正文无关 生活远不止眼前的苦劳与奔波&#xff0c;它还充满了无数值得我们去体验和珍惜的美好事物。在这个快节奏的世界中&#xff0c;我们往往容易陷入工作的漩涡…

Claude 3正式发布,超越GPT-4,一口气读15万单词,OpenAI最强的大对手!

目录 多模态AI大模型Claude 3&#xff08;https://www.anthropic.com/news/claude-3-family&#xff09;Claude 3 的三个版本新增功能&#xff0c;chatgpt没有的使用成本总结 多模态AI大模型Claude 3&#xff08;https://www.anthropic.com/news/claude-3-family&#xff09; …

Stable Diffusion 3报告

报告链接&#xff1a;https://stability.ai/news/stable-diffusion-3-research-paper 文章目录 要点表现架构细节通过重新加权改善整流流量Scaling Rectified Flow Transformer Models灵活的文本编码器RF相关论文 要点 发布研究论文&#xff0c;深入探讨Stable Diffuison 3的…

Sora到底有多强?

北京时间2月16日凌晨&#xff0c;OpenAI发布文本生成视频的AI模型Sora&#xff0c;瞬时刷屏科技圈&#xff0c;成为2024年开年“顶流”。 官方称&#xff0c;Sora只需文本就能自动生成高度逼真和高质量的视频&#xff0c;且时长突破1分钟。这是继文本模型ChatGPT和图片模型Dal…

三整数排序问题的解题逻辑

【题目描述】 输入3个整数&#xff0c;从小到大排序后输出。 【样例输入】 20 7 33 【样例输出】 7 20 33 【解析】 本题解法大概有3种&#xff1a; 1、穷举条件法。 此方法先判断a、b、c大小的所有可能&#xff0c;再根据各种可能性输出不同的排序。 思路是先判断a、…