Skip to content

minimum_diameter_spanning_tree.hpp

SECTIONGraph INCLUDEnoya/minimum_diameter_spanning_tree.hpp

Construct a minimum-diameter spanning tree of a connected graph. The center of an optimal tree can be placed at a vertex or inside one graph edge. All-pairs shortest paths give every vertex eccentricity. For an edge center, sorting vertices by distance from one endpoint makes the maximum of the two endpoint-distance envelopes change monotonically, so one sweep tests its best split point. Doubling edge weights keeps every half-integral center exact. Finally, a shortest-path tree rooted at the best absolute center realizes the selected minimum diameter.

Verified by minimum_diameter_spanning_tree.

在带权无向图中求直径最小的生成树,并构造对应树边。

Implementation

View on GitHub

#ifndef NOYA_MINIMUM_DIAMETER_SPANNING_TREE_HPP
#define NOYA_MINIMUM_DIAMETER_SPANNING_TREE_HPP 1

/// @complexity Time: O(V(E + V) log V + VE + V^2 log V).
/// Space: O(V^2 + V + E).

#include <algorithm>
#include <cassert>
#include <functional>
#include <limits>
#include <queue>
#include <tuple>
#include <utility>
#include <vector>

namespace noya {

template <class Weight> struct minimum_diameter_spanning_tree_result {
  Weight diameter{};
  std::vector<int> edge_ids;
};

/// @brief Construct a minimum-diameter spanning tree of a connected graph.
/// The center of an optimal tree can be placed at a vertex or inside one
/// graph edge. All-pairs shortest paths give every vertex eccentricity. For
/// an edge center, sorting vertices by distance from one endpoint makes the
/// maximum of the two endpoint-distance envelopes change monotonically, so
/// one sweep tests its best split point. Doubling edge weights keeps every
/// half-integral center exact. Finally, a shortest-path tree rooted at the
/// best absolute center realizes the selected minimum diameter.
template <class Weight>
minimum_diameter_spanning_tree_result<Weight>
minimum_diameter_spanning_tree(
    int vertex_count,
    const std::vector<std::tuple<int, int, Weight>> &edges) {
  assert(vertex_count > 0);
  struct adjacent_edge {
    int to;
    Weight weight;
    int id;
  };
  std::vector<std::vector<adjacent_edge>> graph(vertex_count);
  for (int id = 0; id < int(edges.size()); id++) {
    auto [first, second, weight] = edges[id];
    assert(0 <= first && first < vertex_count);
    assert(0 <= second && second < vertex_count);
    assert(weight >= Weight{});
    weight *= Weight(2);
    graph[first].push_back({second, weight, id});
    graph[second].push_back({first, weight, id});
  }

  const Weight infinity = std::numeric_limits<Weight>::max() / 4;
  struct shortest_path_result {
    std::vector<Weight> distance;
    std::vector<int> predecessor;
    std::vector<int> predecessor_edge;
  };
  auto shortest_paths = [&](int source,
                            const std::vector<std::vector<adjacent_edge>>
                                &current_graph) {
    int size = int(current_graph.size());
    shortest_path_result result{std::vector<Weight>(size, infinity),
                                std::vector<int>(size, -1),
                                std::vector<int>(size, -1)};
    using state = std::pair<Weight, int>;
    std::priority_queue<state, std::vector<state>, std::greater<state>> queue;
    result.distance[source] = Weight{};
    queue.emplace(Weight{}, source);
    while (!queue.empty()) {
      auto [distance, vertex] = queue.top();
      queue.pop();
      if (distance != result.distance[vertex]) {
        continue;
      }
      for (auto [to, weight, id] : current_graph[vertex]) {
        Weight candidate = distance + weight;
        if (candidate < result.distance[to]) {
          result.distance[to] = candidate;
          result.predecessor[to] = vertex;
          result.predecessor_edge[to] = id;
          queue.emplace(candidate, to);
        }
      }
    }
    return result;
  };

  std::vector<std::vector<Weight>> distance(
      vertex_count, std::vector<Weight>(vertex_count));
  for (int source = 0; source < vertex_count; source++) {
    distance[source] = shortest_paths(source, graph).distance;
    for (Weight value : distance[source]) {
      assert(value != infinity);
    }
  }

  std::vector<std::vector<int>> order(vertex_count,
                                      std::vector<int>(vertex_count));
  Weight best_vertex_score = infinity;
  int best_vertex = 0;
  for (int vertex = 0; vertex < vertex_count; vertex++) {
    for (int other = 0; other < vertex_count; other++) {
      order[vertex][other] = other;
    }
    std::stable_sort(order[vertex].begin(), order[vertex].end(),
                     [&](int first, int second) {
                       return distance[vertex][first] >
                              distance[vertex][second];
                     });
    Weight score = distance[vertex][order[vertex][0]] * Weight(2);
    if (score < best_vertex_score) {
      best_vertex_score = score;
      best_vertex = vertex;
    }
  }

  Weight best_edge_score = infinity;
  int best_edge = -1;
  int best_first = -1;
  int best_second = -1;
  Weight center_to_first{};
  Weight center_to_second{};
  for (int id = 0; id < int(edges.size()); id++) {
    auto [first, second, original_weight] = edges[id];
    if (first == second) {
      continue;
    }
    Weight weight = original_weight * Weight(2);
    int previous = order[first][0];
    for (int vertex : order[first]) {
      if (distance[second][vertex] > distance[second][previous]) {
        Weight score = distance[second][previous] +
                       distance[first][vertex] + weight;
        if (score < best_edge_score) {
          best_edge_score = score;
          best_edge = id;
          best_first = first;
          best_second = second;
          center_to_first = score / Weight(2) - distance[first][vertex];
          center_to_second = weight - center_to_first;
        }
        previous = vertex;
      }
    }
  }

  minimum_diameter_spanning_tree_result<Weight> result;
  if (best_edge_score < best_vertex_score) {
    int center = vertex_count;
    auto augmented = graph;
    augmented.emplace_back();
    augmented[center].push_back(
        {best_first, center_to_first, -1});
    augmented[center].push_back(
        {best_second, center_to_second, -1});
    auto tree = shortest_paths(center, augmented);
    result.diameter = best_edge_score / Weight(2);
    result.edge_ids.reserve(vertex_count - 1);
    for (int vertex = 0; vertex < vertex_count; vertex++) {
      if (vertex != best_first && vertex != best_second) {
        assert(tree.predecessor_edge[vertex] >= 0);
        result.edge_ids.push_back(tree.predecessor_edge[vertex]);
      }
    }
    result.edge_ids.push_back(best_edge);
  } else {
    auto tree = shortest_paths(best_vertex, graph);
    result.diameter = best_vertex_score / Weight(2);
    result.edge_ids.reserve(vertex_count - 1);
    for (int vertex = 0; vertex < vertex_count; vertex++) {
      if (vertex != best_vertex) {
        assert(tree.predecessor_edge[vertex] >= 0);
        result.edge_ids.push_back(tree.predecessor_edge[vertex]);
      }
    }
  }
  return result;
}

} // namespace noya

