2020-3-25 update: 原洛谷日报#2中代码部分出现一些问题,详情见此帖。并略微修改本文一些描述,使得语言更加自然。

2020-4-9 update:修了一些代码的锅,并且将文章同步发表于我的个人博客

同步发表于

洛谷博客

题目传送门

BST就是二叉搜索树,这里讲的是最普通的BST。


BST(Binary Search Tree),二叉搜索树,又叫二叉排序树

是一棵空树或具有以下几种性质的树:

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

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

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

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

看到第4条,我们会有一个疑问,在数据中遇到多个相等的数该怎么办呢,显然我们可以多加一个计数器,就是当前这个值出现了几遍。

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

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

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

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

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

至于为什么要有4.我们放到后面讲。

节点是这样的:

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

  

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


插入:

x是当前节点的下标,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!=)
add(tree[x].ls,v);//如果x有左子树,就去x的左子树
else{//如果不是,v就是x的左子树的权值
cont++;//cont是目前BST一共有几个节点
tree[cont].val=v;
tree[cont].siz=tree[cont].cnt=;
tree[x].ls=cont;
}
}
else{//右子树同理
if(tree[x].rs!=)
add(tree[x].rs,v);
else{
cont++;
tree[cont].val=v;
tree[cont].siz=tree[cont].cnt=;
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==)//如果没有左子树就直接返回找到的ans
return ans;
else//如果不是的话,去查左子树
return queryfr(tree[x].ls,val,ans);
}
else
{//如果当前值小于val,就说明我们找比val小的了
if (tree[x].rs==)//如果没有右孩子,就返回tree[x].val,因为走到这一步时,我们后找到的一定比先找到的大(参考第二条性质)
return (tree[x].val<val) ? tree[x].val : ans
//如果有右孩子,,我们还要找这个节点的右子树,因为万一右子树有比当前节点还大并且小于要找的val的话,ans需要更新
if (tree[x].cnt!=)//如果当前节数的个数不为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==)
return ans;
else
return queryne(tree[x].rs,val,ans);
}
else
{
if (tree[x].ls==)
return (tree[x].val>val)? tree[x].val : ans;
if (tree[x].cnt!=)
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==) return ;//没有排名
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和性质2,我们发现排名为n的数在BST上是第n靠左的数。或者说排名为n的数的节点在BST中,它的左子树的siz与它的各个祖先的左子树的siz相加恰好=n (这里相加是要减去重复部分)。

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

rk是要找的排名

int queryrk(int x,int rk)
{
if(x==) 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的,样例的正确输出应该是3 3 1 5


然后是完整版代码

Code:

#include<iostream>
#include<cstdio>
using namespace std;
const int INF=0x7fffffff;
int cont;
struct node{
int val,ls,rs,cnt,siz;
}tree[];
int n,opt,xx;
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!=)
add(tree[x].ls,v);
else{
cont++;
tree[cont].val=v;
tree[cont].siz=tree[cont].cnt=;
tree[x].ls=cont;
}
}
else{
if(tree[x].rs!=)
add(tree[x].rs,v);
else{
cont++;
tree[cont].val=v;
tree[cont].siz=tree[cont].cnt=;
tree[x].rs=cont;
}
}
}
int queryfr(int x, int val, int ans) {
if (tree[x].val>=val)
{
if (tree[x].ls==)
return ans;
else
return queryfr(tree[x].ls,val,ans);
}
else
{
if (tree[x].rs==)
return (tree[x].val<val) ? tree[x].val : ans;
if (tree[x].cnt!=)
return queryfr(tree[x].rs,val,tree[x].val);
else
return queryfr(tree[x].rs,val,ans);
}
}
int queryne(int x, int val, int ans) {
if (tree[x].val<=val)
{
if (tree[x].rs==)
return ans;
else
return queryne(tree[x].rs,val,ans);
}
else
{
if (tree[x].ls==)
return (tree[x].val>val)? tree[x].val : ans;
if (tree[x].cnt!=)
return queryne(tree[x].ls,val,tree[x].val);
else
return queryne(tree[x].ls,val,ans);
}
}
int queryrk(int x,int rk)
{
if(x==) 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==) return ;
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;
}
inline int read()
{
int r=,w=;
char ch=getchar();
while(ch<''||ch>''){
if(ch=='-') w=-;
ch=getchar();
}
while(ch>=''&&ch<=''){
r=(r<<)+(r<<)+(ch^);
ch=getchar();
}
return r*w;
}
int main()
{
n=read();
while(n--){
opt=read();xx=read();
if(opt==) printf("%d\n",queryval(,xx)+);
else if(opt==) printf("%d\n",queryrk(,xx));
else if(opt==) printf("%d\n",queryfr(,xx,-INF));
else if(opt==) printf("%d\n",queryne(,xx,INF));
else{
if(cont==){
cont++;
tree[cont].cnt=tree[cont].siz=;
tree[cont].val=xx;
}
else add(,xx);
}
}
return ;
}

