Skip to content

Commit 9a6b926

Browse files
committed
Generic dfs in cycle detection
1 parent d73c54a commit 9a6b926

File tree

4 files changed

+60
-65
lines changed

4 files changed

+60
-65
lines changed

cp-algo/graph/cycle.hpp

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,38 @@
11
#ifndef CP_ALGO_GRAPH_CYCLE_HPP
22
#define CP_ALGO_GRAPH_CYCLE_HPP
3+
#include "dfs.hpp"
34
#include "base.hpp"
4-
#include <algorithm>
5+
#include <deque>
56
namespace cp_algo::graph {
67
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;
8+
struct cycle_context: dfs_context<graph> {
9+
using base = dfs_context<graph>;
10+
using base::base;
11+
std::deque<edge_index> cycle;
1112
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;
26-
}
27-
return true;
28-
}
29-
} else if(state[u] == visiting) {
30-
cycle = {e};
31-
return true;
32-
}
13+
int v0;
14+
15+
void on_return_from_child(node_index v, edge_index e) {
16+
if (!empty(cycle) && !closed) {
17+
cycle.push_front(e);
18+
closed |= v == v0;
3319
}
34-
state[v] = visited;
35-
return false;
36-
};
37-
for(node_index i: g.nodes()) {
38-
if(state[i] == unvisited && dfs(i)) {
39-
break;
20+
}
21+
22+
void on_back_edge(node_index v, edge_index e) {
23+
if (empty(cycle)) {
24+
v0 = base::g->edge(e).to;
25+
base::done = true;
26+
closed = v == v0;
27+
cycle.push_front(e);
4028
}
4129
}
42-
std::ranges::reverse(cycle);
43-
return cycle;
30+
};
31+
32+
template<graph_type graph>
33+
std::deque<edge_index> find_cycle(graph const& g) {
34+
auto context = dfs<cycle_context>(g);
35+
return context.cycle;
4436
}
4537
}
4638
#endif // CP_ALGO_GRAPH_CYCLE_HPP

