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