Skip to content

euler_walk.hpp

SECTIONGraph INCLUDEnoya/euler_walk.hpp

Find an Euler trail in an undirected multigraph, or nullopt if none exists.

在图中构造恰好经过每条边一次的欧拉游走,并返回顶点与边顺序。

Implementation

View on GitHub

#ifndef NOYA_EULER_WALK_HPP
#define NOYA_EULER_WALK_HPP 1

/// @complexity Time: O(V + E).
/// Space: O(V + E).

#include <algorithm>
#include <cassert>
#include <optional>
#include <utility>
#include <vector>

namespace noya {

struct euler_walk_result {
  std::vector<int> vertices;
  std::vector<int> edge_ids;
};

namespace euler_walk_internal {

inline euler_walk_result
hierholzer(const std::vector<std::vector<std::pair<int, int>>> &graph,
           int edge_count, int start) {
  std::vector<int> next_edge(graph.size());
  std::vector<bool> used(edge_count);
  std::vector<std::pair<int, int>> stack = {{start, -1}};
  euler_walk_result reversed;
  while (!stack.empty()) {
    int u = stack.back().first;
    while (next_edge[u] < int(graph[u].size()) &&
           used[graph[u][next_edge[u]].second]) {
      next_edge[u]++;
    }
    if (next_edge[u] == int(graph[u].size())) {
      auto [vertex, incoming_edge] = stack.back();
      stack.pop_back();
      reversed.vertices.push_back(vertex);
      if (incoming_edge != -1) {
        reversed.edge_ids.push_back(incoming_edge);
      }
      continue;
    }
    auto [v, edge_id] = graph[u][next_edge[u]++];
    if (!used[edge_id]) {
      used[edge_id] = true;
      stack.emplace_back(v, edge_id);
    }
  }
  std::reverse(reversed.vertices.begin(), reversed.vertices.end());
  std::reverse(reversed.edge_ids.begin(), reversed.edge_ids.end());
  return reversed;
}

} // namespace euler_walk_internal

/// @brief Find an Euler trail in an undirected multigraph, or nullopt if none
/// exists.
inline std::optional<euler_walk_result>
euler_walk_undirected(int n, const std::vector<std::pair<int, int>> &edges,
                      int start = -1) {
  assert(n >= 0);
  if (n == 0) {
    return edges.empty() && start == -1
               ? std::optional<euler_walk_result>(euler_walk_result{})
               : std::nullopt;
  }
  if (start < -1 || start >= n) {
    return std::nullopt;
  }
  std::vector<std::vector<std::pair<int, int>>> graph(n);
  std::vector<int> degree(n);
  for (int id = 0; id < int(edges.size()); id++) {
    auto [u, v] = edges[id];
    assert(0 <= u && u < n);
    assert(0 <= v && v < n);
    graph[u].emplace_back(v, id);
    graph[v].emplace_back(u, id);
    degree[u]++;
    degree[v]++;
  }
  std::vector<int> odd;
  for (int u = 0; u < n; u++) {
    if (degree[u] & 1) {
      odd.push_back(u);
    }
  }
  if (odd.size() != 0 && odd.size() != 2) {
    return std::nullopt;
  }
  if (edges.empty()) {
    int chosen = start == -1 ? 0 : start;
    return euler_walk_result{{chosen}, {}};
  }
  if (start == -1) {
    start = odd.empty()
                ? int(std::find_if(degree.begin(), degree.end(),
                                   [](int value) { return value > 0; }) -
                      degree.begin())
                : odd[0];
  } else if ((!odd.empty() && degree[start] % 2 == 0) || degree[start] == 0) {
    return std::nullopt;
  }
  euler_walk_result result =
      euler_walk_internal::hierholzer(graph, int(edges.size()), start);
  if (result.edge_ids.size() != edges.size()) {
    return std::nullopt;
  }
  return result;
}

/// @brief Find an Euler trail in a directed multigraph, or nullopt if none
/// exists.
inline std::optional<euler_walk_result>
euler_walk_directed(int n, const std::vector<std::pair<int, int>> &edges,
                    int start = -1) {
  assert(n >= 0);
  if (n == 0) {
    return edges.empty() && start == -1
               ? std::optional<euler_walk_result>(euler_walk_result{})
               : std::nullopt;
  }
  if (start < -1 || start >= n) {
    return std::nullopt;
  }
  std::vector<std::vector<std::pair<int, int>>> graph(n);
  std::vector<int> in_degree(n), out_degree(n);
  for (int id = 0; id < int(edges.size()); id++) {
    auto [u, v] = edges[id];
    assert(0 <= u && u < n);
    assert(0 <= v && v < n);
    graph[u].emplace_back(v, id);
    out_degree[u]++;
    in_degree[v]++;
  }
  int required_start = -1;
  int required_end = -1;
  for (int u = 0; u < n; u++) {
    int difference = out_degree[u] - in_degree[u];
    if (difference == 1 && required_start == -1) {
      required_start = u;
    } else if (difference == -1 && required_end == -1) {
      required_end = u;
    } else if (difference != 0) {
      return std::nullopt;
    }
  }
  if ((required_start == -1) != (required_end == -1)) {
    return std::nullopt;
  }
  if (edges.empty()) {
    int chosen = start == -1 ? 0 : start;
    return euler_walk_result{{chosen}, {}};
  }
  if (start == -1) {
    start = required_start;
    if (start == -1) {
      start = int(std::find_if(out_degree.begin(), out_degree.end(),
                               [](int value) { return value > 0; }) -
                  out_degree.begin());
    }
  } else if ((required_start != -1 && start != required_start) ||
             out_degree[start] == 0) {
    return std::nullopt;
  }
  euler_walk_result result =
      euler_walk_internal::hierholzer(graph, int(edges.size()), start);
  if (result.edge_ids.size() != edges.size()) {
    return std::nullopt;
  }
  return result;
}

} // namespace noya

