二叉树上懵逼果,二叉树下你和我

好久没更新了,原来是在憋大招,一上午搞懂二叉树的所有题型。

树的模板

先来讲字数和这道经典的树题

子树和 - 洛谷

建树

我先来讲讲怎么建树。首先我们知道,是一种 非线性的数据结构,它是由 n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因 为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。并且两个点只有一条边,所以说n个节点的树有n-1条边;而关于建树,可以使用邻接表存放树的上下子节点,也可以使用vector<int>t[N]存放树,我们先来展示一下如何存放吧。

我比较建议使用vector容易懂,而且也好写。

vector存放

    const int N = 2e5+10;
    vector<int> t[N];
    int n;
    cin>>n;
    for(int i=1;i<=n-1;i++){//因为是n-1条边,一般输入n-1
        int x,y;
        cin>>x>>y;
        t[x].push_back(y);
        t[y].push_back(x);
    }

邻接表存放

const int N = 2e5+10;
int e[N],ne[N],h[N],idx;
int n,m;
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int main{
cin>>n>>m;
    memset(h,-1,sizeof h);
    for(int i=0;i<m;i++){
        int a,b;
        cin>>a>>b;
        add(a,b);
    } 
}

假如说要存以下的内容(我们默认后面的为父节点,因为大部分的题目是这样的)

5
1 2 
5 1 
4 1 
3 2

看,是不是很显然易懂。存放数组,我们一般是存的无向树,所以要把双方塞到对方的子节点,就相当于可以来回了,但是我们遍历的时候不能来回,要怎么办呢。

树的遍历

来看一下vector的遍历

void dfs(int x,int fa){
    for(int i=0;i<t[x].size();i++){
        int ne=t[x][i];
        if(ne==fa)continue;//这一步就是来看是不是遍历到了自己的父节点
        dfs(ne,x);
        //你的操作
        //
    }
}

咱们拿数字1来看

t[1]里面有2,4,5这三个点,如何不便利到自己的父节点,简单,拿一个函数递归存放自己的父亲,像上面的代码中的fa就是x的父节点,但是如果是对于2来说,他没有父亲就不用管了。

邻接表的遍历

void dfs(int u){//u是这个节点
    st[u]=true;//开了一个bool数组,遍历之前看看是不是曾经遍历过
    for(int i=h[u];i!=-1;i=ne[i]){
        int j=e[i];//j就是他的子节点
        if(!st[j])dfs(j);
    }
}

由于邻接表的特殊结构,它的遍历递归就不需要去存放父亲

现在你已经学会了树的建立和遍历啦,你已经可以做上面的问题了。

子树和这个题目,首先分析好谁是父节点谁是子节点,虽然不影响建树,但是还是为了使下面容易懂些还是先画图

他说下面的数字f(i)是i+1的的父节点,所以画图

那么咱们先建树,然后看看问题要干啥

    int n;
    cin>>n;
    for(int i=2;i<=n;i++){
        int x;cin>>x;
        t[x].push_back(i);
        t[i].push_back(x);
    }

问题要求我们i=1~n的f(i)的子节点的个数,看了图显而易见,1有5个子节点,2有4个(当然包括自己)

那么我们先初始化把每一个点的子节点先设为1,然后遍历,最终代码就是

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
vector<int> t[N];
int ans[N];
void dfs(int x,int fa){
    for(int i=0;i<t[x].size();i++){
        int ne=t[x][i];
        if(ne==fa)continue;
        dfs(ne,x);
        ans[x]+=ans[ne];
    }
}
signed main(){
    int n;
    cin>>n;
    for(int i=2;i<=n;i++){
        int x;cin>>x;
        t[x].push_back(i);
        t[i].push_back(x);
    }
    for(int i=1;i<=n;i++){
    ans[i]=1;
    }
    dfs(1,0);
    for(int i=1;i<=n;i++){
       cout<<ans[i]<<endl;
    }
}

好了,你已经做出来了一道题目了,恭喜你,下面我们要开始讲二叉树啦。

所谓二叉树

就是一个头节点最多有两个子节点的树,感觉很宽泛是吧,为啥这样说呢,因为二叉树的种类非常多,包括(完全二叉树、满二叉树、平衡二叉树...)等等,细讲估计讲不完,还是先看别的文章深入,我们这篇文章主要是带0基础的小白(acmer)入门的。

那好,网上和书上大部分的二叉树是那样带->的有指针的,而我今天要讲的,大不相同,虽然是用结构体数组来存的,但是我们的更通俗易懂。

还是老样子,先是二叉树的建树

因为我们下面是对字符串样子的前序遍历进行建树我们就要先搞懂二叉树的前中后序遍历

