• 【转】二叉搜索树


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

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

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

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

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

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

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

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

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

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

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

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

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

    节点是这样的:

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

    其中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!=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+1;
        //如果当前节点值=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的,样例的正确输出应该是3 3 1 5


    然后是完整版代码

    Code:

    #include<bits/stdc++.h>
    using namespace std;
    const int INF=0x7fffffff;
    int cont;
    struct t{
        int val,ls,rs,cnt,siz;
    }tree[500010];
    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!=0)
              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!=0)
              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==0)
                return ans;
            else
                return queryfr(tree[x].ls,val,ans);
        }
        else
        {
            if (tree[x].rs==0)
                return tree[x].val;
                
            else
                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;
                
            else
                return queryne(tree[x].ls,val,tree[x].val);
        }
    }
    int queryrk(int x,int rk)
    {
        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+1; 
        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()
    {
        cin>>n;
        for(int i=1;i<=n;i++){
            cin>>opt>>xx;
            if(opt==1) printf("%d
    ",queryval(1,xx)+1);
            else if(opt==2) 
     {
                 if(xx>tree[1].siz)
                 printf("%d
    ",INF);
                 else printf("%d
    ",queryrk(1,xx));
             }
            else if(opt==3) printf("%d
    ",queryfr(1,xx,-INF));
            else if(opt==4) printf("%d
    ",queryne(1,xx,INF));
            else{
                if(cont==0){
                    cont++;
                    tree[cont].cnt=tree[cont].siz=1;
                    tree[cont].val=xx;
                }
                else add(1,xx);
            }
        }
        return 0;
    }
  • 相关阅读:
    maven项目,去除jar包中的不想要的依赖关系
    Eclipse:An internal error occurred during: "Build Project". GC overhead limit exceeded
    如何用Maven创建web项目(具体步骤)
    让Jackson JSON生成的数据包含的中文以unicode方式编码
    MySQL存储过程详解 mysql 存储过程
    MySQL SQL Injection(注入)
    撤销Excel工作表保护密码(考勤机报表)
    youtube-dl下载视频
    LSI9240 8i在dos下刷IT直通模式
    制作DOS引导U盘(支持扩展任何dos下的程序)
  • 原文地址:https://www.cnblogs.com/lau1997/p/12627991.html
Copyright © 2020-2023  润新知