[week6]氪金带东——树的直径(图和树性质的应用)

题意

实验室里原先有一台电脑(编号为1),最近氪金带师咕咕东又为实验室购置了N-1台电脑,编号为2到N。每台电脑都用网线连接到一台先前安装的电脑上。但是咕咕东担心网速太慢,他希望知道第i台电脑到其他电脑的最大网线长度,但是可怜的咕咕东在不久前刚刚遭受了宇宙射线的降智打击,请你帮帮他。

Input

输入文件包含多组测试数据。对于每组测试数据,第一行一个整数N (N<=10000),接下来有N-1行,每一行两个数,对于第i行的两个数,它们表示与i号电脑连接的电脑编号以及它们之间网线的长度。网线的总长度不会超过10^9,每个数之间用一个空格隔开。

Output

对于每组测试数据输出N行,第i行表示i号电脑的答案 (1<=i<=N).

输入样例

5
1 1
2 1
3 1
1 1

输出样例

3
2
3
4
4

提示

在这里插入图片描述样例输入对应这个图,从这个图中你可以看出,距离1号电脑最远的电脑是4号电脑,他们之间的距离是3。 4号电脑与5号电脑都是距离2号电脑最远的点,故其答案是2。5号电脑距离3号电脑最远,故对于3号电脑来说它的答案是3。同样的我们可以计算出4号电脑和5号电脑的答案是4.


分析

这道题本质上是应用了求树的直径的方法,也就是求树中任意两点之间的最大距离。


  • 如何求树的直径?

树的直径是指,树中任意两个节点之间的最大距离。也就是在树中找到两个点,使得两个点之间的距离是最大的,也可以理解为最长路径。

那么我们首先需要找到这两个相隔最远点。

从树中任意一点开始遍历,找到一个离起始点最远的点,这个点一定为两个最远点中的第一个。再从这个最远点开始遍历找到一个离它最远的点,这个点就是另外一个最远点。两个点之间的距离即为树的直径。【至于为什么是这样,可以试着举例证明】


  • 题目分析

根据题目中给出的图片及其解析,这些电脑实际上就是许多节点,彼此之间的连接就是它们相连的边,而任何两个电脑之间的连接都是双向的,也就是说边是无向的。因此题目给出的电脑连接模型实际上就是一个无向加权图。

题目要求找到距离每个电脑最远的电脑与其之间的距离。而任意一个点可以到达的最远的点一定是该图结构中的最远路径两点中的一个,否则最远路径将不符合要求。

因此,我们就联想到了求树的直径。

树本质上就是权重都相等的无向加权图,结构本质是相同的。所以这道题就可以通过在求树的直径的算法上加以修改解决。

找到两个图结构中的最远点,再求每个点到这两个最远点的距离,输出较大值即可。


  • 代码实现

1. 如何表示图结构?

这在数据结构中都有学习过,在此就不做详细的解释。有以下三种表示方式:

  • 邻接矩阵
  • 邻接链表
  • 链式前向星

【小tip:链式前向星
这种表示方式实际上就是用不规则数组和存储索引来模拟实现链表和链表中的next指针。
1.所有边进行标号,依次存入一个数组中
2.用另外一个数组,下标代表节点编号,存入任意一条与当前编号代表节点相连边的编号
3.在边结构体中添加了一个int型的next索引数据成员,指向射出该边的节点的其他相连边的编号。当next为-1时,说明没有再与该边关联射出点相连的边了。
4.通过从每个节点对应数组中存储编号对应的边开始遍历,顺着next编号遍历该节点相连的所有边,直到next为-1。
5.插入新边的时候总是插入在模拟链表的头部,也就是让其对应节点数组存储的编号更新为新边的编号,而该新边的next索引更新为之前节点数组中存储的编号。

这三种存储方式中:

  • 矩阵的搜索性能最好,但是其空间复杂度最大
  • 链表的空间复杂度低,但是若使用stl实现,可能会在极端情况出现超时
  • 前向星空间复杂度低,但是不能还原拓扑结构

在这次的代码中,我使用的是邻接链表。这个存储方式比较熟悉且性能良好,在数据范围不是很大的时候,很推荐使用这种表示方式。

2. 如何通过遍历找到与固定点相距最远的点?

首先,树和图的遍历方式大家都很熟悉,有BFS👉[week2]经典迷宫问题——BFS搜索法和DFS👉[week3]选数问题——DFS搜索算法两种方法。

此处我使用的是BFS。

那么,问题关键在于,如何在遍历中找到与固定点相距最远的点

只要我们求出其他所有点到该固定点的距离,进行比较,就可以得到与这个固定点相距最远的点了。

于是,只要我们将该固定点设置为起始点,从该点开始遍历,在遍历过程中记录每个点到起始点的距离,直到遍历完所有点时,我们就能得到其他所有点到起始点的距离了。

用一个数组记录距离,每个下标对应所存的元素即为编号等于该下标的节点到起始点的距离。在遍历当前队列中队首所存节点相连的每个节点时,记录未到达的节点到起始点的距离,即为当前队首所存节点到起始点的距离加上这两点所连边的权重。当遍历完全时,数组中已经存储了每个节点到固定点的距离。

