注:这篇文章是本蒟蒻刚学习了BST后写的学习总结,也是我的第一篇blog,请各位大佬多多指教。
目录
在学习BST之前,我们首先要明确为什么要用BST。
在遇到求第k小的问题的时候,最简单的方法是排序后输出第k个,但是这样的时间复杂度是的。还有一种方法是用二分,但在无序的情况下时间复杂度仍然是。
这就有了BST。
什么是BST?
BST全名为Binary Search Tree,中文叫二叉搜索树。支持在线插入、搜索第K小(或大)、Rank、搜索前驱后继的操作,单次操作时间复杂度,空间复杂度。
BST的性质为:左子树<父节点<右子树。
如何存BST?
对于每个节点,我们需要存储它的左孩子、右孩子、重复次数、以当前节点为根的树的大小和当前节点的值,代码如下(默认root为1):
#define N int(1e6)+7
int n, size=1;//为插入第一个节点做准备
struct node{
int val, lc, rc, cnt, len;//值,左子树,右子树,重复个数和大小
}a[N];
BST的各种操作:
1、插入
给定一个值k,将其插入BST。
为了维护BST的性质,插入过程如下:
1、如果当前节点为空,新建节点并赋值。
2、如果k<当前节点,递归将k插入左子树。
3、如果k==当前节点,将当前节点的cnt++。
4、如果k>当前节点,递归将k插入右子树。
(蓝色箭头为路径)
重复时:
(蓝色箭头为路径)
代码如下:
void Insert(int k, int now){
a[now].len++;//当前树大小+1
if(a[now].lc==0 && a[now].rc==0 && a[now].cnt==0){//此节点为空,新建节点并赋值
a[now].val=k;
a[now].cnt=1;
}else if(k<a[now].val){
if(a[were].lc==0)//左子树为空,更新左孩子
a[were].lc=++size;
Insert(k, a[now].lc);//递归插入左子树
}else if(k>a[now].val){
if(a[were].rc==0)//右子树为空,更新右孩子
a[were].rc=++size;
Insert(k, a[now].rc);//递归插入右子树
}else{
a[now].cnt++;//重复了
}
}
2、搜索第k小
给定一个值k,输出第k小的值。
我们可以利用BST的性质递归找到第k小的值,过程如下:
1、k<当前节点的左子树的长len+当前节点的cnt,递归左子树。
2、k==当前节点的左子树的长len+当前节点的cnt,返回当前节点的val
3、k>当前节点的左子树的长len+当前节点的cnt,将k减去当前节点的左子树的长len+当前节点的cnt,再递归右子树(在右子树中找第k-当前节点的左子树的长len+当前节点的cnt小)。
(蓝色箭头为路径)
代码如下:
int kth(int x, int were){//在以were为根的树中找第x小
if((a[were].lc==0 && a[were].rc==0 && a[were].cnt==0) || x<=0)//节点为空或x不存在,无解
return -1;
if(x<a[a[were].lc].len+a[were].cnt)
return kth(x, a[were].lc);
else if(x>a[a[were].lc].len+a[were].cnt)
return kth(x-a[were].cnt-a[a[were].lc].len, a[were].rc);
else
return a[were].val;
}
3、Rank
给定一个值k,输出k是第几小的(<k的数的数量+1)。
过程如下:
1、如果k<当前节点的val,返回Rank(左子树)。
2、如果k==当前节点的val, 返回左子树的len+1。
3、如果k>当前节点的val,返回Rank(右子树)+左子树的长度+当前节点的cnt。
4、如果当前节点为空,返回-1。
(蓝色箭头为路径)
代码如下:
int Rank(int x, int were){
if(a[were].lc==0 && a[were].rc==0 && a[were].cnt==0){//为空
return -1;
}else if(x<a[were].val){
return Rank(x, a[were].lc);
}else if(x>a[were].val){
int tmp=Rank(x, a[were].rc);
if(tmp==-1)
return -1;
else
return a[were].cnt+a[a[were].lc].len+tmp;
}else
return a[a[were].lc].len+a[were].cnt;
}
4、pre
给定一个值k,输出k的前驱。
bool flag=1;//判断找没找到值
int pre(int x, int were){
int max1=-2147483647;
while(1){
if(a[were].val<x){//当前值小
flag=0;//找到了
max1=max(max1, a[were].val);//更新
if(a[were].rc==0){//到头了
return max1;
}else{
were=a[were].rc;//递归
}
}else{
if(a[were].lc==0){
return max1;//到头了
}else{
were=a[were].lc;//递归
}
}
}
}
5、后继
为前驱过程的反演。
完整模版代码:
#include <iostream>
using namespace std;
#define N int(1e6)+7
int n, size=1;
bool flag=1;
struct node{
int val, lc, rc, cnt, len;
bool empty(){
return (lc==0 && rc==0 && cnt==0);
}
}a[N];
void Insert(int k, int now){
a[now].len++;//当前树大小+1
if(a[now].lc==0 && a[now].rc==0 && a[now].cnt==0){//此节点为空,新建节点并赋值
a[now].val=k;
a[now].cnt=1;
}else if(k<a[now].val){
if(a[were].lc==0)//左子树为空,更新左孩子
a[were].lc=++size;
Insert(k, a[now].lc);//递归插入左子树
}else if(k>a[now].val){
if(a[were].rc==0)//右子树为空,更新右孩子
a[were].rc=++size;
Insert(k, a[now].rc);//递归插入右子树
}else{
a[now].cnt++;//重复了
}
}
int Rank(int x, int were){
if(a[were].lc==0 && a[were].rc==0 && a[were].cnt==0){//为空
return -1;
}else if(x<a[were].val){
return Rank(x, a[were].lc);
}else if(x>a[were].val){
int tmp=Rank(x, a[were].rc);
if(tmp==-1)
return -1;
else
return a[were].cnt+a[a[were].lc].len+tmp;
}else
return a[a[were].lc].len+a[were].cnt;
}
int kth(int x, int were){//在以were为根的树中找第x小
if((a[were].lc==0 && a[were].rc==0 && a[were].cnt==0) || x<=0)//节点为空或x不存在,无解
return -1;
if(x<a[a[were].lc].len+a[were].cnt)
return kth(x, a[were].lc);
else if(x>a[a[were].lc].len+a[were].cnt)
return kth(x-a[were].cnt-a[a[were].lc].len, a[were].rc);
else
return a[were].val;
}
int pre(int x, int were){
int max1=-2147483647;
while(1){
if(a[were].val<x){//当前值小
flag=0;//找到了
max1=max(max1, a[were].val);//更新
if(a[were].rc==0){//到头了
return max1;
}else{
were=a[were].rc;//递归
}
}else{
if(a[were].lc==0){
return max1;//到头了
}else{
were=a[were].lc;//递归
}
}
}
}
int nex(int x, int were){
int min1=2147483647;
while(1){
if(a[were].val<=x){
if(a[were].rc==0){
return min1;
}else{
were=a[were].rc;
}
}else{
flag=0;
min1=min(min1, a[were].val);
if(a[were].lc==0){
return min1;
}else{
were=a[were].lc;
}
}
}
}
int main(){
cin>>n;
while(n--){
flag=1;//重置判断值
char command;
int x;
cin>>command>>x;
if(command=='I'){
Insert(x, 1);
}else if(command=='R'){
int tmp=Rank(x, 1);
if(tmp==-1)
cout<<"No"<<endl;
else
cout<<tmp<<endl;
}else if(command=='K'){
int tmp=Find_k(x, 1);
if(tmp==-1)
cout<<"No"<<endl;
else
cout<<tmp<<endl;
}else if(command=='N'){
int tmp=nex(x, 1);
if(flag)
cout<<"No"<<endl;
else
cout<<tmp<<endl;
}else{
int tmp=pre(x, 1);
if(flag)
cout<<"No"<<endl;
else
cout<<tmp<<endl;
}
}
return 0;
}
其他:
显然,BST的结构与数据的随机性有很大关系,虽然BST大多数时候时间复杂度是,但当插入数据变得有序的时候,BST会退化成一条链,时间复杂度变成。这时我们就需要用到平衡树了。