-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfindloop.c
More file actions
206 lines (190 loc) · 6.91 KB
/
findloop.c
File metadata and controls
206 lines (190 loc) · 6.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/*
* Routine for finding loops in graphs, reusable across multiple
* puzzles.
*
* The strategy is Tarjan's bridge-finding algorithm, which is
* designed to list all edges whose removal would disconnect a
* previously connected component of the graph. We're interested in
* exactly the reverse - edges that are part of a loop in the graph
* are precisely those which _wouldn't_ disconnect anything if removed
* (individually) - but of course flipping the sense of the output is
* easy.
*
* For some fun background reading about all the _wrong_ ways the
* Puzzles code base has tried to solve this problem in the past:
* https://www.chiark.greenend.org.uk/~sgtatham/quasiblog/findloop/
*
* The specific variant of Tarjan's algorithm we use is the one from
* https://mathstodon.xyz/@abacabadabacaba@infosec.exchange/113113280480134188
*/
#include "puzzles.h"
struct findloopstate {
int depth, shallowest_reachable, subtree_size;
int parent, component_root;
int prev, next;
};
struct findloopstate *findloop_new_state(int nvertices)
{
return snewn(nvertices, struct findloopstate);
}
void findloop_free_state(struct findloopstate *state)
{
sfree(state);
}
bool findloop_is_loop_edge(struct findloopstate *pv, int u, int v)
{
/*
* In the DFS-built forest, all edges are either are from parent
* to child or from child to ancestor.
*
* Back-edges to ancestors must be parts of loops. In order to
* detect whether a parent-to-child edge is part of a loop, we
* check if any ancestor is reachable from that child's subtree.
*/
if (pv[u].parent == v && pv[u].shallowest_reachable >= pv[u].depth)
return false;
if (pv[v].parent == u && pv[v].shallowest_reachable >= pv[v].depth)
return false;
return true;
}
static bool findloop_is_bridge_oneway(
struct findloopstate *pv, int u, int v, int *u_vertices, int *v_vertices)
{
if (pv[u].parent != v)
return false;
if (pv[u].shallowest_reachable < pv[u].depth)
return false;
if (u_vertices)
*u_vertices = pv[u].subtree_size;
if (v_vertices) {
int r = pv[u].component_root;
*v_vertices = pv[r].subtree_size - pv[u].subtree_size;
}
return true;
}
bool findloop_is_bridge(
struct findloopstate *pv, int u, int v, int *u_vertices, int *v_vertices)
{
return (findloop_is_bridge_oneway(pv, u, v, u_vertices, v_vertices) ||
findloop_is_bridge_oneway(pv, v, u, v_vertices, u_vertices));
}
bool findloop_run(struct findloopstate *pv, int nvertices,
neighbour_fn_t neighbour, void *ctx)
{
int u, v, w;
bool any_loop = false;
/*
* We run a DFS to split the graph into disjoint spanning trees.
* This construction guarantees that every edge either descends
* to a new child never visited before or goes to an ancestor.
* Tracking which ancestors are linked from which subtrees lets
* us detect all loops efficiently.
*
* Here we don't use recursion, instead holding the entire DSF
* state in the findloopstate struct. The loop below visits each
* node exactly twice: before and after visiting its subtree.
*
* The first time we visit a node, we take care of marking its
* children with their position in the tree and when they're
* scheduled to be visited. The second time we update the parent
* with statistics about the subtree.
*
* The order of nodes is managed using a doubly-linked list.
* The first time a node is visited, we add its children before it
* in the list and set the pointer to go through them first. The
* second time we move to the next node in the list, which is a
* sibling or a parent, or if we're at the root of the connected
* component will be a node in the next component. (All the nodes
* of the graph always appear in the list)
* A linked list is used to handle the case where the same child
* appears in two levels, to allow us to efficiently remove it from
* its previous position.
*
* The algorithm tracks whether we're at the first or the second
* visit at a node using the depth property. It's set to a negative
* value on initialization, and to the depth in the connected
* component's tree on the first visit.
* It detects a new connected component using the parent pointer,
* it's always set to a real node in the search, and is negative for
* new trees.
*
* In the first visit, we go over the node's children, moving them
* in the list and setting their parent pointer. Edges going to
* ancestors are noted in the shallowest_reachable field.
* In the second visit, we adjust the subtree_size and
* shallowest_reachable fields of the parent.
*
* Variables:
* u = the current node under examination
* v = the node to go to in the next iteration
* w = neighbour iterator
*/
for (u = 0; u < nvertices; u++) {
pv[u].depth = -1;
pv[u].shallowest_reachable = nvertices;
pv[u].subtree_size = 1;
pv[u].parent = -1;
pv[u].component_root = u;
pv[u].prev = u - 1;
pv[u].next = (u == nvertices - 1) ? -1 : u + 1;
}
debug(("------------- new find_loops, nvertices=%d\n", nvertices));
v = 0;
while (v != -1) {
u = v;
if (pv[u].depth < 0) {
/* Our first visit to the node (on the way down the search) */
if (pv[u].parent < 0) {
debug((" new component: processing %d\n", u));
pv[u].depth = 0;
pv[u].component_root = u;
} else {
debug((" processing %d\n", u));
pv[u].depth = pv[pv[u].parent].depth + 1;
pv[u].component_root = pv[pv[u].parent].component_root;
}
/* Schedule visits to the neighbors, and then back here */
v = u;
for (w = neighbour(u, ctx); w >= 0; w = neighbour(-1, ctx)) {
if (w == pv[u].parent)
continue;
if (pv[w].depth < 0) {
debug((" adding edge %d-%d to tree\n", u, w));
pv[w].parent = u;
/* Remove the neighbour from the linked list */
if (pv[w].prev >= 0)
pv[pv[w].prev].next = pv[w].next;
if (pv[w].next >= 0)
pv[pv[w].next].prev = pv[w].prev;
/* Add it to the start of the list */
pv[w].prev = pv[v].prev;
pv[w].next = v;
if (pv[v].prev >= 0)
pv[pv[v].prev].next = w;
pv[v].prev = w;
/* Mark this as the next node to visit */
v = w;
} else {
debug((" found back-edge %d-%d\n", u, w));
pv[u].shallowest_reachable =
min(pv[u].shallowest_reachable, pv[w].depth);
any_loop = true;
}
}
} else {
debug((" wrapping up %d. |subtree| = %d, min(reachable) = %d\n",
u, pv[u].subtree_size, pv[u].shallowest_reachable));
if (pv[u].parent >= 0) {
if (pv[u].shallowest_reachable >= pv[u].depth) {
debug((" bridge: %d-%d\n", u, pv[u].parent));
}
pv[pv[u].parent].subtree_size += pv[u].subtree_size;
pv[pv[u].parent].shallowest_reachable =
min(pv[pv[u].parent].shallowest_reachable,
pv[u].shallowest_reachable);
}
v = pv[u].next;
}
}
return any_loop;
}