浅析BST二叉搜索树的更多相关文章

  1. 数据结构中很常见的各种树(BST二叉搜索树、AVL平衡二叉树、RBT红黑树、B-树、B+树、B*树)

    数据结构中常见的树(BST二叉搜索树.AVL平衡二叉树.RBT红黑树.B-树.B+树.B*树) 二叉排序树.平衡树.红黑树 红黑树----第四篇:一步一图一代码,一定要让你真正彻底明白红黑树 --- ...

  2. [LeetCode] Serialize and Deserialize BST 二叉搜索树的序列化和去序列化

    Serialization is the process of converting a data structure or object into a sequence of bits so tha ...

  3. bst 二叉搜索树简单实现

    //数组实现二叉树: // 1.下标为零的元素为根节点,没有父节点 // 2.节点i的左儿子是2*i+1:右儿子2*i+2:父节点(i-1)/2: // 3.下标i为奇数则该节点有有兄弟,否则又左兄弟 ...

  4. 数据结构中常见的树(BST二叉搜索树、AVL平衡二叉树、RBT红黑树、B-树、B+树、B*树)

    树 即二叉搜索树: 1.所有非叶子结点至多拥有两个儿子(Left和Right): 2.所有结点存储一个关键字: 非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树: 如: BST树 ...

  5. [LeetCode] Minimum Absolute Difference in BST 二叉搜索树的最小绝对差

    Given a binary search tree with non-negative values, find the minimum absolute difference between va ...

  6. 530 Minimum Absolute Difference in BST 二叉搜索树的最小绝对差

    给定一个所有节点为非负值的二叉搜索树,求树中任意两节点的差的绝对值的最小值.示例 :输入:   1    \     3    /   2输出:1解释:最小绝对差为1,其中 2 和 1 的差的绝对值为 ...

  7. LeetCode #938. Range Sum of BST 二叉搜索树的范围和

    https://leetcode-cn.com/problems/range-sum-of-bst/ 二叉树中序遍历 二叉搜索树性质:一个节点大于所有其左子树的节点,小于其所有右子树的节点 /** * ...

  8. Leetcode938. Range Sum of BST二叉搜索树的范围和

    给定二叉搜索树的根结点 root,返回 L 和 R(含)之间的所有结点的值的和. 二叉搜索树保证具有唯一的值. 示例 1: 输入:root = [10,5,15,3,7,null,18], L = 7 ...

  9. 标准BST二叉搜索树写法

    本人最近被各种数据结构的实验折磨的不要不要的,特别是代码部分,对数据结构有严格的要求,比如写个BST要分成两个类,一个节点类,要给树类,关键是所以操作都要用函数完成,也就是在树类中不能直接操作节点,需 ...

随机推荐

  1. 洛谷 P3627 [APIO2009]抢掠计划 Tarjan缩点+Spfa求最长路

    题目地址:https://www.luogu.com.cn/problem/P3627 第一次寒假训练的结测题,思路本身不难,但对于我这个码力蒟蒻来说实现难度不小-考试时肛了将近两个半小时才刚肛出来. ...

  2. Redis做为缓存的几个问题

    缓存理流程: 前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果. 1.缓存雪崩 解决方案3:如果缓存数据库是分布 ...

  3. Django---进阶10

    目录 forms组件源码 cookie与session Cookie操作 session操作 CBV如何添加装饰器 作业 forms组件源码 """ 切入点: form_ ...

  4. java NIO 原理解析之学习笔记

    关键抽象 1.Buffer缓冲区 NIO数据传递模型,是一个连续的内存区域.所有数据传递均通过buffer类处理:NIO提供了字符串.整形.字节.堆等多种缓冲区. 2.Channel(通道) NIO把 ...

  5. Canonical通过Flutter启用Linux桌面应用程序支持

    子标题:Ubuntu团队为所有Linux发行版上的Flutter应用程序制作了一个新的基于GTK +的主机. 此文翻译自:https://medium.com/flutter/announcing-f ...

  6. Python-01矩阵、数组和列表等的总结

    python的基础知识总结 使用到了numpy库,所以第一步需要 import numpy as np 1.创建矩阵 1.1一般矩阵的创建 创建一个二维的矩阵,并使用ndim.shape.size分别 ...

  7. 并发编程之synchronized锁(一)

    一.设计同步器的意义 多线程编程中,有可能会出现多个线程同时访问同一个共享.可变资源的情况,这个资源我们称之其为临界资源:这种资源可能是:对象.变量.文件等. 共享:资源可以由多个线程同时访问 可变: ...

  8. bzoj4512[Usaco2016 Jan] Build Gates

    bzoj4512[Usaco2016 Jan] Build Gates 题意: 某人从农场的(0,0)出发,沿边界到处乱走,走过的地方会留下栅栏,等走完后问要在多少个栅栏上开门才能使整个农场连通,最多 ...

  9. 数据结构中有关顺序表的问题:为何判断插入位置是否合法时if语句中用length+1,而移动元素的for语句中只用length?

    bool ListInsert(SqList &L,int i, ElemType e){ if(i<||i>L.length+) //判断i的范围是否有效 return fals ...

  10. (2)简单理解和使用webpack-dev-server

    webpack-dev-server能做什么? 每次打包都得像之前一样使用webapck 入口文件 -o 出口文件,每次修改都得打包一次过于麻烦,可以使用webpack-dev-server实现自动打 ...