二叉搜索树

二叉搜索树

前置芝士

中序遍历,二叉树

(本蒟蒻即将要学平衡树,所以先来学搜索树总结一下)

定义
  1. 空树是二叉搜索树。
  2. 若二叉搜索树的左子树不为空,则其左子树上所有点的附加权值均小于其根节点的值。
  3. 若二叉搜索树的右子树不为空,则其右子树上所有点的附加权值均大于其根节点的值。
  4. 二叉搜索树的左右子树均为二叉搜索树。

(人话就是:右儿子大于根节点大于左儿子)

作用

如果你想找第n大的数,你明显需要冒泡排序或快速排序,最坏复杂度分别为: O ( n 2 / 2 ) , O ( n log ⁡ n ) O(n^2/2),O(n\log n) O(n2/2),O(nlogn)

但如果你用上图这棵树,根据他(右儿子>自己>左儿子)的特性,可以在期望复杂度 O ( log ⁡ n ) O(\log n) O(logn) 的时间内查出一个数据

同时修改也变得简单, O ( log ⁡ n ) O(\log n) O(logn) 的时间查找位置, O ( 1 ) O(1) O(1) 的时间连边

性质

对一个二叉树进行中序遍历,如果是单调递增的,则可以说明这个树是二叉搜索树

操作及模板

(在接下来的代码中, n n n 为节点个数, h h h 为树的高度, s i z [ x ] siz[x] siz[x] x x x 及子树的大小, v a l [ x ] val[x] val[x] x x x 点数值, c n t [ x ] cnt[x] cnt[x] x x x 节点的出现次数, l s [ x ] , r s [ x ] ls[x],rs[x] ls[x],rs[x] x x x 的左儿子和右儿子)

遍历

时间复杂度: O ( n ) O(n) O(n)

void print(int rt){
    // 遍历以 o 为根节点的二叉搜索树
    if(!rt) return ;// 遇到空树,返回
    print(ls[rt]);  // 递归遍历左子树
    for(int i=1;i<=cnt[rt];i++) printf("%d\n",val[rt]);  // 输出根节点信息
    print(rs[rt]);  // 递归遍历右子树
    
}
查找最大/最小值

由性质可得,最小值为左链顶点,最大值为右链顶点。时间复杂度: O ( h ) O(h) O(h)

int findmin(int rt){
	if(!ls[rt]) return rt;
	return findmin(ls[rt]);//一直向左儿子跳
}

int findmax(int rt){
    if(!rs[rt]) return rt;
    return findmax(rs[rt]);//一直向右儿子跳
}

findmax和findmin函数返回的是最大/小值的编号,如果需要值,用 v a l [ r t ] val[rt] val[rt] 来获得最大/小值

插入

时间复杂度: O ( h ) O(h) O(h)

void insert(int &rt,int v){
    if(!rt){				//若o为空,直接返回一个值为v的新节点。
        val[rt=++sum]=v;
        cnt[rt]=siz[rt]=1;
        return ;
    }
    siz[rt]++;
    if(val[rt]==v){
        cnt[rt]++;
        return ;
    }
    if(val[rt]>v) insert(ls[rt],v);//若o的权值大于v,在o的左子树中插入权值为v的节点。
    if(val[rt]<v) insert(rs[rt],v);//若o的权值小于v,在o的右子树中插入权值为v的节点。
}
删除

时间复杂度: O ( h ) O(h) O(h)

int deletemin(int &rt){	
    if(!ls[rt]){//若没有左儿子
        int u=rt;
        rt=rs[rt];//用右儿子替换父节点
        return u;
    } else{
        int u=deletemin(ls[rt]);
        siz[rt]-=cnt[u];//删掉了u,还需要将它的siz删掉
        return u;
    }
}
//返回删除的编号
void del(int &rt,int v){//在以rt为根节点的二叉搜索树中删除一个值为v的节点
    siz[rt]--;//不管怎么着,都是删它的子树
    if(val[rt]==v){
        if(cnt[rt]>1){//若附加cnt大于1
            cnt[rt]--;//减去即可
            return ;
        }
        if(ls[rt]&&rs[rt]) rt=deletemin(rs[rt]);//一般是用它左子树的最大值或右子树的最小值代替它,然后将它删除。
        //这里以右子树最小值代替为例
        else
        	rt=ls[rt]+rs[rt];//只有一个儿子的节点,返回这个儿子
        return ;
    }
    if(val[rt]>v) del(ls[rt],v);
    if(val[rt]<v) del(rs[rt],v);
}
求元素的排名

维护每个根节点的子树大小 siz。查找一个元素的排名,首先从根节点跳到这个元素,若向右跳,答案加上左儿子节点个数加当前节点重复的数个数,最后答案加上终点的左儿子子树大小 +1。

int queryrhk(int rt,int v){
    if(val[rt]==v) return siz[ls[rt]]+1;
    if(val[rt]>v) return queryrnk(ls[rt],v);
    if(val[rt]<v) return queryrnk(rs[rt],v)+siz[ls[rt]]+cnt[rt];
}
查找排名为k的元素

在一棵子树中,根节点的排名取决于其左子树的大小。

若其左子树的大小大于等于 k k k,则该元素在左子树中;

若其左子树的大小在区间 [ k − c n t , k − 1 ] [k-cnt,k-1] [kcnt,k1]( c n t cnt cnt为当前结点的值的出现次数)中,则该元素为子树的根节点;

若其左子树的大小小于 k − c n t k-cnt kcnt ,则该元素在右子树中。

时间复杂度: O ( h ) O(h) O(h)

int querykth(int rt,int k){
    if(siz[ls[rt]]>=k) return querykth(ls[rt],k);
    if(siz[ls[rt]]<k-cnt[rt]) return querykth(rs[rt],k-siz[ls[rt]]-cnt[rt]);
    // 如要找排名为 k 的元素所对应的结点,直接 return rt 即可
}
找前驱和后缀

时间复杂度: O ( h ) O(h) O(h)

//找前驱
int getpre(int rt,int val,int ans){
    if(val[rt]>=val){
        if(!ls[rt]) return ans;
        else getpre(ls[rt],val,ans);
    }
    else{
        if(!rs[rt]){
            if(val[rt]<val) return val[rt];
            else return ans;
        }
        if(cnt[rt]) return getpre(rs[rt],val,val[rt]);
        else return getpre(rs[rt],val,ans);
    }
}
//找后缀
int getnext(int rt,int val,int ans){
    if(val[rt]<=val){
        if(!rs[rt]) return ans;
        else getnext(rs[rt],val,ans);
    }
    else{
        if(!ls[rt]){
            if(val[rt]>val) return val[rt];
            else return ans;
        }
        if(cnt[rt]) return getnext(ls[rt],val,val[rt]);
        else return getnext(ls[rt],val,ans);
    }
}
  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值