EduBrick

Диаметр дерева

Два обхода вместо перебора всех пар. Алгоритм в пять строк, доказательство длиннее алгоритма — и почему в общем графе так нельзя.

3 мин

Диаметр дерева — наибольшее расстояние между какой-либо парой вершин.

Наивно: перебрать все пары и найти расстояние — O(n3)O(n^3). Лучше: запустить обход из каждой вершины — O(n2)O(n^2). Но есть решение за O(n)O(n), и оно неожиданно короткое.

Алгоритм

  1. Возьмите произвольную вершину vv.
  2. Обходом найдите самую далёкую от неё вершину aa.
  3. Обходом найдите самую далёкую от aa вершину bb.

Расстояние между aa и bb — это диаметр.

pair<int, int> farthest(int start) {   // {вершина, расстояние}
    vector<int> dist(n, -1);
    dist[start] = 0;
    queue<int> q;
    q.push(start);
    int best = start;
    while (!q.empty()) {
        int v = q.front(); q.pop();
        if (dist[v] > dist[best]) best = v;
        for (int to : g[v]) if (dist[to] == -1) { dist[to] = dist[v] + 1; q.push(to); }
    }
    return {best, dist[best]};
}

int diameter() {
    int a = farthest(0).first;
    return farthest(a).second;
}

Два обхода, O(n)O(n). Проверено: на двадцати тысячах случайных деревьев до двенадцати вершин результат совпал с перебором всех стартовых вершин.

Почему это верно

Утверждение, которое нужно доказать: самая далёкая вершина от любой вершины является концом какого-то диаметра.

Подвесим дерево за vv и посмотрим на найденную вершину aa.

Шаг 1: aa — лист. Иначе у неё есть ребёнок, который дальше от vv, — противоречие с тем, что aa самая далёкая.

Шаг 2. Пусть диаметр — это путь между xx и yy, и ни один из концов не совпадает с aa. Путь в подвешенном дереве — это подъём до наименьшего общего предка xx и yy и спуск обратно; обозначим этот предок cc.

Путь от vv до aa тоже где-то проходит. Возможны два случая.

Если путь vav \to a пересекает путь xyx \to y, пусть точка пересечения — tt. Тогда расстояние от tt до aa не меньше расстояния от tt до xx и до yy: иначе aa не была бы самой далёкой от vv. Значит, заменив xx на aa, мы не укоротим путь, и aa — конец диаметра.

Если пути не пересекаются, соединим их: получится путь длиннее диаметра, что невозможно.

Отсюда: aa — конец какого-то диаметра, и самая далёкая от неё вершина — второй конец.

Замечание про единственность: диаметров может быть много. Алгоритм найдёт какой-то один, и его длину — а она у всех одинакова.

В общем графе так нельзя

В произвольном графе диаметр — наибольшее из кратчайших расстояний между парами. Два обхода тут не работают, и быстрого алгоритма не известно.

Причина глубокая: умей мы быстро считать диаметр, мы умели бы проверять, равен ли он n1n-1, — а это существование гамильтонова пути, задача NP-полная.

На практике диаметр графа считают алгоритмом Флойда за O(n3)O(n^3) или nn обходами в ширину за O(nm)O(nm). Ничего лучше в общем случае нет.

Родственные задачи

Центр дерева — вершина, минимизирующая расстояние до самой далёкой. Она лежит на середине диаметра: находим диаметр, идём по нему до середины. Центров один или два, в зависимости от чётности длины.

Радиус — расстояние от центра до самой далёкой вершины, равен d/2\lceil d/2 \rceil.

Диаметр через динамику. Подвешиваем дерево и для каждой вершины считаем две наибольшие глубины среди детей. Их сумма — длиннейший путь, проходящий через эту вершину; максимум по всем вершинам и есть диаметр. Тоже O(n)O(n), зато обобщается на взвешенные рёбра и на задачи, где нужен не только ответ, но и путь.

Взвешенное дерево. Приём с двумя обходами работает и там, если веса неотрицательны. С отрицательными весами он ломается, и это стандартный контрпример к «а я слышал, что диаметр ищется двумя обходами».