关于树的简单笔记

1树的存储

vector<int>g[N];//这里用邻接表
cin>>x>>y;
g[x].push_back(y);
g[y].push_back(x);

2树的遍历

简单的dfs

void dfs(int u,int fa)//u为当前节点,fa是从哪个节点来的
{
for(int v:g[u])
{
if(v==fa)continue;//防止原路返回
dfs(v,u);
}
}
dfs(1,0);

3树的直径

概念:树上任意两点之间的最长的简单路径。

性质:

1树上任何一个点能走到的最远的点一定是直径的端点。

2

求树的直径

1两次dfs,从任意点出发走到最远点,再从该最远点走到最远点。

int dis[N];//某点到其他点的距离
int w[N];路径的权值,这里默认为1;
void(int u,int fa)
{
    for(int v:g[u])
    {
        if(fa==v)//不原路返回
        continue;
        dis[v]=dis[u]+1;
        dfs(v,u);
    } 
}
int ans1=1;//记录距离最大的点
for(int i=1;i<=n;i++)//遍历寻找距离最远的点
{
if(dis[i]>dis[ans])
ans1=i;
}
//ans1为所求的第一个端点,还需要从ans1开始找到最远点。

2树形dp,求每个点向下的最远和次远的和,取max。

vector<int>f(n+1),k(n+1)//f为最远距离,k为次远距离。
int ans=0;
void(int u,int fa)
{
    for(int v:g[u])
    {
        if(fa==v)continue;
        dfs(v,u);
        if(f[v]+1>f[u])//子节点最远距离加1大于当前点,就更新
        {
        k[u]=f[u];
        f[u]=f[v]+1;
        }
        else if(fa[v]+1>k[u])//更新次节点
        {
        k[u]=fa[v]+1;
        }
    }
    ans=max(ans,fa[u]+k[u]);
}

4LCA最近公共祖先

(两个节点的最近公共祖先)

倍增法

记录父亲节点

int father[N];
int fa[N][25];//记录第i个点上方2^j个父亲节点。
for(int i=1;i<=n;i++)
{
fa[i][0]=father[i];
}
for(int j=1;(1<<j)<=n;j++)
{
    for(int i=1;i<=n;i++)
    fa[i][j]=fa[fa[i][j-1]][j-1];
}
int h[N];记录每个节点的深度,在树的遍历可以得到。
int lca(int a,int b)//查找a与b的最近公共祖先。
{
    if(h[b]>h[a])//让a成为更深的,对a作往上爬的动作。
    {
    int t=a;
    a=b;
    b=a;
    } 
    
}
for(int i=20;i>=0;i--)
{
    if(h[a]-(1<<i)>=b)
    a=fa[a][i];//a向上爬
}
//遍历结束后,a与b的深度相同
if(a==b)//如果a与b相遇,说明b是原来a的祖先之一
{
return b;
}
for(int i=20;i>=0;i--)
{
    if(fa[a][i]!=fa[b][i])//如果不是同一个点就往上爬,如果是的话就往下看。
    {
        a=fa[a][i];
        b=fa[b][i]
    }
}
return fa[a][0];//最终爬到了lca下面

5树的重心

概念:某节点最大子树的节点数最少,该节点就是重心。

求树的重心

int centroid;//记录重心;
int dfs(int u,int fa)
{
    int siz=1,mx=0;//mx记录该节点子树的最大值
    for(int v:g[u])
    {
        if(v==fa)
        continue;
        int son=dfs(v,u);//记录子节点方向子树的节点数
        int siz+=son;
        mx=max(mx,son);
    }
    mx=max(mx,n-siz);//和父节点方向的子树节点数比较。
    if(mx*2<=n)
    centroid=u;//最大的子树不超过总节点的一半,u为重心。
    return siz;
}

6最小生成树

在一个有边权的图中,得到一个边权和最小的树。

(1)KRUSKAL

struct node{
int from,to,w;//起点,终点,权值。
friend bool operator <(const node&a,const node&b)
{
    return a.w<b.w;
}//排序
}edge[N]//存储边
int fa[N];//父节点
void init()
{
for(int i=0;i<n;++i)
fa[i]=i;
}
int find(int x)//寻找父节点
{
    int x2=x,pre;
    while(x2!=fa[x2])x2=fa[x2];
    while(x!=fa[x1])
    {
        pre=x;
        x=fa[x];
        fa[pre]=x2;
    }
return x2;
}
void merge(int x,int y)//合并
{
x=find(x);
y=find(y);
if(x!=y)
fa[x]=y;
return;
}
vector<node>tree;//记录构成树的边
int kruskal(int n,int m);//n为点数,m为边数
{
sort(edge,edge+m);
int sum=0;//边权和
int cnt=0;//树的边的边数
for(int i=0;i<m&&cnt!=n-1;i++)//n-1是最终树的边数
{
    int from=edge[i].from;//起点
    int to=edge[i].to;//终点
    if(find(from)!=find(to))//如果不在同一颗树上
    {
    cnt++;
    merge(from,to);
    sum+=edge[i].w;
    tree.push_back(edge[i]);//把该边加入树内
    }
}
return sum;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值