【小tip:为什么这样计算长度?
假设起始点为x0,那么x0到xi的距离一定是x0到x(i-1)的距离加上x(i-1)到xi的距离。实际上就是一种动态规划的思想,将问题分解为动相互联系的子问题进行求解。】

3. 如何找到最远的两点?

在遍历寻找最远点时,在求出当前未到达点到起始点的距离后,记录当前扫描过的所有距离中的最大距离。当最大距离被更新时,最远点编号更新为当前遍历的关联点的编号。

4. 如何确定每个点到哪个最远点的距离更远?共需要几次遍历?

分别从两个最远点遍历所有点后,就能得到所有点与这两点分别相距的距离。进行比较,较大者即为答案。

在本题的思路设计中,遍历情况为:

  • 第一次遍历是从任意一点开始遍历,目标为找到第一个最远点。此时得到的距离为该任意选择的起始点与所有点的距离,而这个距离和题目答案无关。
  • 第二次遍历是从第一次遍历得到的最远点开始遍历,目标为找到第二个最远点。而这次遍历会得到所有点与第一个起始点的距离。
  • 因此,只需要再进行一次遍历,从第二个最远点开始遍历,得到所有点到该点的距离即可。

  • 遇到的问题

这次的代码又遇到了非常神奇又无语的问题🥵

1. 题目要求输入多组数据,但是样例只给了一组数据

如果读题太马虎,就会和我一样遇到这种好巧不巧的情况,正好整治了我这样的毛病。

题目要求会输入多组数据,而样例恰好只给出了一组,于是就自然而然地顺理成章地认为只会输入一组数据。

这种WA的问题真的是找几个小时都很难发现,因为都会以为是算法的问题。所以也是再一次得到了教训,要认真读题。

2. 数组参数

写代码的过程中遇到的小问题。其实自己整理过很多遍了关于参数传递的问题,但是时间久了每次遇到仍然会傻。

int a[]

这种数组参数声明方式实际上就是将形参数组a所代表的实参数组的首地址传输到对应函数中。因此在函数中的所有操作会直接影响传入的实参。

而一般的值参数,是需要加&或*来达到改变实参的目的。


总结

  1. 再一次证明了小错误比算法错误更让人崩溃🤯
  2. 还是说明了并不是每一次找不到WA的问题都是因为vector,但是首先检查vector也是没错的。

代码

//
//
//  Created by 王黎杉 on 2020/3/30.
//  Copyright © 2020 王黎杉. All rights reserved.
//

#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;

struct edge                 //边
{
    int in,out,weight;          //存储相互连接的两个电脑以及之间的距离
};

vector<edge> computer[100000];      //存储所有电脑之间的连接
/*vector<int> distances1(10001, 0);         //记录到第一个最远点距离
vector<int> distances2(10001, 0);         //记录到第二个最远点距离
vector<int> distances3(10001, 0);         //记录到随机点距离*/
int distances1[10001];
int distances2[10001];
int distances3[10001];

int maxdistance = 0,maxpoint = 0;

void bfs(int n,int distances[])
{
    vector<bool> point(100000,false);       //标记该点是否到达
    maxpoint = 0;
    maxdistance = 0;
    
    queue<int> q;
    
    q.push(n);              //随意选择一个起点进行遍历
    point[n] = true;
    
    while( !q.empty() )
    {
        int now = q.front();
        q.pop();
        
        for( int i = 0 ; i < computer[now].size() ; i++ )     //对当前点相连的点进行遍历
        {
            if( !point[computer[now][i].out] )      //若该点未被访问过
            {
                //则该点到起点的距离为其上个点到起点的距离+该点到上个点的距离
                distances[computer[now][i].out] = distances[now] + computer[now][i].weight;
                q.push(computer[now][i].out);
                point[computer[now][i].out] = true;
                //将该点压入队列中,并标记为已到达
                if( distances[computer[now][i].out] > maxdistance ) //记录最远距离和点
                {
                    maxdistance = distances[computer[now][i].out];
                    maxpoint = computer[now][i].out;
                }
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    
    int n = 0,a = 0,b = 0,max1 = 0,max2 = 0;
//    cin>>n;
    
    while( cin>>n )
    {
        for( int i = 0 ; i < 10001 ; i++ )
        {
            distances1[i] = 0;
            distances2[i] = 0;
            distances3[i] = 0;
        }
        
        for( int i = 1 ; i < n ; i++ )      //依次存储边
        {
            cin>>a>>b;                  //输入与编号为i+1的电脑相连的电脑编号a以及距离
            computer[i+1].push_back({i+1,a,b});     //将边信息存入对应标号的数组中
            computer[a].push_back({a,i+1,b});
        }
    
        bfs(1,distances3);                 //找到一个离随机点最远的点
        max1 = maxpoint;
        
    //    cout<<max1<<" * "<<endl;
        
        bfs(max1,distances1);              //再找到离上一个确立点最远的点
        max2 = maxpoint;                   //并记录每个点到第一个最远点的距离
        
        bfs(max2,distances2);              //再记录每一个点到第二个最远点的距离
        
    //    cout<<max2<<" * "<<endl;
        
        for( int i = 1 ; i <= n ; i++ )
            cout<<max(distances1[i], distances2[i])<<endl;
    
        for( int i = 1 ; i <= n ; i++ )
            computer[i].clear();
    }
    
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天翊藉君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值