五一培训 清北学堂 DAY2

今天还是冯哲老师的讲授~~

今日内容:简单数据结构(没看出来简单qaq)
1.搜索二叉树

前置技能

一道入门题
在初学OI的时候,总会遇到这么一道题。
给出N次操作,每次加入一个数,或者询问当前所有数的最大值。
维护一个最大值Max,每次加入和最大值进行比较。
时间复杂度O(N).
EX:入门题
给出N次操作,每次加入一个数,删除一个之前加入过的数,或者询问当前所有数的最大值。
N 100000.
引入二叉搜索树(BST):

特征:
二叉搜索树的key值是决定树形态的标准。
每个点的左子树中,节点的key值都小于这个点。
每个点的右子树中,节点的key值都大于这个点。

一个好例子:

我们可以发现:每个结点的左儿子一定小于该结点,右儿子一定大于该结点!

进一步可以推出:每一层从左往右都是按从大到小的顺序拍好的(虽然没啥用)

示例:

 

基本操作:

查询最大/最小值
注意到BST左边的值都比右边小,所以如果一个点有左儿子,就往左儿子走,否则这个点就是最小值啦。 

代码(最小值):

int Findmin()
{
    int x = root;     //x记录当前结点,当然从根节点开始找 
    while (ls[x]) x=ls[x];    //如果Is[x]不为0,说明有左儿子,让x等于它的左儿子 
    return key[x];            //返回最小权值 
}

插入一个值
现在我们要插入一个权值为x的节点。
为了方便,我们插入的方式要能不改变之前整棵树的形态
首先找到根,比较一下key[root]x,如果key[root] < x,节点应该插在root右侧,否则再左侧。
看看root有没有右儿子,如果没有,那么直接把root的右儿子赋成x就完事了。
否则,为了不改变树的形态,我们要去右儿子所在的子树里继续这一操作,直到可以插入为止。 

删除一个值
现在我们要删除一个权值为x的点
之前增加一个点我们能够不改变之前的形态。
定位一个节点
要删掉一个权值,首先要知道这个点在哪。
root开始,像是插入一样找权值为x的点在哪。

int Find(int x)               //在搜索二叉堆里定位一个数 
{
    int now=root;             //从根节点开始找 
    while(key[now]!=x)        //如果不相等就一直往下找 
    if (key[now]<x) now=rs[now]; //如果大于当前结点就直接从右边找,二分思想减少复杂度 
    else now=ls[now];            //否则从左边找 
    return now;               //如果没找到会返回空值 
}

方案一
直接把这个点赋成一种空的状态.
但是这样的话查询起来不太方便。
所以还是稍微麻烦一点吧。

方案二
对这个节点x的儿子情况进行考虑。
如果x没有儿子,删掉x就行了。
如果x有一个儿子,直接把x的儿子接到x的父亲下面就行了。 
x如果是x父亲的左儿子,那么x的儿子直接接在x父亲的左下面就好了,否则接在右下面
如果x有两个儿子,这种情况就比较麻烦了。
定义x的后继y,是x右子树中所有点里,权值最小的点。
找这个点可以x先走一次右儿子,再不停走左儿子。(因为y肯定在x右子树中的最左侧)
如果yx的右儿子,那么直接把y的左儿子赋成原来x的左儿子,然后用y代替x的位置。 
原理:

就算y是x右儿子中最小的一个,但一定比x的任何一个左儿子都大,所以换到x的位置其他的点不用动,因为左边的点都比他小,右边的点都比他大,而x小于任何一个右子树的点,所以换到右子树里就是最小的那个,也就是之前y所在的位置,又因为y没有左儿子,但可能有右儿子(y的右儿子代替的是y他本来在的位置),所以x换过去就成了上面只有一个孩子的情况,这样删除就方便啦!(不得不说这也太强了吧!)

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<ctime>
#include<set>
#include<vector>
#include<map>
#include<queue>

#define N 300005
#define M 8000005

#define mid ((l+r)>>1)

#define mk make_pair
#define pb push_back
#define fi first
#define se second

using namespace std;

int i,j,m,n,p,k,ls[N],rs[N],sum[N],size[N],a[N],root,tot,fa[N]; 

void ins(int x)//插入一个权值为x的数字
{
        sum[++tot]=x;   //用tot来表示二叉树里的节点个数,sum数组存第tot个结点的权值 
        size[tot]=1;    //它能遍历到的点只有它自己 
        if (!root) root=tot;//如果一开始一个节点都没有,就要找一个节点当根
        else
        {
                int now=root; //从根开始
                for (;;)
                {
                        ++size[now]; 
                        if (sum[now]>sum[tot]) //判断和当前节点的大小,如果tot小于当前结点now,说明应该插在now左边 
                        {
                                if (!ls[now])  //如果now没左儿子,直接插入 
                                {
                                    ls[now]=tot;fa[tot]=now;   //标记now的左儿子为tot,tot的父亲为now 
                                    break;     //跳出循环,进入下次插入 
                                }
                                else now=ls[now];//如果now有左儿子,那么继续从now的左儿子Is[now]继续往下找 
                        } 
                        else
                        {
                                if (!rs[now])  //与上面同理 
                                {
                                    rs[now]=tot; fa[tot]=now;
                                    break;
                                }
                                else now=rs[now];
                        }
                }
        } 
}

