【树上问题】树的直径

目录

定义

求解

        方法一:两次DFS/BFS算法(更常用、更直观)

                算法核心:

                模板:

                复杂度分析:

方法二:树形动态规划(处理更复杂的问题)

        核心思想:

        状态定义:

        状态转移与答案更新:

        模板:

        复杂度分析:


定义

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

引理:在树中,对于任意节点 u,存在节点 v 使得 d(u,v) 最大,且 v 是树的某个直径端点;


求解

方法一:两次DFS/BFS算法(更常用、更直观)

算法核心:

这是一个基于搜索的算法,其核心思想在于一个关键性质:从树中的任意一点出发,找到距离它最远的点,这个点一定是某条直径的一个端点。然后从这个端点出发,再次寻找距离它最远的点,这两个端点之间的路径就是树的直径。

模板

B4016 树的直径

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+55;
int n,lr,     // lr记录最长路径长度
    pos,      // pos记录最长路径的端点
    dep[N];   // dep记录每个节点的深度(距离起始点的距离)
vector<int> g[N]; // 邻接表存储树结构
queue<int> q;     // BFS使用的队列
// BFS函数,从x节点开始搜索,返回距离x最远的节点
int bfs(int x){
    // 清空队列
    while(!q.empty()) q.pop();
    // 初始化dep数组为0
    memset(dep,0,sizeof(dep));
    // 从x节点开始BFS
    q.push(x);
    dep[x]=1; // 起始节点深度为1
    while(!q.empty()){
        int u=q.front(); // 取出队首节点
        q.pop();
        // 遍历u的所有邻居节点
        for(auto v:g[u]){
            if(!dep[v]){ // 如果v未被访问过
                dep[v]=dep[u]+1; // 计算v的深度
                // 更新最长路径信息
                if(dep[v]>lr){
                    lr=dep[v]; // 记录最长深度
                    pos=v;     // 记录对应节点
                }
                q.push(v); // 将v加入队列
            }
        }
    }
    return pos; // 返回距离x最远的节点
}
int main(){
    // 优化输入输出
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    // 输入节点数量
    cin>>n;
    // 输入n-1条边,构建树结构
    for(int i=1;i<n;i++){
        int u,v;
        cin>>u>>v;
        g[u].push_back(v); // 无向图,双向添加
        g[v].push_back(u);
    }
    // 计算树的直径:
    // 1. 从任意节点(1)开始BFS,找到最远节点pos
    // 2. 从pos节点开始BFS,找到最远节点,此时lr-1就是直径长度
    bfs(bfs(1));
    // 输出直径长度(lr-1是因为初始节点深度从1开始计算)
    cout<<lr-1;
    return 0;
}
复杂度分析

时间复杂度:O(n),进行了两次DFS,每次都遍历了所有节点。

空间复杂度:O(n),用于存储树结构和距离数组。


方法二:树形动态规划(处理更复杂的问题)

核心思想

对于树中的每个节点,我们考虑经过该节点的最长路径。那么,整个树的直径就是所有节点计算出的“经过该节点的最长路径”中的最大值。

状态定义

设 d1[u] 表示以 u 为根的子树中,从 u 出发能到达的最远距离(也称为u的“向下最长链”)。设 d2[u] 表示以 u 为根的子树中,从 u 出发能到达的次远距离(与最远路径没有公共边)。

状态转移与答案更新

在DFS遍历节点u时,遍历其所有子节点v

  1. 从子节点v回来时,我们得到一个候选值 length = d1[v] + w(u, v)

  2. 用这个length去更新 d1[u] 和 d2[u]

    • 如果 length > d1[u],则 d2[u] = d1[u]d1[u] = length

    • 否则如果 length > d2[u],则 d2[u] = length

  3. 那么,经过节点 u 的最长路径长度就是 d1[u] + d2[u]。我们用这个值去更新全局的直径答案 ans = max(ans, d1[u] + d2[u])

模板:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int dp[N],dp2[N];
vector<int> g[N];
void dfs(int u,int fa){
	//dp[u]=dp2[u]=-1e9;//如果有负边权就需要初始化,但是叶节点要初始化成0
	//bool flag=false;//判断该节点是不是叶节点
	for(auto v:g[u]){
		if(v==fa) continue;
    	//flag=true;//如果可以继续向下搜索就不是叶节点
		dfs(v,u);
		if(dp[v]+1>dp[u]){//注意这里如果有边权要加上边权
			dp2[u]=dp[u];//如果从这条路走的长度比dp[u]要长
			dp[u]=dp[v]+1;
		}
		else if(dp[v]+1>dp2[u]){
			dp2[u]=dp[v]+1;//如果从这条路走的长度比dp2[u]要长
		}
	}
	//if(!flag) dp[u]=dp2[u]=0;//如果是叶节点就要初始化成0
	return;
}
int main(){
	int n,ans=0;//ans用于最后求max,所以如果有负边权就要初始化成-1e9
	cin>>n;
	for(int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		g[u].push_back(v);
		g[v].push_back(u);//建图
	}
	dfs(1,0);//从任意点遍历
	for(int i=1;i<=n;i++)//注意最后要遍历找答案
		ans=max(ans,dp[i]+dp2[i]);//dp[i]+dp2[i]就是经过点i的最长的路
	cout<<ans<<endl;
	return 0;
}

复杂度分析

时间复杂度:O(n),只需一次DFS。

空间复杂度:O(n)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值