樹的直徑方法總結

定義:

直徑 :   在圓上兩點(不相交)之間最遠的距離就是咱們一般所說的直徑。
    樹的直徑 : 樹上最遠的兩個節點之間的距離就被稱爲樹的直徑,鏈接這兩點的路徑被稱爲樹的最長鏈。

求法:

一、樹形 DP
  二、兩次 BFS 或者 兩次 DFS

算法 1 : 樹形 DP

優勢 : 能夠有效處理 負邊權
  缺點 : 對於記錄路徑的信息效率較低
  
  簡單分析 :  先經過遞歸的方式到葉子底部,而後經過自底向上的方式進行更新距離,找到最長路徑。
  (看下圖,能夠獲得這棵樹的直徑是通過根節點 1 的 路徑最長的鏈  5 -> 2 -> 1 和 通過根節點 1 的路徑 次長鏈 3 -> 6 -> 1 二者之和
   由此可得:樹的直徑 = (通過某個節點的) 最長鏈 + 次長鏈) -- 是路徑長度哦

實現過程:
    設 D[x] 表示從節點 x 出發走向以 x 爲根的子樹,可以到達的最遠距離。
    設 x 的子節點爲 y1,y2....yt,edge(x,y)表示邊權,顯然有: D[x] = max(D[yi] + edge(x,yi))(i 的範圍是 1 - t)

    也就是說,從 根節點出發,找到本身的最小輩,而後從最小輩向根節點更新,找一個最長的路徑鏈。
    只要子節點是最長的,那麼咱們更新到根節點時,這條鏈毫無疑問也就是最長的。

    咱們在找某個節點的最長鏈會發現一個問題,就是當前節點有有幾個子節點,有一個咱們沒得選,當有多個時咱們就須要選擇最長的。
    拿上面的圖來講, 2 號節點有 兩個子節點,咱們就須要進行比較一下,100 > 2 + 30 ,因此咱們應該選擇 5 -> 2 這條鏈。

具體代碼:node

