【图论C++】树的直径(DFS 与 DP动态规划)

这篇具有很好参考价值的文章主要介绍了【图论C++】树的直径(DFS 与 DP动态规划)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

》》》算法竞赛

/**
 * @file            
 * @author          jUicE_g2R(qq:3406291309)————彬(bin-必应)
 *						一个某双流一大学通信与信息专业大二在读	
 * 
 * @brief           一直在竞赛算法学习的路上
 * 
 * @copyright       2023.9
 * @COPYRIGHT			 原创技术笔记:转载需获得博主本人同意,且需标明转载源
 * @language        C++
 * @Version         1.0还在学习中  
 */
  • UpData Log👆 2023.9.27 更新进行中
  • Statement0🥇 一起进步
  • Statement1💯 有些描述是个人理解,可能不够标准,但能达其意

技术提升站点

21-1 树的直径

21-1-1 定义

树上 最远的两个节点之间 的距离被称为 树的直径,连接这两个点的路径 被称为 树的最长链

21-1-2 性质

  • 1 、这两个最远点一定是叶子节点 1、这 两个最远点 一定是 叶子节点 1、这两个最远点一定是叶子节点
  • 2 、距任意结点最远的点一定是直径的端点 2、距 任意结点最远的点 一定是 直径的端点 2、距任意结点最远的点一定是直径的端点
  • 3 、两棵树相连,新树的直径的两端点一定是原四个端点中的两个 3、两棵树相连,新树的直径的两端点一定是原四个端点中的两个 3、两棵树相连,新树的直径的两端点一定是原四个端点中的两个
  • 4 、若一棵树存在多条直径,多条直径交于一点,且交点是直径的严格中点(中点可能在某条边内) 4、若一棵树存在多条直径,多条直径交于一点,且交点是直径的严格中点(中点可能在某条边内) 4、若一棵树存在多条直径,多条直径交于一点,且交点是直径的严格中点(中点可能在某条边内)

21-1-3 实现的方法 及 选择

1)做两次DFS(或BFS)

2)树形DP

操作方法 优点 缺点
做两次DFS(或BFS) 可以得到完整的路径,从而得到点与点之间的距离 不能用于有 负权值 的树
树形DP 能用于有 负权值 的树 不可以得到完整的路径

树的直径

Input

就测试一个边上权值都为1的满二叉树

7
1 2 1
1 3 1
2 4 1
2 5 1
3 6 1
3 7 1

Output

4
直通车——>树的存储方法:链式前向星

21-1-4 法一:做两次DFS(或BFS)

  • 从任意 u 结点 u结点 u结点 出发,离 u 结点 u结点 u结点 最远的 e 结点 e结点 e结点,一定是该树直径的其中一个端点(性质2)
  • 从得到的这个 e 结点 e结点 e结点 出发,离 u 结点 u结点 u结点 最远的 s 结点 s结点 s结点,一定是该树直径的其中另一个端点(性质2,定义)
  • s 结点 s结点 s结点 e 结点 e结点 e结点 就是这棵树直径的端点
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
vector<int> head(N,-1);
struct Edge{                                        //链式前向星
    int to,next;
    int weight;
    Edge():to(-1), next(-1){}				        //初始化为无邻居节点
} edge[N<<1];
int n;                                              //人有n个,关系有n-1条
int cot=0;
vector<int> dis(N,0);                               //记录距离
void Add_Edge(int u, int v,int w){
    edge[cot].to=v;
    edge[cot].weight=w;
    edge[cot].next=head[u];                         //记录 上一个邻居节点 的 存储编号
    head[u]=cot++;                                  //当前 邻居节点 的 存储编号,以便下一个邻居节点的访问
}
void DFS(int u, int father, int w){
    dis[u]=dis[father]+w;                           //更新当前节点的距离
    for(int i=head[u]; ~i; i=edge[i].next){		    //遍历cur节点的邻居节点[~i相当于i=-1]
        int v=edge[i].to;                           //v 是 u 的子节点
        if(v==father)       continue;               //不遍历父节点
        DFS(v, u, edge[i].weight);
    }
} 
int main(void){
    int n;      cin>>n;
    for(int i=1; i<n; i++){
        int u,v,w;    cin>> u >> v >> w;
        Add_Edge(u,v,w);  Add_Edge(v,u,w);          //无向 记录 双向有向
    }

    /*找到树直径的其中一个端点*/
    DFS(1,0,0);                                     //以 1号节点 为根节点遍历整个树,获得所有节点离节点1的距离
    int n1_id=1;                                    //初始化
    for(int i=1; i<=n; i++)                         //遍历输入的n个结点
        if(dis[i]>dis[n1_id])                       //最终是为了找到到 结点1 距离最远的那个节点
            n1_id=i;
    
    /*找到树直径的另一端点*/
    DFS(n1_id,0,0);                                 //以 n1_id结点 为根节点开始遍历整棵树,最终最远的那个距离就是直径
    int n2_id=1;                                    //初始化
    for(int i=1; i<=n; i++)                         //遍历输入的n个结点
        if(dis[i]>dis[n2_id])                       //最终是为了找到到 n1_id节点 距离最远的那个节点
            n2_id=i;
    cout<<dis[n2_id];       
    return 0;
}
//法一
void DFS(int u, int father, int w){
    dis[u]=dis[father]+w;                           //更新当前节点的距离
    for(int i=head[u]; ~i; i=edge[i].next){			//遍历cur节点的邻居节点[~i相当于i=-1]
        int v=edge[i].to;                           //v 是 u 的子节点
        if(v==father)       continue;               //不遍历父节点
        DFS(v, u, edge[i].weight);
    }
} 

//法二
vector<bool> visit(N,false);
void DFS(int u, int father, int w){
    dis[u]=dis[father]+w;                           //更新当前节点的距离
    visit[u]=true;								    //标记为已访问,避免下次再访问
    for(int i=head[u]; ~i; i=edge[i].next){
        int v=edge[i].to;                           //v 是 u 的子节点
        if(visit[v])        continue;               //v已经算过了,避免重复遍历
        DFS(v, u, edge[i].weight);
    }
}  
DFS(BFS)为何不能用在有 负权值 的树里呢?

很容易想到一个反例:离目标节点 的 倒数第二远的节点到最远的节点 这条边如果权值为负,会得出 dis[倒数第二远]>dis[最远的节点] 的错误结论。(是因为我们让权值和作为判断 是否远 的依据)

而我们比较depth,就可以解决这个问题。

21-1-5 法二:树形DP(动态规划)

直通车——>DP算法求最大子序和
为何 DP能解决 有 负权值 的树 的树直径问题?
  • 贪心思想 实现的 DFS算法 暴露的问题就是只满足 “局部最优,而不顾全局”Dijkstra算法 同理也不能使用在有 负权值 的树。

  • 全局最优的 DP动态规划算法可以弥补这个短板,Floyd算法 基于 DP 同理也能使用在有 负权值 的树。

如何实现 动态规划?

d p [ u ] dp[u] dp[u] 是 以 u结点 为根节点的子树上,从 u结点 出发能到达的最远路径的长度,这个路径的终点是 u结点子树 的叶子节点

  • 状态转移方程

d p [ u ] = m a x ( d p [ v i ] + e d g e [ u , v i ] ) dp[u]=max(dp[v_i]+edge[u,v_i]) dp[u]=max(dp[vi]+edge[u,vi]) v i v_i vi u 结点 u结点 u结点 第 i 个邻居节点, e d g e [ u , v i ] edge[u,v_i] edge[u,vi] 是他们边上的权值】

  • 每个结点的最长路径长度

u 节点 u节点 u节点 的每个结点的最长路径长度 记录在 a n s [ u ] ans[u] ans[u]

f [ u ] f[u] f[u] 状态转换方程: f [ u ] = m a x ( d p [ u ] + d p [ v i ] + e d g e [ u , v i ] ) f[u]=max(dp[u]+dp[v_i]+edge[u,v_i]) f[u]=max(dp[u]+dp[vi]+edge[u,vi])【此时的 d p [ u ] dp[u] dp[u] 是不包含 v i 子树 v_i子树 vi子树 的,即 d p [ u ] = m a x ( d p [ v i ] + e d g e [ u , v i ] ) dp[u]=max(dp[v_i]+edge[u,v_i]) dp[u]=max(dp[vi]+edge[u,vi]) 是在这个状态转化方程后执行的】

maxlen=max(maxlen, dp[u]+dp[v]+edge[i].weight);
dp[u]=max(dp[u], dp[v]+edge[i].weight);
//注这里的 max函数 可以替换成 三目运算符 来实现

树的直径为 m a x l e n = m a x ( f [ u ] ) maxlen=max(f[u]) maxlen=max(f[u]),即 最大的 结点的最长路径长度(从定义出发考虑)文章来源地址https://www.toymoban.com/news/detail-729963.html

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
vector<int> head(N,-1);
struct Edge{                                        //链式前向星
    int to,next;
    int weight;
    Edge():to(-1), next(-1){}				        //初始化为无邻居节点
} edge[N<<1];
int n;                                              //人有n个,关系有n-1条
int maxlen=0;                                       //树的直径
int cot=0;
vector<bool> visit(N,false);
vector<int> dp(N,0);
void Add_Edge(int u, int v,int w){
    edge[cot].to=v;
    edge[cot].weight=w;
    edge[cot].next=head[u];                         //记录 上一个邻居节点 的 存储编号
    head[u]=cot++;                                  //当前 邻居节点 的 存储编号,以便下一个邻居节点的访问
}
void DP(int u){
    visit[u]=true;                                  //标记为已访问,避免下次再访问
    for(int i=head[u]; ~i; i=edge[i].next){         //遍历cur节点的邻居节点[~i相当于i=-1]
        int v=edge[i].to;                           //v 是 u 的子节点
        int u_v=edge[i].weight;                     //u 与 v 边上的权值
        if(visit[v])       continue;                //v已经算过了,避免重复遍历
        DP(v);
        maxlen=max(maxlen, dp[u]+dp[v]+u_v);        //将当前值与历史最大比较
        dp[u]=max(dp[u], dp[v]+u_v); 
    }
}
int main(void){
    int n;      cin>>n;
    for(int i=1; i<n; i++){
        int u,v,w;    cin>> u >> v >> w;
        Add_Edge(u,v,w);  Add_Edge(v,u,w);          //无向 记录 双向有向
    }
    DP(1);
    cout<<maxlen;       
    return 0;
}

到了这里,关于【图论C++】树的直径(DFS 与 DP动态规划)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++ DP算法,动态规划——背包问题(背包九讲)

    有N件物品和一个容量为 V V V 的背包。放入第i件物品耗费的空间是 C i C_i C i ​ ,得到的价值是 W i W_i W i ​ 。 求解将哪些物品装入背包可使价值总和最大。 这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。 用子问题定义状态:即 F [ i , v ] F[i, v] F

    2024年02月16日
    浏览(48)
  • 【数位dp】【动态规划】C++算法:233.数字 1 的个数

    视频算法专题 动态规划汇总 给定一个整数 n,计算所有小于等于 n 的非负整数中数字 1 出现的个数。 示例 1: 输入:n = 13 输出:6 示例 2: 输入:n = 0 输出:0 提示: 0 = n = 10 9 本题比较简单,主要讲封装类。m_vPre记录上一位所有状态,程序结束时,记录的是最后一位的所有

    2024年01月16日
    浏览(41)
  • 【图论】树的直径

    树的直径即为一棵树中距离最远的两点之间的路径 先以任意一点为起点跑一遍dfs,记录离起点距离最远的点p(这个点一定是直径的一个端点,感性理解一下不证明了),然后再以最远点再跑一遍dfs,记录此时距离最远的点q,那么pq就是该树的直接 树中有负权边时不可以用这

    2024年01月20日
    浏览(44)
  • 【算法深度探索】动态规划之旅(1):挑战OJ题海,解锁15道经典难题,让你成为DP大师!

    📃 博客主页: 小镇敲码人 🚀 欢迎关注:👍点赞 👂🏽留言 😍收藏 🌏 任尔江湖满血骨,我自踏雪寻梅香。 万千浮云遮碧月,独傲天下百坚强。 男儿应有龙腾志,盖世一意转洪荒。 莫使此生无痕度,终归人间一捧黄。🍎🍎🍎 ❤️ 什么?你问我答案,少年你看,下一

    2024年04月11日
    浏览(36)
  • [算法日志]图论: 深度优先搜索(DFS)

    ​ 深度优先搜索算法是一种遍历图这种数据结构的算法策略,其中心思想是朝图节点的一个方向不断跳转,当该节点无下一个节点或所有方向都遍历完时,便回溯朝上一个节点的另一个方向继续遍历。这种搜索策略与回溯法有异曲同工之妙。 正因为和回溯法有相似之处,所

    2024年02月03日
    浏览(62)
  • 「算法小记」-2:矩阵链相乘的方案数【迭代/递归/动态规划/区域化DP/记忆化搜索】(C++ )

    😎 作者介绍:我是程序员洲洲,一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主、前后端开发、人工智能研究生。公粽号:程序员洲洲。 🎈 本文专栏:本文收录于洲洲的《算法小记》系列专栏,该专栏记录了许

    2024年02月05日
    浏览(50)
  • [C++][算法基础]树的重心(树图DFS)

    给定一颗树,树中包含 n 个结点(编号 1∼n)和 n−1 条无向边。 请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。 重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心

    2024年04月12日
    浏览(35)
  • DP算法:动态规划算法

    (1)确定初始状态 (2)确定转移矩阵,得到每个阶段的状态,由上一阶段推到出来 (3)确定边界条件。 蓝桥杯——印章(python实现) 使用dp记录状态,dp[i][j]表示买i张印章,凑齐j种印章的概率 i表示买的印章数,j表示凑齐的印章种数 情况一:如果ij,不可能凑齐印章,概

    2024年02月07日
    浏览(49)
  • 【c++】动态规划(dp)框架

    动态规划(Dynamic Programming)是解决计算问题的一种方法,通常用于解决优化问题和涉及重叠子问题的计算问题。它的核心思想是通过将问题分解为更小的子问题来求解,并且记忆化子问题的解,避免重复计算,从而提高效率。下面详细解释动态规划以及优化方法,并提供一个

    2024年02月03日
    浏览(42)
  • 算法——动态规划(DP)——递推

    动态规划常用于解决优化问题。 动态规划通常以自底向上或自顶向下的方式进行求解。 自底向上的动态规划从最简单的子问题开始,逐步解决更复杂的问题,直到达到原始问题。 自顶向下的动态规划则从原始问题出发,分解成子问题,并逐步求解这些子问题。 动态规划算法

    2024年01月20日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包