The longest simple path between any two vertices of a tree is called the tree's diameter.
Prerequisite: tree basics.
Introduction
Clearly, a tree may have multiple diameters, all with the same length.
The tree's diameter can be found in time either by running DFS twice or by using tree DP.
Two DFS traversals
First, run a DFS from any vertex and let be the vertex reached at the greatest distance. Then run a second DFS from ; the vertex reached at the greatest distance from is denoted by , so is the tree's diameter.
Clearly, if the vertex reached by the first DFS is an endpoint of a diameter, the vertex reached by the second DFS is necessarily the other endpoint. We only need to prove that must be an endpoint of a diameter in every case.
Theorem: Starting from any vertex in a tree, a DFS that reaches the vertex farthest from it must reach an endpoint of a diameter.
Proof
We use contradiction. Let the starting vertex be . Let the actual diameter be , and suppose that the farthest vertex reached by the first DFS from is , and that this vertex is neither nor . There are three cases.
- If lies on :
We have , contradicting the fact that is the longest simple path between any two vertices of the tree.
- If does not lie on and shares a path with :
Again, , contradicting the fact that is the longest simple path between any two vertices of the tree.
- If does not lie on and shares no path with :
We have , contradicting the fact that is the longest simple path between any two vertices of the tree.
In all three cases, the assumption leads to a contradiction, proving the theorem.
Negative edge weights
The proof above assumes that every path has nonnegative total weight. If the tree contains a negative-weight edge, the proof no longer holds. Therefore, when negative-weight edges are present, the diameter cannot be found by the two-DFS method.
If we need all vertices on one diameter, record each vertex's predecessor during the second DFS. We can then walk from one endpoint toward the starting point and enumerate every vertex on the diameter.
Tree DP
Method 1
Root the tree at vertex . For each vertex, record the length of the longest path that extends downward from it and the length of the second-longest path that shares no edge with the longest path. The diameter is the maximum value of over all vertices.
Tree DP can find the diameter even when negative edge weights are present.
If all vertices on one diameter are required, record during the DP the child corresponding to each vertex's longest and second-longest downward path (as defined above). While finding , also record the vertex satisfying . Then follow the child corresponding to the longest path from and the child corresponding to the second-longest path in their respective directions. For an unrooted tree, although was chosen as the root, the direction of every jump must still be recorded; for a rooted tree, one side can simply be followed upward. This enumerates every vertex on the diameter.
Method 2
Here is a tree-DP method that uses only one array.
Define as the longest path starting from within the subtree rooted at . The transition is straightforward: , where is a child of and is the weight of the edge traversed.
The tree's diameter can be obtained by maximizing the sum of two different paths starting from the same vertex. Therefore, during the DP, before updating , compute ; this gives the diameter .
Example
Luogu B4016 Tree Diameter
Given a tree with vertices, find the length of its diameter. .
Reference implementation using two DFS traversals
#include <cstdio>
#include <vector>
using namespace std;
constexpr int N = 100000 + 10;
int n, c, d[N];
vector<int> E[N];
void dfs(int u, int fa) {
for (int v : E[u]) {
if (v == fa) continue;
d[v] = d[u] + 1;
if (d[v] > d[c]) c = v;
dfs(v, u);
}
}
int main() {
scanf("%d", &n);
for (int i = 1; i < n; i++) {
int u, v;
scanf("%d %d", &u, &v);
E[u].push_back(v), E[v].push_back(u);
}
dfs(1, 0);
d[c] = 0, dfs(c, 0);
printf("%d\n", d[c]);
return 0;
}
Reference implementation of tree DP with two arrays
#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;
constexpr int N = 100000 + 10;
int n, d;
int d1[N], d2[N];
vector<int> E[N];
void dfs(int u, int fa) {
d1[u] = d2[u] = 0;
for (int v : E[u]) {
if (v == fa) continue;
dfs(v, u);
int t = d1[v] + 1;
if (t > d1[u])
d2[u] = d1[u], d1[u] = t;
else if (t > d2[u])
d2[u] = t;
}
d = max(d, d1[u] + d2[u]);
}
int main() {
scanf("%d", &n);
for (int i = 1; i < n; i++) {
int u, v;
scanf("%d %d", &u, &v);
E[u].push_back(v), E[v].push_back(u);
}
dfs(1, 0);
printf("%d\n", d);
return 0;
}
Reference implementation of tree DP with one array
#include <algorithm>
#include <cstdio>
#include <vector>
using namespace std;
constexpr int N = 100000 + 10;
int n, d;
int dp[N];
vector<int> E[N];
void dfs(int u, int fa) {
for (int v : E[u]) {
if (v == fa) continue;
dfs(v, u);
d = max(d, dp[u] + dp[v] + 1);
dp[u] = max(dp[u], dp[v] + 1);
}
}
int main() {
scanf("%d", &n);
for (int i = 1; i < n; i++) {
int u, v;
scanf("%d %d", &u, &v);
E[u].push_back(v), E[v].push_back(u);
}
dfs(1, 0);
printf("%d\n", d);
return 0;
}
Property
Tree diameters have the following property: if every edge of a tree has positive weight, the midpoints of all its diameters coincide.
Proof
We use contradiction. Let two diameters with distinct midpoints be and , with midpoints and , respectively. Clearly, .
We have , contradicting the fact that is the longest simple path between any two vertices of the tree. Hence the property follows.
Exercises
- CodeChef, Diameter of Tree
- Educational Codeforces Round 35, Problem F, Tree Destruction
- ZOJ 3820 Building Fire Stations
- CEOI2019/CodeForces 1192B. Dynamic Diameter
- ICPC 2019 Shanghai Regional Online Contest, Lightning Routing I
- NOIP2007 Advanced Group, Tree Network Core
- SDOI2011 Firefighting
- APIO2010 Patrol