void dp(int x) {
     vis[x] = 1;
     for(int i = head[x]; i ; i = Next[i]) {
        int y = ver[i];
        if(vis[y]) continue;                          // 判斷是否已經通過該節點
        dp(y);                                        // 繼續向下尋找子節點
        ans = max(ans,dist[x] + dist[y] + edge[i]);   // 枚舉從 x 節點出發的全部邊,找一個最遠的路徑(看上面的 2 號節點)(dist[x] 是當前目前已知的最長的,
                                                              // 可是 x 可能有多個分支,因此須要枚舉找最長的或者次長的,造成通過該節點的直徑)
        dist[x] = max(dist[x],dist[y] + edge[i]);     // 通過枚舉後 dist[x] 就不必定是當前最長的的,因此須要更新一下。
     }

算法 2 : 兩次 DFS 或者 兩次 BFS

優勢 : 能夠經過一個新的數組記錄路徑信息(例如父節點與子節點之間的關係)
        缺點 : 沒法處理 負邊權(遇到 負邊權 涼涼)
       
        實現過程 :
        一、從任意一個節點出發,經過 BFS 或 DFS 對樹進行一次遍歷,求出與出發點距離最遠的節點,記爲 p。
        二、從節點 p 出發,經過 BFS 或 DFS 再進行一次遍歷,求出與 p 距離最遠的節點,記爲 q。
        (p 是一個節點的最遠的一個端點,那麼從 p 出發的最遠的端點就是直徑的另外一個端點)
        
       爲何沒法處理負邊權?


看上面這個圖: 若是按照 DFS 或者 BFS 咱們第一次 找到的最遠距離的節點是 2 , 而後從 2 出發
到達的最遠距離的節點是 1 ,因此獲得的樹的直徑長度是 1 ,但咱們從圖中很容易看出來樹的直徑最長
應該是 2.(用樹形 DP 的話從下向上就能夠獲得最長的樹的直徑的長度)ios

具體代碼 :
BFS:
#include <queue>
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int maxn = 1e5 + 10;

int head[maxn * 2],edge[maxn * 2],Next[maxn * 2],ver[maxn * 2];
int vis[maxn],dist[maxn];
int n,p,q,d;
int tot = 0;
int maxd = 0;

int main(void) {
    int BFS(int u);
    void add(int u,int v,int w);
    scanf("%d",&n);
    for(int i = 1; i < n; i ++) {
        scanf("%d%d%d",&p,&q,&d);
        add(p,q,d);                   // 創建無向圖
        add(q,p,d);
    }
    int u = BFS(1);
    int s = BFS(u);
    printf("第一次遍歷獲得的節點 : %d\n",u);
    printf("第二次遍歷獲得的節點 : %d\n",s); 
    return 0;
}

void add(int u,int v,int w) {
    ver[ ++ tot] = v,edge[tot] = w;
    Next[tot] = head[u],head[u] = tot;
    return ;
}

int BFS(int u) {
    queue<int>Q;
    while(!Q.empty()) Q.pop();
    memset(vis,0,sizeof(vis));                         // 每次遍歷的時候記得對數組進行 Clear
    memset(dist,0,sizeof(dist));  
    Q.push(u);
    int x,max_num = 0;
    while(!Q.empty()) {
        x = Q.front();
        Q.pop();
        vis[x] = 1;
        for(int i = head[x]; i ; i = Next[i]) {
            int y = ver[i];
            if(vis[y]) continue;
            vis[y] = 1;
            dist[y] = dist[x] + edge[i];    // 從上向下走,因此須要進行累加(這是與 樹形 DP最大的不一樣)
            if(dist[y] > maxd ) {           // 更新 值 和 節點編號
                maxd = dist[y];
                max_num = y;
            }
            Q.push(y);                      // 每一個新的節點都要加入到隊列中,有可能與該節點相連的路徑是比較長的
        }
    }
    return max_num;
}
DFS :
#include <vector>
#include <cstdio>
#include <string>
#include <cstring>
#include <string.h>
#include <iostream>
#include <algorithm>

#define x first
#define y second

using namespace std;

const int maxn = 1e5 + 10;
typedef pair<int,int> P ;   // 用 pair<int,int>來保存部分信息,相對於 結構體來講更加方便一點

vector<P> G[maxn];
int dist[maxn];
int n,p,q,d;

int main(void) {
    void solve();
    scanf("%d",&n);
    for(int i = 1; i < n; i ++) {
        scanf("%d%d%d",&p,&q,&d);
        G[p].push_back(make_pair(q,d));
        G[q].push_back(make_pair(p,d));
    }
    solve();
    return 0;
}

void DFS(int u,int father,int value) {
    dist[u] = value;                                           // 這種方式就不用進行對數組 Clear 了
    for(int i = 0; i < G[u].size(); i ++) {
        if(G[u][i].x != father) {
            DFS(G[u][i].x,u,value + G[u][i].y);
        }
    }
    
    return ;
}

void solve() {
    DFS(1,-1,0);
    int u = 1;
    for(int i = 1; i <= n; i ++) {                    // 遍歷尋找最大值
        if(dist[i] > dist[u]) {
            u = i;
        }
    }
    int x = u;
    DFS(u,-1,0);                                      // 第二次進行找另外一個端點
    for(int i = 1; i <= n; i ++) {
        if(dist[i] > dist[u]) {
            u = i;
        }
    }
    int s = u;
    printf("第一次遍歷獲得的最遠的節點編號 : %d\n",x);
    printf("第二次遍歷獲得的最遠的節點編號 : %d\n",s);
    return ;
}

參考資料:

yxc 的視頻講解:https://www.acwing.com/video/710/
    秦淮岸大佬的講義:https://www.acwing.com/blog/content/319
    最重要的是 AS 的細心講解。

例題:大臣的旅費

題目連接:https://www.acwing.com/problem/content/1209/)

題目描述:

好久之前,T王國空前繁榮。
    爲了更好地管理國家,王國修建了大量的快速路,用於鏈接首都和王國內的各大城市。
    爲節省經費,T國的大臣們通過思考,制定了一套優秀的修建方案,使得任何一個大城市都能從首都直接或者經過其餘大城市間接到達。
    同時,若是不重複通過大城市,從首都到達每一個大城市的方案都是惟一的。
    J是T國重要大臣,他巡查於各大城市之間,體察民情。
    因此,從一個城市快馬加鞭地到另外一個城市成了J最常作的事情。
    他有一個錢袋,用於存放往來城市間的路費。
    聰明的J發現,若是不在某個城市停下來修整,在連續行進過程當中,他所花的路費與他已走過的距離有關,在走第x公里到第x+1公里這一公里中(x是整數),他花費的路費是x+10這麼多。也就是說走1公里花費11,走2公里要花費23。
    J大臣想知道:他從某一個城市出發,中間不休息,到達另外一個城市,全部可能花費的路費中最可能是多少呢?
    輸入格式
    輸入的第一行包含一個整數 n,表示包括首都在內的T王國的城市數。
    城市從 1 開始依次編號,1 號城市爲首都。
    接下來 n−1 行,描述T國的高速路(T國的高速路必定是 n−1 條)。
    行三個整數 Pi,Qi,Di,表示城市 Pi 和城市 Qi 之間有一條雙向高速路,長度爲 Di 公里。
    輸出格式
    輸出一個整數,表示大臣J最多花費的路費是多少。
    數據範圍
    1≤n≤105,
    1≤Pi,Qi≤n,
    1≤Di≤1000

input:

5 
    1  2  2 
    1  3  1 
    2  4  5 
    2  5  4

output:

135

析題得說:

求路上的最大花費,最大花費由與距離有關,因此求出距離就能夠解出這道題目。
    實際上就是爲樹的直徑是多少,只不過這道題在最後計算結果的時候還須要注意一下。


看上圖和題意,咱們能夠得知: 距離爲 5 的花費爲
s = 5;
money = s * 10 + (s * (s + 1)) / 2算法

具體代碼:數組

算法 1 : 樹形 DP
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int maxn = 1e5 + 10;
int head[maxn],edge[maxn],Next[maxn],ver[maxn];
int dist[maxn],vis[maxn];
int tot = 0, ans = 0;
int n,p,q,d;

int main(void) {
    void dp(int x);
    void add(int u,int v,int w);
    scanf("%d",&n);
    for(int i = 1; i < n; i ++) {
        scanf("%d%d%d",&p,&q,&d);
        add(p,q,d);
        add(q,p,d);
    }
    ans = 0;
    dp(1);
    printf("%lld\n", ans * 10 + ans * (ans + 1ll ) / 2);
    return 0;
}

void add(int u,int v,int w) {
    ver[++ tot] = v,edge[tot] = w;
    Next[tot] = head[u],head[u] = tot;
    return ; 
}

void dp(int x) {
    vis[x] = 1;
    for(int i = head[x]; i ; i = Next[i]) {
        int y = ver[i];
        if(vis[y]) continue;
        dp(y);
        ans = max(ans,dist[x] + dist[y] + edge[i]);
        dist[x] = max(dist[x],dist[y] + edge[i]);
    }
    return ;
}
算法 2 : 兩次 BFS 

#include <queue>
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int maxn = 1e5 + 10;

int head[maxn * 2],edge[maxn * 2],Next[maxn * 2],ver[maxn * 2];
int vis[maxn],dist[maxn];
int n,p,q,d;
int tot = 0;
int maxd = 0;

int main(void) {
    int BFS(int u);
    void add(int u,int v,int w);
    scanf("%d",&n);
    for(int i = 1; i < n; i ++) {
        scanf("%d%d%d",&p,&q,&d);
        add(p,q,d);
        add(q,p,d);
    }
    int u = BFS(1);
    int s = BFS(u);
    printf("%lld\n", maxd * 10 + maxd * (maxd + 1ll ) / 2); 
    return 0;
}

void add(int u,int v,int w) {
    ver[ ++ tot] = v,edge[tot] = w;
    Next[tot] = head[u],head[u] = tot;
    return ;
}

int BFS(int u) {
    queue<int>Q;
    while(!Q.empty()) Q.pop();
    memset(vis,0,sizeof(vis));
    memset(dist,0,sizeof(dist));
    Q.push(u);
    int x,max_num = 0;
    while(!Q.empty()) {
        x = Q.front();
        Q.pop();
        vis[x] = 1;
        for(int i = head[x]; i ; i = Next[i]) {
            int y = ver[i];
            if(vis[y]) continue;
            vis[y] = 1;
            dist[y] = dist[x] + edge[i]; 
            if(dist[y] > maxd ) {
                maxd = dist[y];
                max_num = y;
            }
            Q.push(y);
        }
    }
    return max_num;
}
算法 3 : 兩次 DFS

#include <vector>
#include <cstdio>
#include <string>
#include <cstring>
#include <string.h>
#include <iostream>
#include <algorithm>

#define x first
#define y second

using namespace std;

const int maxn = 1e5 + 10;
typedef pair<int,int> P ;  

struct node {
    int id,w;
};
vector<P> G[maxn];
int dist[maxn];
int n,p,q,d;

int main(void) {
    void solve();
    scanf("%d",&n);
    for(int i = 1; i < n; i ++) {
        scanf("%d%d%d",&p,&q,&d);
        G[p].push_back(make_pair(q,d));
        G[q].push_back(make_pair(p,d));
    }
    solve();
    return 0;
}

void DFS(int u,int father,int value) {
    dist[u] = value;
    for(int i = 0; i < G[u].size(); i ++) {
        if(G[u][i].x != father) {
            DFS(G[u][i].x,u,value + G[u][i].y);
        }
    }
    return ;
}

void solve() {
    DFS(1,-1,0);
    int u = 1;
    for(int i = 1; i <= n; i ++) {
        if(dist[i] > dist[u]) {
            u = i;
        }
    }
    DFS(u,-1,0);
    for(int i = 1; i <= n; i ++) {
        if(dist[i] > dist[u]) {
            u = i;
        }
    }
    int s = dist[u];
    printf("%lld\n", s * 10 + s * (s + 1ll ) / 2);
    return ;
}
相關文章
相關標籤/搜索