樹的直徑方法總結


定義:

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

求法:

  1、樹形 DP
  2、兩次 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 這條鏈。

具體代碼:

     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

        優點 : 可以通過一個新的數組記錄路徑信息(例如父節點與子節點之間的關系)
        缺點 : 無法處理 負邊權(遇到 負邊權 涼涼)
       
        實現過程 :
        1、從任意一個節點出發,通過 BFS 或 DFS 對樹進行一次遍歷,求出與出發點距離最遠的節點,記為 p。
        2、從節點 p 出發,通過 BFS 或 DFS 再進行一次遍歷,求出與 p 距離最遠的節點,記為 q。
        (p 是一個節點的最遠的一個端點,那么從 p 出發的最遠的端點就是直徑的另一個端點)
        
       為什么無法處理負邊權?


看上面這個圖: 如果按照 DFS 或者 BFS 我們第一次 找到的最遠距離的節點是 2 , 然后從 2 出發
到達的最遠距離的節點是 1 ,所以得到的樹的直徑長度是 1 ,但我們從圖中很容易看出來樹的直徑最長
應該是 2.(用樹形 DP 的話從下向上就可以得到最長的樹的直徑的長度)

具體代碼 : 
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 ;
}



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM