一.树的由来
树是一种数据结构,它是由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;
}
五.练习题
学了那么多,是时候该大显身手了:
#D. 树的路径覆盖之覆盖边1_zealous_zzx的博客-CSDN博客
树的练习题之#D. Journey之二_zealous_zzx的博客-CSDN博客
如果我学了新的关于树的知识,会继续补充的,有什么不对的欢迎发评论!