#endif // NOYA_EULER_WALK_HPP
#include <algorithm>
#include <cassert>
#include <optional>
#include <utility>
#include <vector>

/// @complexity Time: O(V + E).
/// Space: O(V + E).

namespace noya {

struct euler_walk_result {
  std::vector<int> vertices;
  std::vector<int> edge_ids;
};

namespace euler_walk_internal {

inline euler_walk_result
hierholzer(const std::vector<std::vector<std::pair<int, int>>> &graph,
           int edge_count, int start) {
  std::vector<int> next_edge(graph.size());
  std::vector<bool> used(edge_count);
  std::vector<std::pair<int, int>> stack = {{start, -1}};
  euler_walk_result reversed;
  while (!stack.empty()) {
    int u = stack.back().first;
    while (next_edge[u] < int(graph[u].size()) &&
           used[graph[u][next_edge[u]].second]) {
      next_edge[u]++;
    }
    if (next_edge[u] == int(graph[u].size())) {
      auto [vertex, incoming_edge] = stack.back();
      stack.pop_back();
      reversed.vertices.push_back(vertex);
      if (incoming_edge != -1) {
        reversed.edge_ids.push_back(incoming_edge);
      }
      continue;
    }
    auto [v, edge_id] = graph[u][next_edge[u]++];
    if (!used[edge_id]) {
      used[edge_id] = true;
      stack.emplace_back(v, edge_id);
    }
  }
  std::reverse(reversed.vertices.begin(), reversed.vertices.end());
  std::reverse(reversed.edge_ids.begin(), reversed.edge_ids.end());
  return reversed;
}

} // namespace euler_walk_internal

/// @brief Find an Euler trail in an undirected multigraph, or nullopt if none
/// exists.
inline std::optional<euler_walk_result>
euler_walk_undirected(int n, const std::vector<std::pair<int, int>> &edges,
                      int start = -1) {
  assert(n >= 0);
  if (n == 0) {
    return edges.empty() && start == -1
               ? std::optional<euler_walk_result>(euler_walk_result{})
               : std::nullopt;
  }
  if (start < -1 || start >= n) {
    return std::nullopt;
  }
  std::vector<std::vector<std::pair<int, int>>> graph(n);
  std::vector<int> degree(n);
  for (int id = 0; id < int(edges.size()); id++) {
    auto [u, v] = edges[id];
    assert(0 <= u && u < n);
    assert(0 <= v && v < n);
    graph[u].emplace_back(v, id);
    graph[v].emplace_back(u, id);
    degree[u]++;
    degree[v]++;
  }
  std::vector<int> odd;
  for (int u = 0; u < n; u++) {
    if (degree[u] & 1) {
      odd.push_back(u);
    }
  }
  if (odd.size() != 0 && odd.size() != 2) {
    return std::nullopt;
  }
  if (edges.empty()) {
    int chosen = start == -1 ? 0 : start;
    return euler_walk_result{{chosen}, {}};
  }
  if (start == -1) {
    start = odd.empty()
                ? int(std::find_if(degree.begin(), degree.end(),
                                   [](int value) { return value > 0; }) -
                      degree.begin())
                : odd[0];
  } else if ((!odd.empty() && degree[start] % 2 == 0) || degree[start] == 0) {
    return std::nullopt;
  }
  euler_walk_result result =
      euler_walk_internal::hierholzer(graph, int(edges.size()), start);
  if (result.edge_ids.size() != edges.size()) {
    return std::nullopt;
  }
  return result;
}

/// @brief Find an Euler trail in a directed multigraph, or nullopt if none
/// exists.
inline std::optional<euler_walk_result>
euler_walk_directed(int n, const std::vector<std::pair<int, int>> &edges,
                    int start = -1) {
  assert(n >= 0);
  if (n == 0) {
    return edges.empty() && start == -1
               ? std::optional<euler_walk_result>(euler_walk_result{})
               : std::nullopt;
  }
  if (start < -1 || start >= n) {
    return std::nullopt;
  }
  std::vector<std::vector<std::pair<int, int>>> graph(n);
  std::vector<int> in_degree(n), out_degree(n);
  for (int id = 0; id < int(edges.size()); id++) {
    auto [u, v] = edges[id];
    assert(0 <= u && u < n);
    assert(0 <= v && v < n);
    graph[u].emplace_back(v, id);
    out_degree[u]++;
    in_degree[v]++;
  }
  int required_start = -1;
  int required_end = -1;
  for (int u = 0; u < n; u++) {
    int difference = out_degree[u] - in_degree[u];
    if (difference == 1 && required_start == -1) {
      required_start = u;
    } else if (difference == -1 && required_end == -1) {
      required_end = u;
    } else if (difference != 0) {
      return std::nullopt;
    }
  }
  if ((required_start == -1) != (required_end == -1)) {
    return std::nullopt;
  }
  if (edges.empty()) {
    int chosen = start == -1 ? 0 : start;
    return euler_walk_result{{chosen}, {}};
  }
  if (start == -1) {
    start = required_start;
    if (start == -1) {
      start = int(std::find_if(out_degree.begin(), out_degree.end(),
                               [](int value) { return value > 0; }) -
                  out_degree.begin());
    }
  } else if ((required_start != -1 && start != required_start) ||
             out_degree[start] == 0) {
    return std::nullopt;
  }
  euler_walk_result result =
      euler_walk_internal::hierholzer(graph, int(edges.size()), start);
  if (result.edge_ids.size() != edges.size()) {
    return std::nullopt;
  }
  return result;
}

} // namespace noya