cppAcademy
← Learning PathLESSON 5 / 15 · TREES · 32 MIN✕

Tree Diameter

The diameter is the maximum distance between any two vertices of a tree. This lesson measures distance in edges, so a single vertex has diameter 0. A tree may have several longest paths; a correct algorithm only needs to find one. Diameter endpoints are useful landmarks for distance problems, and the middle of a diameter identifies the tree center or centers.

Trying BFS from every vertex costs quadratic time. A special property of trees lets two traversals find a diameter in linear time. You will implement both endpoint searches and reconstruct an actual longest path, then justify why choosing a farthest vertex works.

Prerequisites: Tree Traversals (DFS and BFS); parent arrays and path reconstruction.

Open in Tree Lab ↗

In the existing Tree Lab, choose Tree Diameter in Algorithms mode. The lab opens with its own working tree; the example below is described independently.

Build the idea

  1. 1. Make the goal precise

    Take edges (1,2), (2,3), (3,4), (3,5), (5,6). Path 1 → 2 → 3 → 5 → 6 has four edges. No pair is farther apart, so the diameter length is 4. A diameter is not necessarily a root-to-leaf path for the root selected by the input or interface. Rooting at 3 gives maximum depth 2, while the diameter is 4.

  2. 2. Make two endpoint searches

    Start from any vertex s and use BFS to find a vertex A of maximum distance from s. Then BFS from A and choose a vertex B of maximum distance from A. The distance from A to B is the diameter. Store parent pointers during the second BFS; follow them from B back to A and reverse the list to output the path.

    In the example, starting at 3 may choose A=1 or A=6, depending on ties. Either choice is valid. Starting the second search from 1 finds B=6 and distance 4. The implementation uses strict improvement when updating farthest, so tied farthest vertices are resolved by traversal order without affecting correctness.

  3. 3. Why is a farthest vertex a diameter endpoint?

    Root the tree at the arbitrary start s, and let x–y be any diameter. Write t for the lowest common ancestor of x and y. A farthest vertex A has depth at least both depth[x] and depth[y]. If A lies below t, choose the endpoint lying in a different child branch of t from A, or t itself if t is an endpoint. The path from A to that endpoint goes through t. Replacing the other endpoint by A cannot shorten the path, because A is at least as deep.

    If A is not below t, its path to either endpoint goes through a common ancestor above t. Using depth[A] at least the depth of the opposite endpoint again gives a path at least as long as x–y. Since x–y was longest possible, the resulting path must also be a diameter. Thus A is an endpoint of some diameter, including when the first BFS has ties. The second BFS finds a farthest B from this endpoint and therefore reaches the diameter length.

  4. 4. Why BFS works, and where the method applies

    BFS discovers vertices in increasing numbers of edges from its start. On a tree, DFS can also accumulate exact unweighted distances because each vertex has a unique path from the start; ordinary DFS distances are not shortest-path distances on general graphs. The two-search endpoint theorem relies on tree structure and must not be applied blindly to a cyclic graph.

    For a tree with nonnegative edge weights, accumulating unique-path weight sums also supports a two-search diameter method. The code here is strictly unweighted. Negative edge weights need a different treatment, particularly if endpoints must be distinct; do not silently reuse this implementation.

  5. 5. Reconstruct and verify the answer

    The second parent array points toward A. Start at B and append vertices until reaching parent 0; the list ends at A. After reversing, consecutive labels must be actual edges, and path.size()−1 must equal distance[B]. N=1 naturally gives A=B=1, length 0, and path {1}. The longest path contains no repeated vertices because a tree has no cycles.

    Two traversals examine every vertex and edge only a constant number of times. The important optimization is not a faster queue: it is using the endpoint property to avoid N separate searches.

C++17 implementation

This is a complete program. Compile with C++17 and enter the input described below.

#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
int bfs(int start,const vector<vector<int>>&graph,vector<int>&distance,vector<int>&parent){
    int n=(int)graph.size()-1;
    // Reset distances and parents for this search, including the new start vertex.
    distance.assign(n+1,-1);
    parent.assign(n+1,0);
    queue<int>q;
    q.push(start);
    distance[start]=0;
    int farthest=start;
    while(!q.empty()){
        int v=q.front();
        q.pop();
        if(distance[v]>distance[farthest]){
            farthest=v;
        }
        for(int next:graph[v]){
            if(distance[next]!=-1){
                continue;
            }
            distance[next]=distance[v]+1;
            // This parent records the unique path back to this BFS start.
            parent[next]=v;
            q.push(next);
        }
    }
    return farthest;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin>>n;
    vector<vector<int>>graph(n+1);
    for(int i=0;i<n-1;++i){
        int a,b;
        cin>>a>>b;
        graph[a].push_back(b);
        graph[b].push_back(a);
    }
    vector<int>distance,parent;
    // A farthest vertex from any start is an endpoint of some diameter.
    int a=bfs(1,graph,distance,parent);
    // Searching from that endpoint reaches the opposite diameter endpoint.
    int b=bfs(a,graph,distance,parent);
    // The second search parents reconstruct B-to-A; reverse for A-to-B output.
    vector<int>path;
    for(int v=b;v!=0;v=parent[v]){
        path.push_back(v);
    }
    reverse(path.begin(),path.end());
    cout<<distance[b]<<'\n';
    for(int i=0;i<(int)path.size();++i){
        cout<<path[i]<<(i+1==(int)path.size()?'\n':' ');
    }
}

Complexity analysis

O(N) time for two BFS runs and path reconstruction. O(N) space for adjacency lists, queue, distance/parent arrays, and the output path. The path has at most N vertices.

Common mistakes and edge cases. Count edges, not vertices: a path containing k vertices has length k−1. Do not confuse maximum root depth with diameter. Keep the second BFS parent array for reconstruction. Multiple longest paths are valid. The theorem and this code assume a valid connected unweighted tree.

Example input and output

N, followed by N−1 edges of a valid unweighted tree. Print diameter length in edges, then one diameter path. Other longest paths are accepted.

Input

6
1 2
2 3
3 4
3 5
5 6

Output

4
6 5 3 2 1

The first BFS from 1 chooses 6; the second finds 1. The printed path crosses four edges. Reversing this path would also be correct.

Practical applications

Find worst-case travel distance, tree centers, and endpoints used in farthest-distance queries. In an unweighted tree with diameter endpoints A,B, the eccentricity of v is max(distance(v,A),distance(v,B)), a useful extension requiring its own justification.

Check your understanding

Why is maximum depth from an arbitrary root not necessarily the diameter?

Put it into practice

Print both middle vertex/vertices of your diameter path. Test paths with odd and even diameter lengths.

Show a hint

If D is even, path[D/2] is the only middle vertex; if D is odd, use path[D/2] and path[D/2+1].