树上的动态规划学习3 - 求最长路径 (最远点对)

这里的树是指无根树。

样例输入
8
1 2
1 3
1 4
4 5
3 6
6 7
7 8
样例输出
6

解法1:先将无根树转有根树(任选一点做根),然后用DFS找到离根最远的节点,则此节点必为最远点对中的一个。然后再以此节点为根将该树转有根树,找到离其最远的节点,其距离即为最长路径。

注意: 第2次转有根树的时候要将新根的parent节点清成-1,不然它还是用上次的parent节点。

#include <iostream>
#include <vector>

using namespace std;
#define N 1000000+10
vector<int> G[N];   //stores the graph
int parent[N];
int n;

void read_tree()
{
    int u, v;
    cin>>n;
    for (int i = 0; i < n - 1; i++)    //注意无向图n个节点,边数为n-1
    {
        cin>>u>>v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
}


void dfs_root_tree(int u, int father)
{
    for (int i = 0; i < G[u].size(); i++)
    {
        if (G[u][i] != father) { //加此判断,防止无限递归
            parent[G[u][i]] = u;
            dfs_root_tree(G[u][i], u);
        }
    }
}

int end_root;
int max_depth=0;

void dfs_find_longest_node(int u, int father, int depth) {

    if (max_depth < depth ) {
        max_depth = depth;
        end_root = u;
    }

    if (G[u].size()==0) return;

    for (int i=0; i<G[u].size(); i++) {
        if (G[u][i] != father) {
            dfs_find_longest_node(G[u][i], u, depth+1);
        }
    }

   return;
}


int main()
{
    read_tree();
    int root;

    cin >> root;
    parent[root] = -1;
    dfs_root_tree(root, -1);
    //for (int i = 1; i <= n; i++)
     //   cout << parent[i] << ' ';
    //cout<<endl;

    dfs_find_longest_node_1(root, parent[root], 0);
    cout<<"end_root is "<<end_root<<endl;

    parent[end_root] = -1;
    dfs_root_tree(end_root, -1);
    //for (int i = 1; i <= n; i++)
    //   cout << parent[i] << ' ';
    //cout<<endl;

    dfs_find_longest_node(end_root, parent[end_root], 0);
    cout<<"end_root is "<<end_root<<endl;
    cout<<"len = "<<max_depth<<endl;

    return 0;
}

该解法稍嫌繁琐,因为要转两次有根树。实际上我们不用parent[]也可以,因为可以用vis[]来表示有没有访问过,也就是说不需要转换成有根树了。简化解法下次再写。

解法2:设d[i]为根为节点i的子树中根到叶子的最大距离。我们选一个子节点数>=2的节点作为root (如果儿子数都是<=1,那么这就是一个简单的link list,return n-1 就可以了),然后对每个节点i, 要记录该节点下面的子节点们的d[]里面的前两个最大的,比如说d[u]和d[v]。一次DFS后,遍历所有节点,找出它们所记录的d[u]+d[v]的最大值,然后+2即可。

#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
#define N 1000000+10
vector<int> G[N];   //stores the graph
int n;

void read_tree()
{
    int u, v;
    cin>>n;
    for (int i = 0; i < n - 1; i++)    //注意无向图n个节点,边数为n-1
    {
        cin>>u>>v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
}

int d[N] = {0};
int vis[N] = {0};
struct two_longest_children{
    int longest_depth;
    int second_longest_node;
}record[N];

void dfs_find_longest_node_2(int u) {
    vis[u] = 1;
    if (G[u].size()==0) {
        d[u] = 0;
        return;
    }
    for (int i=0; i<G[u].size(); i++) {
        if (!vis[G[u][i]]) {
            dfs_find_longest_node_2(G[u][i]);
            int temp = d[G[u][i]];
            if (d[u] <= temp+1) {
                d[u]= temp+1;
            }

            if (record[u].longest_depth <= temp) {
                record[u].second_longest_depth = record[u].longest_depth;
                record[u].longest_depth = temp;
            }

            if ((record[u].second_longest_depth <= temp) && (record[u].longest_depth > temp)) {
                record[u].second_longest_depth = temp;
            }
        }
    }
    return;
}

int main()
{
    int root=0xFF;
    read_tree();
    memset(record,0,sizeof(record));

    for (int i=1; i<n; i++) {
        if (G[i].size()>=2) {
            root=i;
            break;
        }
    }

    if (root==0xFF) return n-1;
    dfs_find_longest_node_2(root);

    int g_max_len = 0;

    for (int i=1; i<=n; i++) {
        if (g_max_len <= record[i].longest_depth+record[i].second_longest_depth + 2) {
            g_max_len = record[i].longest_depth+record[i].second_longest_depth + 2;
        }
    }
    cout<<"g_max_len is "<<g_max_len<<endl;
    return 0;
}

注意:
1) 用该方法最长路径并不一定经过根。看看下面的图就知道了。 所以我们要记录所有的节点的子节点d[]里面的最大的两个值。上面的办法可行是因为用了两次转换成有根树。

 2) 跟第一种方法相比,该方法有个缺点就是不能记录到底是哪两个节点的最远对。因为dfs_find_longest_node_2()里面每个节点只能知道自己哪两个子节点有最长的depth,但不能一直跟踪到叶子节点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值