#endif // NOYA_MINIMUM_DIAMETER_SPANNING_TREE_HPP
#include <algorithm>
#include <cassert>
#include <functional>
#include <limits>
#include <queue>
#include <tuple>
#include <utility>
#include <vector>

/// @complexity Time: O(V(E + V) log V + VE + V^2 log V).
/// Space: O(V^2 + V + E).

namespace noya {

template <class Weight> struct minimum_diameter_spanning_tree_result {
  Weight diameter{};
  std::vector<int> edge_ids;
};

/// @brief Construct a minimum-diameter spanning tree of a connected graph.
/// The center of an optimal tree can be placed at a vertex or inside one
/// graph edge. All-pairs shortest paths give every vertex eccentricity. For
/// an edge center, sorting vertices by distance from one endpoint makes the
/// maximum of the two endpoint-distance envelopes change monotonically, so
/// one sweep tests its best split point. Doubling edge weights keeps every
/// half-integral center exact. Finally, a shortest-path tree rooted at the
/// best absolute center realizes the selected minimum diameter.
template <class Weight>
minimum_diameter_spanning_tree_result<Weight>
minimum_diameter_spanning_tree(
    int vertex_count,
    const std::vector<std::tuple<int, int, Weight>> &edges) {
  assert(vertex_count > 0);
  struct adjacent_edge {
    int to;
    Weight weight;
    int id;
  };
  std::vector<std::vector<adjacent_edge>> graph(vertex_count);
  for (int id = 0; id < int(edges.size()); id++) {
    auto [first, second, weight] = edges[id];
    assert(0 <= first && first < vertex_count);
    assert(0 <= second && second < vertex_count);
    assert(weight >= Weight{});
    weight *= Weight(2);
    graph[first].push_back({second, weight, id});
    graph[second].push_back({first, weight, id});
  }

  const Weight infinity = std::numeric_limits<Weight>::max() / 4;
  struct shortest_path_result {
    std::vector<Weight> distance;
    std::vector<int> predecessor;
    std::vector<int> predecessor_edge;
  };
  auto shortest_paths = [&](int source,
                            const std::vector<std::vector<adjacent_edge>>
                                &current_graph) {
    int size = int(current_graph.size());
    shortest_path_result result{std::vector<Weight>(size, infinity),
                                std::vector<int>(size, -1),
                                std::vector<int>(size, -1)};
    using state = std::pair<Weight, int>;
    std::priority_queue<state, std::vector<state>, std::greater<state>> queue;
    result.distance[source] = Weight{};
    queue.emplace(Weight{}, source);
    while (!queue.empty()) {
      auto [distance, vertex] = queue.top();
      queue.pop();
      if (distance != result.distance[vertex]) {
        continue;
      }
      for (auto [to, weight, id] : current_graph[vertex]) {
        Weight candidate = distance + weight;
        if (candidate < result.distance[to]) {
          result.distance[to] = candidate;
          result.predecessor[to] = vertex;
          result.predecessor_edge[to] = id;
          queue.emplace(candidate, to);
        }
      }
    }
    return result;
  };

  std::vector<std::vector<Weight>> distance(
      vertex_count, std::vector<Weight>(vertex_count));
  for (int source = 0; source < vertex_count; source++) {
    distance[source] = shortest_paths(source, graph).distance;
    for (Weight value : distance[source]) {
      assert(value != infinity);
    }
  }

  std::vector<std::vector<int>> order(vertex_count,
                                      std::vector<int>(vertex_count));
  Weight best_vertex_score = infinity;
  int best_vertex = 0;
  for (int vertex = 0; vertex < vertex_count; vertex++) {
    for (int other = 0; other < vertex_count; other++) {
      order[vertex][other] = other;
    }
    std::stable_sort(order[vertex].begin(), order[vertex].end(),
                     [&](int first, int second) {
                       return distance[vertex][first] >
                              distance[vertex][second];
                     });
    Weight score = distance[vertex][order[vertex][0]] * Weight(2);
    if (score < best_vertex_score) {
      best_vertex_score = score;
      best_vertex = vertex;
    }
  }

  Weight best_edge_score = infinity;
  int best_edge = -1;
  int best_first = -1;
  int best_second = -1;
  Weight center_to_first{};
  Weight center_to_second{};
  for (int id = 0; id < int(edges.size()); id++) {
    auto [first, second, original_weight] = edges[id];
    if (first == second) {
      continue;
    }
    Weight weight = original_weight * Weight(2);
    int previous = order[first][0];
    for (int vertex : order[first]) {
      if (distance[second][vertex] > distance[second][previous]) {
        Weight score = distance[second][previous] +
                       distance[first][vertex] + weight;
        if (score < best_edge_score) {
          best_edge_score = score;
          best_edge = id;
          best_first = first;
          best_second = second;
          center_to_first = score / Weight(2) - distance[first][vertex];
          center_to_second = weight - center_to_first;
        }
        previous = vertex;
      }
    }
  }

  minimum_diameter_spanning_tree_result<Weight> result;
  if (best_edge_score < best_vertex_score) {
    int center = vertex_count;
    auto augmented = graph;
    augmented.emplace_back();
    augmented[center].push_back(
        {best_first, center_to_first, -1});
    augmented[center].push_back(
        {best_second, center_to_second, -1});
    auto tree = shortest_paths(center, augmented);
    result.diameter = best_edge_score / Weight(2);
    result.edge_ids.reserve(vertex_count - 1);
    for (int vertex = 0; vertex < vertex_count; vertex++) {
      if (vertex != best_first && vertex != best_second) {
        assert(tree.predecessor_edge[vertex] >= 0);
        result.edge_ids.push_back(tree.predecessor_edge[vertex]);
      }
    }
    result.edge_ids.push_back(best_edge);
  } else {
    auto tree = shortest_paths(best_vertex, graph);
    result.diameter = best_vertex_score / Weight(2);
    result.edge_ids.reserve(vertex_count - 1);
    for (int vertex = 0; vertex < vertex_count; vertex++) {
      if (vertex != best_vertex) {
        assert(tree.predecessor_edge[vertex] >= 0);
        result.edge_ids.push_back(tree.predecessor_edge[vertex]);
      }
    }
  }
  return result;
}

} // namespace noya