cp-algo/graph/dfs.hpp

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,32 @@ namespace cp_algo::graph {
1010
struct dfs_context {
1111
big_vector<node_state> state;
1212
graph const* g;
13+
bool done = false; // Set to true to stop DFS early
1314

1415
dfs_context(graph const& g): state(g.n()), g(&g) {}
1516

1617
// Called when first entering a node
1718
void on_enter(node_index) {}
1819

1920
// Called when discovering a tree edge (v->u is a tree edge, u is unvisited)
20-
void on_tree_edge(node_index, node_index, edge_index) {}
21+
void on_tree_edge(node_index, edge_index) {}
2122

2223
// Called after returning from a child via tree edge
23-
void on_return_from_child(node_index, node_index, edge_index) {}
24+
void on_return_from_child(node_index, edge_index) {}
2425

2526
// Called when encountering a back edge (v->u, u is visiting)
26-
void on_back_edge(node_index, node_index, edge_index) {}
27+
void on_back_edge(node_index, edge_index) {}
2728

2829
// Called when encountering a forward/cross edge (v->u, u is visited)
29-
void on_forward_cross_edge(node_index, node_index, edge_index) {}
30+
void on_forward_cross_edge(node_index, edge_index) {}
3031

3132
// Called when exiting a node (all edges processed)
3233
void on_exit(node_index) {}
3334
};
3435

3536
template<template<typename> class Context, graph_type graph>
36-
Context<graph>& dfs(Context<graph>& context) {
37-
graph const& g = *context.g;
37+
Context<graph> dfs(graph const& g) {
38+
Context<graph> context(g);
3839
auto const& adj = g.incidence_lists();
3940
struct frame {
4041
node_index v;
@@ -48,6 +49,7 @@ namespace cp_algo::graph {
4849
std::stack<frame> dfs_stack;
4950

5051
for (auto root: g.nodes()) {
52+
if (context.done) break;
5153
if (context.state[root] != unvisited) continue;
5254

5355
if constexpr (undirected_graph_type<graph>) {
@@ -56,27 +58,28 @@ namespace cp_algo::graph {
5658
dfs_stack.push({root, {}, 0, frame::INIT});
5759
}
5860

59-
while (!dfs_stack.empty()) {
61+
while (!empty(dfs_stack)) {
6062
auto& f = dfs_stack.top();
6163

6264
if (f.state == frame::INIT) {
6365
context.state[f.v] = visiting;
6466
context.on_enter(f.v);
6567
f.sv = adj.head[f.v];
6668
f.state = frame::PROCESS_EDGES;
69+
continue;
6770
}
6871

6972
if (f.state == frame::HANDLE_CHILD) {
7073
auto e = adj.data[f.sv];
7174
f.sv = adj.next[f.sv];
72-
node_index u = g.edge(e).to;
73-
context.on_return_from_child(f.v, u, e);
75+
context.on_return_from_child(f.v, e);
7476
f.state = frame::PROCESS_EDGES;
77+
continue;
7578
}
76-
79+
7780
// PROCESS_EDGES
7881
bool found_child = false;
79-
while (f.sv != 0) {
82+
while (f.sv != 0 && !context.done) {
8083
auto e = adj.data[f.sv];
8184

8285
if constexpr (undirected_graph_type<graph>) {
@@ -88,7 +91,7 @@ namespace cp_algo::graph {
8891

8992
node_index u = g.edge(e).to;
9093
if (context.state[u] == unvisited) {
91-
context.on_tree_edge(f.v, u, e);
94+
context.on_tree_edge(f.v, e);
9295
f.state = frame::HANDLE_CHILD;
9396
if constexpr (undirected_graph_type<graph>) {
9497
dfs_stack.push({u, e, 0, frame::INIT});
@@ -98,9 +101,9 @@ namespace cp_algo::graph {
98101
found_child = true;
99102
break;
100103
} else if (context.state[u] == visiting) {
101-
context.on_back_edge(f.v, u, e);
104+
context.on_back_edge(f.v, e);
102105
} else if (context.state[u] == visited) {
103-
context.on_forward_cross_edge(f.v, u, e);
106+
context.on_forward_cross_edge(f.v, e);
104107
}
105108
f.sv = adj.next[f.sv];
106109
}

cp-algo/graph/tarjan.hpp

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@
99
namespace cp_algo::graph {
1010
template<graph_type graph>
1111
struct tarjan_context: dfs_context<graph> {
12+
using base = dfs_context<graph>;
1213
big_vector<int> tin, low;
1314
std::stack<int> stack;
1415
int timer;
1516
structures::csr<node_index> components;
1617

17-
tarjan_context(graph const& g): dfs_context<graph>(g),
18+
tarjan_context(graph const& g): base(g),
1819
tin(g.n()), low(g.n()), timer(0) {
1920
components.reserve_data(g.n());
2021
}
@@ -24,15 +25,18 @@ namespace cp_algo::graph {
2425
stack.push(v);
2526
}
2627

27-
void on_return_from_child(node_index v, node_index u, edge_index) {
28+
void on_return_from_child(node_index v, edge_index e) {
29+
node_index u = base::g->edge(e).to;
2830
low[v] = std::min(low[v], low[u]);
2931
}
3032

31-
void on_back_edge(node_index v, node_index u, edge_index) {
33+
void on_back_edge(node_index v, edge_index e) {
34+
node_index u = base::g->edge(e).to;
3235
low[v] = std::min(low[v], tin[u]);
3336
}
3437

35-
void on_forward_cross_edge(node_index v, node_index u, edge_index) {
38+
void on_forward_cross_edge(node_index v, edge_index e) {
39+
node_index u = base::g->edge(e).to;
3640
low[v] = std::min(low[v], tin[u]);
3741
}
3842

@@ -44,17 +48,11 @@ namespace cp_algo::graph {
4448
do {
4549
u = stack.top();
4650
stack.pop();
47-
this->state[u] = blocked;
51+
base::state[u] = blocked;
4852
components.push(u);
4953
} while(u != v);
5054
}
5155
};
52-
53-
template<template<typename> class Context, graph_type graph>
54-
auto tarjan(graph const& g) {
55-
Context<graph> context(g);
56-
return dfs(context).components;
57-
}
5856
template<graph_type graph>
5957
struct exit_context: tarjan_context<graph> {
6058
using tarjan_context<graph>::tarjan_context;
@@ -69,22 +67,23 @@ namespace cp_algo::graph {
6967
// returns components in reverse topological order
7068
template<digraph_type graph>
7169
auto strongly_connected_components(graph const& g) {
72-
return tarjan<exit_context>(g);
70+
return dfs<exit_context>(g).components;
7371
}
7472

7573
// Tarjan's algorithm for Two-Edge-Connected Components
7674
template<undirected_graph_type graph>
7775
auto two_edge_connected_components(graph const& g) {
78-
return tarjan<exit_context>(g);
76+
return dfs<exit_context>(g).components;
7977
}
8078

8179
template<undirected_graph_type graph>
8280
struct bcc_context: tarjan_context<graph> {
8381
using base = tarjan_context<graph>;
8482
using base::base;
8583

86-
void on_return_from_child(node_index v, node_index u, edge_index e) {
87-
base::on_return_from_child(v, u, e);
84+
void on_return_from_child(node_index v, edge_index e) {
85+
base::on_return_from_child(v, e);
86+
node_index u = base::g->edge(e).to;
8887
if (base::low[u] >= base::tin[v]) {
8988
base::collect(u);
9089
base::components.push(v);
@@ -99,7 +98,7 @@ namespace cp_algo::graph {
9998
// Tarjan's algorithm for Biconnected Components (vertex-biconnected)
10099
template<undirected_graph_type graph>
101100
auto biconnected_components(graph const& g) {
102-
return tarjan<bcc_context>(g);
101+
return dfs<bcc_context>(g).components;
103102
}
104103
}
105104
#endif // CP_ALGO_GRAPH_TARJAN_HPP

verify/graph/cycle_undirected.test.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ void solve() {
1616
if(empty(res)) {
1717
cout << -1 << "\n";
1818
} else {
19-
ranges::reverse(res);
2019
cout << size(res) << "\n";
20+
ranges::rotate(res, prev(end(res)));
2121
for(auto it: res) {cout << g.edge(it).to << ' ';}
2222
cout << "\n";
23+
ranges::rotate(res, next(begin(res)));
2324
for(auto it: res) {cout << graph<>::canonical_idx(it) << ' ';}
2425
cout << "\n";
2526
}

0 commit comments

Comments
 (0)