Skip to content

Commit e311615

Browse files
committed
Add outgoing(v) to graphs, don't store reverse edges in digraphs
1 parent 46a8a5d commit e311615

File tree

14 files changed

+288
-151
lines changed

14 files changed

+288
-151
lines changed

cp-algo/graph/base.hpp

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,86 @@
11
#ifndef CP_ALGO_GRAPH_BASE_HPP
22
#define CP_ALGO_GRAPH_BASE_HPP
33
#include "edge_types.hpp"
4+
#include "concepts.hpp"
45
#include "../structures/stack_union.hpp"
56
#include <ranges>
6-
#include <vector>
77
namespace cp_algo::graph {
88
using edge_index = int;
9-
enum type {directed = 0, undirected = 1};
10-
template<type _undirected, edge_type edge_t = edge_base>
9+
template<edge_type _edge_t = edge_base, graph_mode _mode = undirected>
1110
struct graph {
12-
static constexpr bool undirected = _undirected;
11+
using edge_t = _edge_t;
12+
static constexpr auto mode = _mode;
13+
using incidence_list = structures::stack_union<edge_index>;
1314
graph(int n, int v0 = 0): v0(v0), adj(n) {}
1415

1516
void add_edge(node_index u, edge_t e) {
16-
adj.push(u, (edge_index)size(edges));
17-
edges.push_back(e);
18-
if constexpr (undirected) {
19-
adj.push(e.to, (edge_index)size(edges));
17+
adj.push(u, (edge_index)size(E));
18+
E.push_back(e);
19+
if constexpr (mode == undirected) {
20+
adj.push(e.to, (edge_index)size(E));
21+
E.push_back(e.backedge(u));
2022
}
21-
edges.push_back(e.backedge(u));
23+
}
24+
void add_edge(node_index u, auto... Args) {
25+
add_edge(u, edge_t(Args...));
2226
}
2327
void read_edges(node_index m) {
24-
adj.reserve(m);
28+
adj.reserve(mode == undirected ? 2 * m : m);
2529
for(edge_index i = 0; i < m; i++) {
2630
auto [u, e] = edge_t::read(v0);
2731
add_edge(u, e);
2832
}
2933
}
30-
void call_adjacent(node_index v, auto &&callback, auto &&terminate) const {
31-
for(int sv = adj.head[v]; sv && !terminate(); sv = adj.next[sv]) {
32-
callback(adj.data[sv]);
33-
}
34+
auto outgoing(node_index v) const {
35+
return adj[v];
3436
}
35-
void call_adjacent(node_index v, auto &&callback) const {
36-
call_adjacent(v, callback, [](){return false;});
37+
auto nodes() const {
38+
return std::views::iota(node_index(0), n());
3739
}
38-
void call_edges(auto &&callback) const {
39-
for(edge_index e: edges_view()) {
40-
callback(e);
40+
41+
auto edges() const {
42+
if constexpr (mode == undirected) {
43+
return E | std::views::stride(2);
44+
} else {
45+
return E | std::views::all;
4146
}
4247
}
43-
auto nodes_view() const {
44-
return std::views::iota(0, n());
45-
}
46-
auto edges_view() const {
47-
return std::views::iota(0, 2 * m()) | std::views::stride(2);
48+
auto edge_indices() const {
49+
auto indices = std::views::iota(edge_index(0), edge_index(size(E)));
50+
if constexpr (mode == undirected) {
51+
return indices | std::views::stride(2);
52+
} else {
53+
return indices;
54+
}
4855
}
49-
auto const& incidence_lists() const {return adj;}
50-
edge_t const& edge(edge_index e) const {return edges[e];}
56+
incidence_list const& incidence_lists() const {return adj;}
57+
edge_t const& edge(edge_index e) const {return E[e];}
5158
node_index n() const {return (node_index)adj.size();}
52-
edge_index m() const {return (edge_index)size(edges) / 2;}
59+
edge_index m() const {
60+
return (edge_index)size(edges());
61+
}
62+
static edge_index canonical_idx(edge_index e) {
63+
if constexpr (mode == undirected) {
64+
return e / 2;
65+
} else {
66+
return e;
67+
}
68+
}
69+
static edge_index opposite_idx(edge_index e) {
70+
static_assert(mode == undirected, "opposite_index is only defined for undirected graphs");
71+
return e ^ 1;
72+
}
5373
private:
5474
node_index v0;
55-
std::vector<edge_t> edges;
56-
structures::stack_union<edge_index> adj;
75+
big_vector<edge_t> E;
76+
incidence_list adj;
5777
};
78+
// aliases for most standard cases
79+
template<edge_type edge_t = edge_base>
80+
using digraph = graph<edge_t, directed>;
81+
template<weighted_edge_type edge_t = weighted_edge, graph_mode mode = undirected>
82+
using weighted_graph = graph<edge_t, mode>;
83+
template<weighted_edge_type edge_t = weighted_edge>
84+
using weighted_digraph = digraph<edge_t>;
5885
}
5986
#endif // CP_ALGO_GRAPH_BASE_HPP

cp-algo/graph/concepts.hpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#ifndef CP_ALGO_GRAPH_CONCEPTS_HPP
2+
#define CP_ALGO_GRAPH_CONCEPTS_HPP
3+
#include "edge_types.hpp"
4+
#include <type_traits>
5+
6+
namespace cp_algo::graph {
7+
// Shared graph mode enum for all graph headers
8+
enum graph_mode { directed, undirected };
9+
// Traits: true for types that expose `edge_t` and static `mode`
10+
template<typename T, typename = void>
11+
struct graph_traits : std::false_type {};
12+
13+
template<typename T>
14+
struct graph_traits<T, std::void_t<typename T::edge_t, decltype(T::mode)>> : std::true_type {
15+
using edge_t = typename T::edge_t;
16+
static constexpr auto mode = T::mode;
17+
static constexpr bool is_directed = mode == directed;
18+
static constexpr bool is_undirected = mode == undirected;
19+
static constexpr bool is_weighted = weighted_edge_type<edge_t>;
20+
};
21+
22+
// Concepts
23+
template<typename G>
24+
concept graph_type = graph_traits<G>::value;
25+
26+
template<typename G>
27+
concept digraph_type = graph_type<G> && graph_traits<G>::is_directed;
28+
29+
template<typename G>
30+
concept undirected_graph_type = graph_type<G> && graph_traits<G>::is_undirected;
31+
32+
template<typename G>
33+
concept weighted_graph_type = graph_type<G> && graph_traits<G>::is_weighted;
34+
35+
template<typename G>
36+
concept weighted_digraph_type = digraph_type<G> && graph_traits<G>::is_weighted;
37+
38+
template<typename G>
39+
concept weighted_undirected_graph_type = undirected_graph_type<G> && graph_traits<G>::is_weighted;
40+
}
41+
#endif // CP_ALGO_GRAPH_CONCEPTS_HPP

cp-algo/graph/cycle.hpp

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,40 @@
22
#define CP_ALGO_GRAPH_CYCLE_HPP
33
#include "base.hpp"
44
#include <algorithm>
5-
#include <vector>
65
namespace cp_algo::graph {
7-
std::vector<int> find_cycle(auto const& g) {
8-
std::vector<char> state(g.n());
9-
std::vector<int> cycle;
10-
auto dfs = [&](auto &&self, int v, int pe) -> bool {
11-
state[v] = 1;
12-
bool found = false;
13-
g.call_adjacent(v, [&](int e) {
14-
if(e / 2 != pe / 2) {
15-
int u = g.edge(e).to;
16-
if(state[u] == 0) {
17-
if(self(self, u, e)) {
18-
if(g.edge(cycle[0]).to != g.edge(cycle.back() ^ 1).to) {
19-
cycle.push_back(e);
20-
}
21-
found = true;
6+
template<graph_type graph>
7+
std::vector<edge_index> find_cycle(graph const& g) {
8+
enum node_state { unvisited, visiting, visited };
9+
std::vector<node_state> state(g.n());
10+
std::vector<edge_index> cycle;
11+
bool closed = false;
12+
auto dfs = [&](this auto &&dfs, node_index v, edge_index ep = -1) -> bool {
13+
state[v] = visiting;
14+
for(auto e: g.outgoing(v)) {
15+
if constexpr (undirected_graph_type<graph>) {
16+
if (ep == graph::opposite_idx(e)) {
17+
continue;
18+
}
19+
}
20+
node_index u = g.edge(e).to;
21+
if(state[u] == unvisited) {
22+
if (dfs(u, e)) {
23+
if (!closed) {
24+
cycle.push_back(e);
25+
closed |= g.edge(cycle[0]).to == v;
2226
}
23-
} else if(state[u] == 1) {
24-
cycle = {e};
25-
found = true;
27+
return true;
2628
}
29+
} else if(state[u] == visiting) {
30+
cycle = {e};
31+
return true;
2732
}
28-
}, [&found](){return found;});
29-
state[v] = 2;
30-
return found;
33+
}
34+
state[v] = visited;
35+
return false;
3136
};
32-
for(int i: g.nodes_view()) {
33-
if(!state[i] && dfs(dfs, i, -2)) {
37+
for(node_index i: g.nodes()) {
38+
if(state[i] == unvisited && dfs(i)) {
3439
break;
3540
}
3641
}

cp-algo/graph/euler.hpp

Lines changed: 61 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,58 +2,79 @@
22
#define CP_ALGO_GRAPH_EULER_HPP
33
#include "base.hpp"
44
#include <algorithm>
5+
#include <iostream>
6+
#include <optional>
57
#include <utility>
68
#include <vector>
9+
#include <cassert>
710
namespace cp_algo::graph {
8-
template<typename graph>
9-
int euler_start(graph const& g) {
11+
template<graph_type graph>
12+
std::optional<node_index> euler_start(graph const& g) {
1013
std::vector<int> deg(g.n());
11-
constexpr bool undirected = graph::undirected;
12-
int res = 0;
13-
g.call_edges([&](edge_index e) {
14-
int u = g.edge(e ^ 1).to;
15-
int v = g.edge(e).to;
16-
res = u;
17-
deg[u]++;
18-
deg[v]--;
19-
});
20-
if constexpr (undirected) {
14+
std::optional<node_index> default_start = 0;
15+
for(auto v: g.nodes()) {
16+
for(auto e: g.outgoing(v)) {
17+
deg[v]++;
18+
default_start = v;
19+
if constexpr (digraph_type<graph>) {
20+
deg[g.edge(e).to]--;
21+
}
22+
}
23+
}
24+
if constexpr (undirected_graph_type<graph>) {
2125
for(auto &it: deg) {
22-
it = bool(it % 2);
26+
it %= 2;
2327
}
2428
}
25-
auto nodes = g.nodes_view();
2629
auto is_start = [&](int v) {return deg[v] > 0;};
27-
auto starts = std::ranges::count_if(nodes, is_start);
28-
if(starts > 1 + undirected) {
29-
res = -1;
30-
} else if(starts == 1 + undirected) {
31-
auto start = *std::ranges::find_if(nodes, is_start);
32-
res = deg[start] == 1 ? start : -1;
30+
auto starts = std::ranges::count_if(g.nodes(), is_start);
31+
auto need_starts = undirected_graph_type<graph> ? 2 : 1;
32+
if(starts > need_starts) {
33+
return std::nullopt;
34+
} else if(starts == need_starts) {
35+
auto start = *std::ranges::find_if(g.nodes(), is_start);
36+
if (deg[start] == 1) {
37+
return start;
38+
} else {
39+
return std::nullopt;
40+
}
3341
}
34-
return res;
42+
return default_start;
3543
}
36-
auto euler_trail(auto const& g) {
37-
int v0 = euler_start(g);
38-
std::vector<int> trail;
39-
if(~v0) {
40-
std::vector<bool> used(g.m());
41-
auto& adj = g.incidence_lists();
42-
auto head = adj.head;
43-
auto dfs = [&](auto &&self, int v) -> void {
44-
while(head[v]) {
45-
int e = adj.data[std::exchange(head[v], adj.next[head[v]])];
46-
if(!used[e / 2]) {
47-
used[e / 2] = 1;
48-
self(self, g.edge(e).to);
49-
trail.push_back(e);
50-
}
44+
// Try finding a trail starting from v0
45+
// may be partial if graph is not Eulerian or disconnected
46+
template<graph_type graph>
47+
std::vector<edge_index> try_euler_trail(graph const& g, node_index v0) {
48+
std::vector<edge_index> trail;
49+
std::vector<bool> used(g.m());
50+
auto head = g.nodes() | std::views::transform([&](auto v) {
51+
return begin(g.outgoing(v));
52+
}) | std::ranges::to<std::vector>();
53+
auto dfs = [&](this auto &&dfs, int v) -> void {
54+
while (head[v] != end(g.outgoing(v))) {
55+
auto e = *head[v]++;
56+
if(!used[graph::canonical_idx(e)]) {
57+
used[graph::canonical_idx(e)] = 1;
58+
dfs(g.edge(e).to);
59+
trail.push_back(e);
5160
}
52-
};
53-
dfs(dfs, v0);
54-
std::ranges::reverse(trail);
61+
}
62+
};
63+
dfs(v0);
64+
std::ranges::reverse(trail);
65+
return trail;
66+
}
67+
template<graph_type graph>
68+
std::optional<std::pair<node_index, std::vector<edge_index>>> euler_trail(graph const& g) {
69+
auto v0 = euler_start(g);
70+
if (!v0) {
71+
return std::nullopt;
72+
}
73+
auto result = try_euler_trail(g, *v0);
74+
if ((edge_index)result.size() != g.m()) {
75+
return std::nullopt;
5576
}
56-
return std::pair{v0, trail};
77+
return {{*v0, std::move(result)}};
5778
}
5879
}
5980
#endif // CP_ALGO_GRAPH_EULER_HPP

cp-algo/graph/mst.hpp

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,29 @@
22
#define CP_ALGO_GRAPH_MST_HPP
33
#include "base.hpp"
44
#include "../structures/dsu.hpp"
5+
#include "../util/sort.hpp"
56
#include <algorithm>
67
namespace cp_algo::graph {
7-
template<weighted_edge_type edge_t>
8-
auto mst(graph<undirected, edge_t> const& g) {
9-
std::vector<std::pair<int64_t, edge_index>> edges;
10-
g.call_edges([&](edge_index e) {
11-
edges.emplace_back(g.edge(e).w, e);
8+
template<weighted_undirected_graph_type graph>
9+
std::pair<int64_t, std::vector<edge_index>> mst(graph const& g) {
10+
struct edge {
11+
int64_t w;
12+
edge_index i;
13+
};
14+
auto edges = g.edge_indices() | std::ranges::to<std::vector>();
15+
radix_sort(edges, [&](auto e) {
16+
return g.edge(e).w;
1217
});
13-
std::ranges::sort(edges);
1418
structures::dsu me(g.n());
1519
int64_t total = 0;
1620
std::vector<edge_index> mst;
17-
for(auto [w, e]: edges) {
18-
if(me.uni(g.edge(e ^ 1).to, g.edge(e).to)) {
19-
total += w;
20-
mst.push_back(e);
21+
for(auto e: edges) {
22+
if(me.uni(g.edge(e).to, g.edge(graph::opposite_idx(e)).to)) {
23+
total += g.edge(e).w;
24+
mst.push_back(graph::canonical_idx(e));
2125
}
2226
}
23-
return std::pair{total, mst};
27+
return {total, mst};
2428
}
2529
}
2630
#endif // CP_ALGO_GRAPH_MST_HPP

0 commit comments

Comments
 (0)