树的入门(满满的干货,建议收藏)

一.树的由来

是一种数据结构,它是由n(n≥0)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:

每个节点有零个或多个子节点;没有父节点的节点称为根节点;每一个非根节点有且只有一个父节点;除了根节点外,每个子节点可以分为多个不相交的子树

二.树的定义

1.树

一种由n个节点组成的具有一定层次关系的有限数据集合。每个节点有0个或者n个子节点有一个根节点没有前驱只有后继),除根节点外每一个节点都有一个前驱0个或多个后继

2.树的叶子结点

叶子结点是指只有父结点,没有子结点。叶子结点就像自然界中树上的叶子都长不出来新的树枝一样,故而得名。

3.树中每个节点的度

每个节点的度是指该结点的子结点个数。

4.树的度

树中结点的最大的度。

5.分支结点

不为0的结点。

6.树的深度

即从根节点到叶子节点的行数(根结点深度是0)

7.每个结点的度

即该结点到根节点的行数

8.树的高度

任意叶子节点距离根节点的最大深度

二.树的储存

这里有两种方法:

1.

pre[i]:对于第i条边来说,他的上一条边是那一条边

Now[i]:对与点x来说,最后一条描述它充当父结点的边是哪一条边

Son[i]:在第i条边中,充当子结点的点是哪一个


void put(int x,int y)
{
    pre[++tot]=nom[x];
    now[x]=tot;
    son[tot]=y;
}

2.我比较推荐的


vector<int> adj;
int main()
{
    int n,x,y;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>x>>y;   //x和y相连
        adj[x].push_back(y);  //双向建边
        adj[y].push_back(x);
    }
    return 0;
}

三.树的遍历

我们就拿求每个点的深度模板来举例吧。


#include<bits/stdc++.h>
using namespace std;
int n,q,a[50001];
vector<int> adj[50001];  //我用的是vector
void dfs(int s,int d,int dep)   //dfs,s是当前结点,d是s的父结点,dep是深度
{
    a[s]=dep;   //当前的结点s的深度是dep
    for(int i=0;i<adj[s].size();i++)    //遍历与s相连的每一个结点
    {
        if(adj[s][i]!=d)dfs(adj[s][i],s,dep+1);   //如果不是父结点就继续dfs
    }
}
int main()
{
    cin>>n;
    for(int i=1;i<n;i++)   //输入
    {
        int c,d;
        cin>>c>>d;   //c和d是相连的
        adj[c].push_back(d);
        adj[d].push_back(c);
    }
    dfs(1,0,0);   //dfs
    for(int i=1;i<=n;i++)cout<<a[i]<<endl;   //输出每个点的深度
    return 0;
}

四.树的直径

1.定义

树上任意两节点之间最长的简单路径即为树的直径。

2.求法

第一种

两次dfs,首先求出离跟结点z最远的结点x,再求出离x最远的结点y,那么结点x就是直径的一端结点y就是直径的另一端,所以从结点x到结点y的这一段路径就是树的直径

证明:

注意事项:

上述证明过程建立在所有路径均不为负的前提下。如果树上存在负权边则上述证明不成立

因此:

如果存在 负权边,是无法使用 两次DFS的方式来求解 直径的。
代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,x,y,z,maxx,t;
vector<int> adj[1000001];
void dfs(int u,int fa,int dep)
{
    if(maxx<dep)    //更新深度最深的结点
    {
        maxx=dep;
        t=u;
    }
    for(int i=0;i<adj[u].size();i++)
    {
        int t=adj[u][i];
        if(t==fa)
        {
            continue;
        }
        dfs(t,u,dep+1);
    }
}
signed main()
{
    cin>>n;
    for(int i=1;i<n;i++)
    {
        cin>>x>>y;
        adj[x].push_back(y);
        adj[y].push_back(x);
    }
    dfs(1,0,0);
    maxx=0;
    dfs(t,0,0);
    cout<<maxx;
    return 0;
}

第二种

树形dp

我们记录以1为根节点,每个结点作为当前这个子树的根时,假设能延伸出的最长距离为d1,次长距离是d2,那么以该结点为当前这个子树的根时的直径就是d1+d2

注意事项:

如果存在 负权边,是可以使用树形dp的方式来求解 直径的。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,x,y,d1[1000001],d2[1000001],cnt;   //d1数组是指以该结点为当前这个子树的根时的最长距离,d2数组是指次长距离
vector<int> adj[1000001];
void dfs(int u,int fa)
{
    d1[u]=0;d2[u]=0;
    for(int i=0;i<adj[u].size();i++)
    {
        int t=adj[u][i];
        if(t==fa)
        {
            continue;
        }
        dfs(t,u);
        int tep=d1[t]+1;
        if(tep>d1[u])
        {
            d2[u]=d1[u];
            d1[u]=tep;
        }
        else if(tep>d2[u])d2[u]=tep;
    }
    cnt=max(cnt,d1[u]+d2[u]);
}
signed main()
{
    cin>>n;
    for(int i=1;i<n;i++)
    {
        cin>>x>>y;
        adj[x].push_back(y);
        adj[y].push_back(x);
    }
    dfs(1,0);
    cout<<cnt;
    return 0;
}

五.练习题

学了那么多,是时候该大显身手了:

#F. 树的直径的问题-CSDN博客

#D. 树的路径覆盖之覆盖边1_zealous_zzx的博客-CSDN博客

#E. 加加加树的边权-CSDN博客

树的练习题之#D. Journey之二_zealous_zzx的博客-CSDN博客

树的练习题之#C. Journey-CSDN博客

扫雪系列I与扫雪系列II题解-CSDN博客

#F. 啸聚山林-CSDN博客

如果我学了新的关于树的知识,会继续补充的,有什么不对的欢迎发评论!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值