但是看这个你估计还是不懂,等会到了遍历就明白了。

const int N=1e4+10;
struct node{
    char data;
    int l,r;
}tre[N];
string s;
int tidx,idx;
int build(){
    if(s[idx]==','){//相当于遇到空节点的判断,遇到空节点就idx++,并且回溯
       idx++;
       return 0;
    }
    tre[++tidx].data=s[idx++];
    int now=tidx;//每个节点的内容
    tre[now].l=build();
    tre[now].r=build();
    return now;
}

建树就是这样建的

二叉树的遍历,看了上图,我们知道了二叉树的遍历的类型,其实还有二叉树的层序遍历这一说。

就是这样

额,看起来像一坨,呃呃,不说了。

关于遍历的话看一下模板就明白了

void predfs(int c){//前序
    if(c==0)return;
    cout<<tre[c].data;
    predfs(tre[c].l,h+1);
    predfs(tre[c].r,h+1);
}
void dfs1(int c){//中序
    if(c==0)return;
    dfs1(tre[c].l);
    cout<<tre[c].data;
    dfs1(tre[c].r);
}
void dfs2(int c){//后序
    if(c==0)return;
    dfs2(tre[c].l);
    dfs2(tre[c].r);
    cout<<tre[c].data;
}
void bfs(int x){//层序
    queue<int> q;
    if(x)q.push(x);
    while(q.size()){
        x=q.front();
        if(t[x].l)q.push(t[x].l);
        if(t[x].r)q.push(t[x].r);
        cout<<t[x].data;
        q.pop();
    }
}

你看关于前序的话,我们是先输出这个节点的数,再去递归左节点,然后递归右节点,如果你的递归思想或者说头脑比较好的话应该就记住了。

而这个层序遍历的话,就是bfs(),哎呀,我又忘了,学树之前得先去学习搜索,那么先推荐我的好朋友的Java的dfs的推文吧,bfs的我之前发过。

【杀红眼了】dfs刷题 (洛谷P1683 入门,P1596Lake Counting S,P1025数的划分,洛谷P1088火星人,Acwing1114. 棋盘问题)-CSDN博客

每日好题:acwing:(走迷宫bfs的运用)好久没更新啦-CSDN博客

来吧,讲讲,二叉树的例题不光是遍历输出,还包括二叉树的子节点的计算,二叉树的深度计算,二叉树的还原

先看前两个

二叉树的中序后序遍历
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
struct node{
    char data;
    int l,r;
}tre[N];
string s;
int tidx,idx,res,high;//树的下标和字符串的下标
int build(){
    if(s[idx]==','){
       idx++;
       return 0;
    }
    tre[++tidx].data=s[idx++];
    int now=tidx;
    tre[now].l=build();
    tre[now].r=build();
    return now;
}
void predfs(int c,int h){
    if(c==0)return;
    // cout<<tre[c].data;
    predfs(tre[c].l,h+1);
    predfs(tre[c].r,h+1);
    if(tre[c].l==0&&tre[c].r==0)res++;
    high=max(h,high);
}
signed main(){
    while(cin>>s){
        idx=0,tidx=0;
        res=0,high=0;
        int root=build();
        predfs(root,1);
        dfs1(root);puts("");
        dfs2(root);puts("");
        cout<<res<<endl;//节点个数
        cout<<high<<endl;//树深
    }
}

这里我是先进行前序遍历一次,判断节点的子节点是否为空,为空的话就res++,就算为一个子节点,深度嘛,还是老样子,在函数里面加入一个变量专门用于递归,并且定义全局的high求h的最大值。

说了这么多,你没有发现每次都是把前序遍历的空节点啥的给你,为啥不只给你一个前序或者中序啊,因为要是只是前序,你不能确定唯一的排序方法,所以出题的时候就会在给你前序的基础上给你确定二叉树的信息,比如说下面这个例题,给你二叉树的前序和中序遍历输出后序和层序的遍历还有树深。

这里我要给出一个规律你没有发现前序的第一个就是树的头结点,而中序中树的头结点的左边为二叉树的左枝子,头结点的右边为树的右枝子。

看图

通过很难的想,其实也就是递归啦,得到这样一个公式,看代码吧。

int build(int prel,int prer,int midl,int midr){//pre 是 前序的字符串  mid 是中序的字符串
    int asc=pre[prel];
    int pos=w[asc];
    if(midl<pos)t[asc].l=build(prel+1,pos-midl+prel,midl,pos-1);//看就是相当于线段树的操作,只不过更麻烦啦,一个区域里面细分,直到确定 节点
    if(midr>pos)t[asc].r=build(pos-midl+prel+1,prer,pos+1,midr);
    return asc;
}

