BST学习总结

 注:这篇文章是本蒟蒻刚学习了BST后写的学习总结,也是我的第一篇blog,请各位大佬多多指教。

目录

在学习BST之前,我们首先要明确为什么要用BST。

什么是BST?

如何存BST?

BST的各种操作:

1、插入

2、搜索第k小

3、Rank

4、pre

5、后继

完整模版代码:

其他:


在学习BST之前,我们首先要明确为什么要用BST。

在遇到求第k小的问题的时候,最简单的方法是排序后输出第k个,但是这样的时间复杂度是\Theta (n*log_n)的。还有一种方法是用二分,但在无序的情况下时间复杂度仍然是\Theta (n*log_n)

这就有了BST。

什么是BST?

BST全名为Binary Search Tree,中文叫二叉搜索树。支持在线插入、搜索第K小(或大)、Rank、搜索前驱后继的操作,单次操作时间复杂度\Theta (log_n),空间复杂度\Theta (n)

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大多数时候时间复杂度是\Theta (n*log_n),但当插入数据变得有序的时候,BST会退化成一条链,时间复杂度变成\Theta (n^2)。这时我们就需要用到平衡树了。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值