int FindMin()             //找最小值,肯定在左子树里 
{
        int now=root;     //从根节点开始找 
        while (ls[now]) now=ls[now]; //一直往下找,直到没有左孩子 
        return sum[now];             //返回最小值 
}

void build1()//暴力build的方法,每次插入一个值 
{
    for (i=1;i<=n;++i) ins(a[i]); 
}

int Divide(int l,int r)
{
         if (l>r) return 0;
        ls[mid]=Divide(l,mid-1);
        rs[mid]=Divide(mid+1,r);
        fa[ls[mid]]=fa[rs[mid]]=mid; fa[0]=0;
        sum[mid]=a[mid];
        size[mid]=size[ls[mid]]+size[rs[mid]]+1;
        return mid;
}

void build2()//精巧的构造,使得树高是log N的
{
        sort(a+1,a+n+1);
        root=Divide(1,n);
        tot=n;
}

int Find(int x)//查询值为x的数的节点编号 
{
    int now=root;
    while (sum[now]!=x&&now)
        if (sum[now]<x) now=rs[now]; else now=ls[now];
    return now; 
}

int Findkth(int now,int k)
{
        if (size[rs[now]]>=k) return Findkth(rs[now],k); //因为右子树的数都大于左子树中的数,所以如果当前点右子树的size值大于k,那么第k大的值一定在右子树里,递归右子树继续往下找 
        else if (size[rs[now]]+1==k) return sum[now]; //因为右子树一共有size[rs[now]]个,若size[rs[now]]+1==k,说明第k大的值就是当前结点,直接返回,因为左边的数都比当前结点的数小 
        else Findkth(ls[now],k-size[rs[now]]-1);//注意到递归下去之后右侧的部分都比它要大,第k大的数只能在左子树里了,此时我们已经找了前size[rs[now]]+1位大数了,所以要用k减去它 
}

void del(int x)       //删除一个值为x的点
{
        int id=Find(x),t=fa[id];//找到这个点的编号id,t位id的父亲 
        if (!ls[id]&&!rs[id])   //如果这个结点没有儿子 
        {
                if (ls[t]==id) ls[t]=0;   //是左儿子就将ls[t]置空 
                else rs[t]=0; //否则就将rs[t]置空 
                for (i=id;i;i=fa[i]) size[i]--;  //将结点id删去后,他及他的祖先能遍历到的点都减少了一,所以要减去一 
        }
        else
        if (!ls[id]||!rs[id])   //只有一个儿子 
        {
                int child=ls[id]+rs[id];//找存在的儿子的编号,因为其中肯定有一个儿子的编号为0表示没有该儿子,加起来就是存在的那个儿子的编号 
                if (ls[t]==id) ls[t]=child; //是左儿子就将ls[t]接上id的孩子child
                else rs[t]=child;        //否则就将rs[t]接上id的孩子child       
                fa[child]=t;             //标记child的新父亲 
                for (i=id;i;i=fa[i]) size[i]--;      //将结点id删去后,他及他的祖先能遍历到的点都减少了一,所以要减去一  
        }
        else
        {
                int y=rs[id]; while (ls[y]) y=ls[y]; //找后继 
                if (rs[id]==y) 
                {
                        if (ls[t]==id) ls[t]=y; else rs[t]=y;
                        fa[y]=t;
                        ls[y]=ls[id];
                        fa[ls[id]]=y;
                        for (i=id;i;i=fa[i]) size[i]--;
                        size[y]=size[ls[y]]+size[rs[y]];//y的子树大小需要更新 
                }
                else //最复杂的情况         
                {
                        for (i=fa[y];i;i=fa[i]) size[i]--;//注意到变换完之后y到root路径上每个点的size都减少了1
                        int tt=fa[y]; //先把y提出来 
                        if (ls[tt]==y)
                        {
                                ls[tt]=rs[y];
                                fa[rs[y]]=tt;
                        }                    
                        else
                        {
                                rs[tt]=rs[y];
                                fa[rs[y]]=tt;
                        }    
                        //再来提出x          
                        if (ls[t]==x)
                        {
                            ls[t]=y;
                            fa[y]=t;
                            ls[y]=ls[id];
                            rs[y]=rs[id];
                        }
                        else
                        {
                            rs[t]=y;
                            fa[y]=t;
                            ls[y]=ls[id];
                            rs[y]=rs[id];
                        }
                        size[y]=size[ls[y]]+size[rs[y]]+1;//更新一下size 
                }
        }
}

int main()
{
        scanf("%d",&n);
        for (i=1;i<=n;++i) scanf("%d",&a[i]);
        build1();
        printf("%d\n",Findkth(root,2));//查询第k大的权值是什么,这里k==2 
        del(4);
        printf("%d\n",Findkth(root,2)); 
return 0; }

又是未完待续qaq~

猜你喜欢

转载自www.cnblogs.com/xcg123/p/10792867.html