那么完整的代码就是

#include<bits/stdc++.h>
using namespace std;
const int N=1000+10;
char pre[1000],mid[1000];
int w[N];
int ans;
struct node{
    char data;
    int l,r;
}t[N];
int build(int prel,int prer,int midl,int midr){
    int asc=pre[prel];
    int pos=w[asc];
    if(midl<pos)t[asc].l=build(prel+1,pos-midl+prel,midl,pos-1);
    if(midr>pos)t[asc].r=build(pos-midl+prel+1,prer,pos+1,midr);
    t[asc].data=pre[prel];
    return asc;
}
void dfs(int x,int high){
    if(x==0)return;
    dfs(t[x].l,high+1);
    dfs(t[x].r,high+1);
    cout<<t[x].data;
    ans=max(high,ans);
}
void bfs(int x){
    queue<int> q;
    if(x)q.push(x);
    while(q.size()){
        x=q.front();
        if(t[x].l)q.push(t[x].l);
        if(t[x].r)q.push(t[x].r);
        cout<<t[x].data;
        q.pop();
    }
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int T;
    cin>>T;
    while(T--){
        //init
        ans=0;
        for(int i='A';i<='z';i++){
            t[i].l=0;
            t[i].r=0;
        }
       cin>>pre+1>>mid+1;
       int n=strlen(pre+1);
        for(int i=1;i<=n;i++)
           w[mid[i]]=i;
        int root=build(1,n,1,n);
        dfs(root,1);
        cout<<endl;
        bfs(root);
        cout<<endl;
    }
}

顺便帮忙在复习一遍层序遍历。doge

树形dp

本来还想将一下树形dp的,但是怎么说呢,跟咱们的第一个模板很像,又包含dp的内容,这么样吧,先讲一个例题开开胃。

活动 - AcWing还有这个活动 - AcWing都是经典题目

树形dp就是跟咱们做的dp没区别,在学会树的基础上

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef pair<int,int> PII;
#define xx first
#define yy second
int n;
int res;
// struct node{
//     int ne,quan;
// };
//vector<node> t[N];
vector<PII> t[N];
int dp[N],dp2[N];//dp1为第一大的,dp2为第二大的
void dfs(int x,int fa){
    dp[x]=0,dp2[x]=0;
     for(int i=0;i<t[x].size();i++){
        int ne=t[x][i].xx;
        int dis=t[x][i].yy;
        if(ne==fa)continue;
        dfs(ne,x);
        if(dp[x]<dp[ne]+dis){
            dp2[x]=dp[x];
            dp[x]=dp[ne]+dis;
        }
        else if(dp2[x]<dp[ne]+dis)
            dp2[x]=dp[ne]+dis;
     }
}
signed main(){
     cin>>n;
     for(int i=1;i<=n-1;i++){
        int a,b,c;
        cin>>a>>b>>c;
        t[a].push_back({b,c});
        t[b].push_back({a,c});
     }
     dfs(1,0);
     for(int i=1;i<=n;i++)
        res=max(dp[i]+dp2[i],res);
     cout<<res<<endl;
}
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int f[N][2];
vector<int> a[N];
int n;
bool st[N];
void dfs(int x,int pa){
    for(int i=0;i<a[x].size();i++){
        int ne=a[x][i];
        if(ne==pa)continue;
        dfs(ne,x);
        f[x][0]+=max(f[ne][1],f[ne][0]);
        f[x][1]+=f[ne][0];
    }
}
signed main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        scanf("%d",&f[i][1]);
    }
    for(int i=1;i<=n-1;i++){
        int x,y;
        cin>>x>>y;
        st[x]=true;
        a[x].push_back(y);
        a[y].push_back(x);
    }
    int root=1;
    while(st[root])root++;
    dfs(root,0);
    int ans=max(f[root][1],f[root][0]);
    cout<<ans;
}

这两个题目我觉得很妙的地方在于第一个的开两个数组(开一个也行,dp[N][2]不过就是二维的啦)记录第一长的子树长和第二长的,第二个题目while(st[root])root++;很好,为啥呢,因为我在输入的时候拿bool把子节点都设为了true,这样如果遍历一遍1~n的时候停下来的就是最顶上的头结点,从这里开始递归搜索dp。

以上就讲这么多了,再讲可能你也食而不化。

就这样把。

今天是我的生日,留下更多的赞和评论鼓励我把,虽然赞和评论根本换不了一分钱csdn是这样的。

依旧是芙芙单推人的自律的学习的一天呢。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值