def find_two_vertex_disjoint_paths(s, t, y, z, graph, DEBUG=DO_DEBUG): # finds 2 vertex disjoint paths in a graph. One from s to t. The other from y to z S_to_T_bfs = bfs_with_restriction_set(graph=graph, start=s, end=t, restriction_set=set()) if (S_to_T_bfs["result"] == False): if (DEBUG): print("FAILED ON FIRST BFS. from s -> t", (s, t)) return {"result": False} S_to_T_bfs_path = get_path_to_root(S_to_T_bfs["parents"], X)[::-1] # Do second dfs if DEBUG: print("S_to_X_bfs_path_found: ", S_to_T_bfs_path) Y_to_Z_dfs = dfs_with_restriction_set(graph=graph, start=y, end=z, avoidance_set=set(S_to_T_bfs_path), restriction_set=set()) if (Y_to_Z_dfs["result"] == True): if (DEBUG): print("IT WAS MERGED WITHIN FIRST BFS AND DFS") return { "result": True, "S_to_X_path": S_to_T_bfs_path, "Y_to_Z_path": get_path_to_root(Y_to_Z_dfs["parents"], z)[::-1] } '''
def brute_force_get_all_solution(graph, s, t, get_shortest_paths_too=False, DEBUG=False): graphBFS = bfs(graph, s) bfs_parents_root_s = graphBFS["parents"] bfs_dist_root_s = graphBFS["dist"] shortest_length = bfs_dist_root_s.get(t) if (shortest_length is None): # There is no path from s to t, FAIL! if (DEBUG): print("Hi brute force here. THERE IS NO SHORTEST PATH. FAIL.") return {"result": False, "shortest_paths": [], "longer_paths": []} a_shortest_path = get_path_to_root(bfs_parents_root_s, t)[::-1] ''' use dfs to find longer path! start at s, ''' if (get_shortest_paths_too): shortest_length = 0 result = find_longer_path(graph, s, t, shortest_length) return { "shortest_path": a_shortest_path, "longer_paths": result["longer_paths"] }
def brute_force_solution(graph, s, t, DEBUG=False): graphBFS = bfs(graph, s) bfs_parents_root_s = graphBFS["parents"] bfs_dist_root_s = graphBFS["dist"] shortest_length = bfs_dist_root_s.get(t) if (shortest_length is None): # There is no path from s to t, FAIL! if (DEBUG): print("Hi brute force here. THERE IS NO SHORTEST PATH. FAIL.") return { "result": False, } a_shortest_path = get_path_to_root(bfs_parents_root_s, t)[::-1] ''' use dfs to find longer path! start at s, ''' result = find_longer_path(graph, s, t, shortest_length) if (result["found_longer_path"]): if (DEBUG): print("FOUND A SHORTER AND LONGER SIMPLE PATH") if (DEBUG): print("THE SHORTEst PATH IS THE FOLLOWING: ", a_shortest_path) if (DEBUG): print("the longer path is the following: ", result["a_longer_path"]) return { "result": True, "a_shortest_path": a_shortest_path, "a_longer_path": result["a_longer_path"] } else: if (DEBUG): print("DID NOT FIND 2 SIMPLE PATHS") return { "result": False, }
def merge_two_overlapping_paths_in_dag(s, t, X, Y, shortest_paths_dag, DEBUG=DO_DEBUG): ''' PSUEDOCODE FOR THE 4 DFS's in this function numbered 1 to 4: 1) DFS from S->X on shortestPathsDAG and get a path, and store visited vertices in visitedX. if S->X DFS see's vertex Y, give up on path, continue dfs search with other nodes. 1a) If DFS fails, and we can't avoid Y, then return False. 1b) We found a path. Go to Step 2 2) Then DFS from Y->T on shortestPathsDAG without visiting the vertices from from visitedX or X. 2a) If T is reached, we are done. Return True. 2b) Otherwise T was not reached. Go to Step 3 3) Lift restriction that Y->T path can't use visitedX, and try to use visitedX vertices to get to T (this DFS however, still cant visit X). Save visited vertices to visitedY, including the ones we see when we lift the restriction to not use visitedX. 3a) If T still cant be reached because Y->T path still sees X, then FAIL PATH CREATION. Return false. 3b) If T is reached. Go To Step 4 4) We need to do DFS 1 final time on S->X and attempt to get path, without seeing Y or visitedY. Store the dfs vertices in visitedX2. 4a) S->X worked. Then Done. Return True. 4b) S->X did not work. Return false. The piece of the path we gave up to Y->T was the only segment we could use to go from S->X without seeing Y or its vertices. Y->T needed this segment too, otherwise, the previous DFS would have suggested another way for Y to reach T. Fail Path Creation due to contention for this piece of critical segment in the DAG that both S->X and Y->T needed. ''' S_to_X_bfs = bfs_with_restriction_set(graph=shortest_paths_dag, start=s, end=X, restriction_set=set([Y, t])) if (S_to_X_bfs["result"] == False): if (DEBUG): print("FAILED ON FIRST DFS. from s -> x", (s, X)) return {"result": False} ''' a_longer_path = get_path_to_root(longer_path_result["S_to_X_dfs_tree"], x)[::-1] + \ X_to_Z_to_Y_path["crazy_path"][1:] + \ get_path_to_root(longer_path_result["Y_to_T_dfs_tree"], t)[::-1][1:] ''' S_to_X_path = get_path_to_root(S_to_X_bfs["parents"], X)[::-1] # Do second dfs if DEBUG: print("S_to_X_path_found: ", S_to_X_path) Y_to_T_dfs = dfs_with_restriction_set(graph=shortest_paths_dag, start=Y, end=t, restriction_set=set(S_to_X_path)) if (Y_to_T_dfs["result"] == True): if (DEBUG): print("IT WAS MERGED WITHIN 2 DFS'S") return { "result": True, "S_to_X_path": S_to_X_path, "Y_to_T_path": get_path_to_root(Y_to_T_dfs["parents"], t)[::-1] } # The second dfs failed, so move on to the third dfs: if DEBUG: print("SECOND DFS FAILED move on to third. second was y->T", (Y, t)) Y_to_T_bfs_2 = bfs_with_restriction_set(graph=shortest_paths_dag, start=Y, end=t, restriction_set=set([X, s])) if (Y_to_T_bfs_2["result"] == False): if (DEBUG): print("FAILED ON THIRD DFS. Y->T", (Y, t)) return {"result": False} Y_to_T_path_2 = get_path_to_root(Y_to_T_bfs_2["parents"], t)[::-1] # do fourth dfs (IMPLEMENT AVOIDANCE HERE!!!!!) if (DEBUG): print("Y TO T 2 PATH ", Y_to_T_path_2) S_to_X_dfs_2 = dfs_with_restriction_set(graph=shortest_paths_dag, start=s, end=X, restriction_set=set(Y_to_T_path_2)) if (S_to_X_dfs_2["result"] == True): if (DEBUG): print("IT WAS MERGED WITHIN 4 DFS'S") return { "result": True, "S_to_X_path": get_path_to_root(S_to_X_dfs_2["parents"], X)[::-1], "Y_to_T_path": Y_to_T_path_2 } else: if (DEBUG): print("FAILED ON FOURTH DFS") return {"result": False}
def create_crazy_path_without_overlaps(X, Y, Z, X_to_Z_bfs_tree_parents, Z_to_Y_bfs_tree_parents, graph, shortest_paths_dag_vertices, DEBUG=DO_DEBUG): # BFS from X to Z to get shortest path (visits least numebr of vertices) # DFS from Z to Y (if this fails, the shortest path from X to Z had important nodes) # If it intersects, then do the following: # BFS from Z to Y # Then DFS from X to Z if DEBUG: print( "CALLLED CREATE CRAZY PATH WITHOUT OVERLAPPING! #########################################3" ) if DEBUG: print("X is " + str(X) + " Y is " + str(Y) + " Z is " + str(Z)) shortest_path_x_to_z = get_path_to_root(X_to_Z_bfs_tree_parents, X) if (DEBUG): print("SHORTEST PATH FROM X TO Z IS THE FOLLOWING ", shortest_path_x_to_z) dfs_from_z_to_y_restriction_set = set(shortest_path_x_to_z + shortest_paths_dag_vertices) - set( [Z, Y]) dfs_path_from_z_to_y = dfs_with_restriction_set( graph=graph, start=Z, end=Y, restriction_set=dfs_from_z_to_y_restriction_set) if (DEBUG): print("dfs path from z to y crazy path", dfs_path_from_z_to_y) if (dfs_path_from_z_to_y["result"]): return { "result": True, "crazy_path": shortest_path_x_to_z[:-1] + # remove z from end get_path_to_root(dfs_path_from_z_to_y["parents"], Y)[::-1] } if (DEBUG): print("CRAZY PATH BUILD FAILED ON FIRST ATTEMPT. SECOND ATTEMPT") shortest_path_z_to_y = get_path_to_root(Z_to_Y_bfs_tree_parents, Y) if (DEBUG): print("SHORTEST PATH FROM Z TO Y IS THE FOLLOWING ", shortest_path_z_to_y) dfs_path_from_x_to_z = dfs_with_restriction_set( graph=graph, start=X, end=Z, restriction_set=( set(shortest_path_z_to_y + shortest_paths_dag_vertices) - set([X, Z]))) if (DEBUG): print("dfs path from x to z crazy path", dfs_path_from_x_to_z) if (dfs_path_from_x_to_z["result"]): return { "result": True, "crazy_path": get_path_to_root(dfs_path_from_x_to_z["parents"], Z)[::-1][:-1] + shortest_path_z_to_y[::-1] #IDK IF THIS WORKS!!! } return { "result": False, }
def create_shortest_paths_dag(graph, reversed_graph, s, t, DEBUG=DO_DEBUG): graphBFS = bfs(graph, s) reverseGraphBFS = bfs(reversed_graph, t) parentsS = graphBFS["parents"] distS = graphBFS["dist"] parentsT = reverseGraphBFS["parents"] distT = reverseGraphBFS["dist"] shortestLength = distS.get(t) # also equal to distT[s] if (shortestLength is None): # There is no path from s to t, FAIL! return {"is_there_shortest_path": False} if (DEBUG): print("shortest_path_length is ", shortestLength) if (DEBUG): print("shortest length other way is ", distT[s]) a_shortest_path = get_path_to_root(parentsS, t)[::-1] shortest_paths_dag = defaultdict(set) # add shortest path to dag first, add other shortest paths after add_path_to_graph(a_shortest_path, shortest_paths_dag) vertices_to_test = set(graph.keys()) # - set([s, t]) if (DEBUG): print("dist S", distS) if (DEBUG): print("dist T", distT) # shortest_path_vertices = set() # have to maintain seperately if (DEBUG): print(vertices_to_test) for v in vertices_to_test: # if(DEBUG): print("v is ", v) dist_from_s_to_v = distS.get(v) dist_from_v_to_t = distT.get(v) # If the distances exist because they were traversed then if (dist_from_s_to_v and dist_from_v_to_t): length = dist_from_s_to_v + dist_from_v_to_t # Add them to find distance of path [S -> v -> T] # if(DEBUG): print("length was", length) if (length == shortestLength): # Add path [s -> v -> t] to shortest paths subgraph path_V_to_S = get_path_to_root(parentsS, v) path_V_to_T = get_path_to_root(parentsT, v) # remove the V node # if(DEBUG): print("path V to S", path_V_to_S) # if(DEBUG): print("path V to T", path_V_to_T) path_S_to_V_to_T = path_V_to_S[::-1] + path_V_to_T[1::] if (DEBUG): print("FOR THIS V, path S to V to T added is", v, path_S_to_V_to_T) # add path to subgraph add_path_to_graph(path_S_to_V_to_T, shortest_paths_dag) # shortest_path_vertices.update(path_S_to_V_to_T) # TODO: you can optimize here by subtracting vertices you added from vertices to test. # All the vertices for the shortest path dag were added but some # edges pointing to other vertices in this dag may be missing # Those will be added next. vertices_in_dag = set(shortest_paths_dag.keys()) shortest_paths_dag_with_all_neighbors = defaultdict(set) # WE HAVE ALL THE VERTICES FOR THE SHORTEST PATHS DAG AND SOME OF THE EDGES. NOW WE WILL ADD THE MISSING EDGES # BETWEEN THESE VERTICES THAT ARE USED IN A SHORTEST PATH FROM S TO T. IF THE EDGE ISNT # USED IN A SHORTEST PATH, IT IS CALLED A LOST EDGE IN THE DAG. # for V in vertices_in_dag: all_neighbors_for_vertex = graph[V] dag_neighbors_for_vertex = shortest_paths_dag[V] all_neighbors_for_vertex_in_dag = vertices_in_dag.intersection( all_neighbors_for_vertex) # set subtraction shortest_paths_dag_with_all_neighbors[ V] = all_neighbors_for_vertex_in_dag # THESE EDGE RELATIONSHIPS MAY BE PART OF A SHORTEST PATH FROM S TO T. WE HAVE TO CHECK THAT AND ADD IT TO OUR # SHORTEST PATHS DAG lost_dag_neighbors = all_neighbors_for_vertex_in_dag - dag_neighbors_for_vertex if (DEBUG): print("FOR VERTEX V, LOST DAG NEIGHBORS IS ", V, lost_dag_neighbors) for K in lost_dag_neighbors: # check if V -> K is a forward edge from S to T in shortest paths dag and if it is, add it # because it creates shortest paths if (distT.get(K) < distT.get(V)): if (DEBUG): print("OK so adding a neighbor relationship! (V, K)", (V, K)) if (DEBUG): print("distT.get(K)", distT.get(K)) if (DEBUG): print("distT.get(V)", distT.get(V)) if (DEBUG): print("distS.get(K)", distS.get(K)) if (DEBUG): print("distS.get(V)", distS.get(V)) shortest_paths_dag[V].add(K) if (DEBUG): print("VERTICES IN THE GRAPH ", set(graph.keys())) if (DEBUG): print("VERTICES IN SHORTEST PATH DAG", vertices_in_dag) if (DEBUG): print("VERTICES NOT IN SHORTEST PATH DAG", set(graph.keys()) - vertices_in_dag) if (DEBUG): print("shortest path subgraph is: ", shortest_paths_dag) if (DEBUG): print("graph_bfs_tree_with_root_s", graphBFS) if (DEBUG): print("reverse_graph_bfs_tree_with_root_t", reverseGraphBFS) return { "is_there_shortest_path": True, "shortest_paths_dag": shortest_paths_dag, "shortest_paths_dag_with_all_neighbors": shortest_paths_dag_with_all_neighbors, "a_shortest_path": a_shortest_path, "shortest_path_length": shortestLength, "graph_bfs_tree_with_root_s": graphBFS["parents"], "reverse_graph_bfs_tree_with_root_t": reverseGraphBFS["parents"] }
def simple_disjoint_graph(s, t, y, z, graph, DEBUG=False): S_to_T_bfs = bfs_with_restriction_set(graph=graph, start=s, end=t, restriction_set=set([y, z])) if (S_to_T_bfs["result"] == False): if (DEBUG): print("FAILED ON FIRST BFS. from s -> t", (s, t)) return {"result": False} S_to_T_bfs_path = get_path_to_root(S_to_T_bfs["parents"], t)[::-1] # Do second dfs if DEBUG: print("S_to_T_bfs_path_found: ", S_to_T_bfs_path) Y_to_Z_dfs = dfs_with_restriction_set( graph=graph, start=y, end=z, # avoidance_set=set(S_to_T_bfs_path), restriction_set=set(S_to_T_bfs_path)) print("Y TO Z DFS RESULT IS:") print(Y_to_Z_dfs) if (Y_to_Z_dfs["result"] == True): if (DEBUG): print("FAILED ON FIRST DFS FROM Y TO Z") Y_to_Z_dfs_path = get_path_to_root(Y_to_Z_dfs["parents"], z)[::-1] return { "result": True, "S_to_T_path": S_to_T_bfs_path, "Y_to_Z_path": Y_to_Z_dfs_path } Y_to_Z_bfs = bfs_with_restriction_set(graph=graph, start=y, end=z, restriction_set=set([s, t])) if (Y_to_Z_bfs["result"] == False): if (DEBUG): print("FAILED ON SECOND BFS. from Y -> Z", (y, z)) return {"result": False} Y_to_Z_bfs_path = get_path_to_root(Y_to_Z_bfs["parents"], z)[::-1] print("Y TO Z BFS", Y_to_Z_bfs_path) S_to_T_dfs_2 = dfs_with_restriction_set( graph=graph, start=s, end=t, # avoidance_set=set(Y_to_Z_bfs_path), restriction_set=set(Y_to_Z_bfs_path)) if (S_to_T_dfs_2["result"] == True): S_to_T_dfs_path = get_path_to_root(S_to_T_dfs_2["parents"], t)[::-1] return { "result": True, "S_to_T_path": S_to_T_dfs_path, "Y_to_Z_path": Y_to_Z_bfs_path } return {"result": False}
def max_flow_disjoint_paths(s, t, y, z, graph, DEBUG=True): # create a residual graph! # each edge has weight 1! import copy residual = {} # We will be adding reverse edges to the residual graph as we do the max flow algorithm! ''' Try this to get simple paths if ford fulk dont make em: Split each node v in the graph into to nodes: vin and vout. For each node v, add an edge of capacity one from vin to vout. Replace each other edge (u, v) in the graph with an edge from uout to vin of capacity 1. ''' for i, neighbors in graph.items(): i_out = str(i) + "o" # split vertex into 2. out and in version. i_in = str(i) + "i" residual[i_in] = {i_out: 1} residual[i_out] = {} for n in neighbors: n_in = str(n) + "i" if (residual.get(i_out) is None): residual[i_out] = {n_in: 1} else: residual[i_out][ n_in] = 1 # VALUE IS CURRENT CAPACITY IN RESIDUAL GRAPH s = str(s) + "i" y = str(y) + "i" t = str(t) + "o" z = str(z) + "o" # Connect A to all start nodes # Connect B to all end nodes # Max flow from A to B, get all edge independent paths from A to B # then get min cut edges! == max flow # then check if you can use min cut edges to # create independent paths from s to t, and y to z? residual["A"] = {} residual["B"] = {} residual["A"][s] = 1 residual["A"][y] = 1 residual[t]["B"] = 1 residual[z]["B"] = 1 original_graph = copy.deepcopy(residual) # remove in and out str parts from path! def normalize_path(path): # removes the in and out part from the path! p = [] for i in path: if i[-1] == "i": p.append(i[:-1]) else: continue return p if DEBUG: pprint.pprint(residual) '''Returns true if there is a path from source 's' to sink 't' in residual graph. Also fills parent[] to store the path ''' def BFS(s, t): from collections import deque # Mark all the vertices as not visited visited = set() parent = {} # Create a queue for BFS visited, queue = set([s]), deque([s]) parent[s] = None # Standard BFS Loop while queue: #Dequeue a vertex from queue and print it u = queue.pop() # Get all adjacent vertices of the dequeued vertex u # If a adjacent has not been visited, then mark it # visited and enqueue it for neighbor in residual[u]: if neighbor not in visited and residual[u][neighbor] > 0: queue.appendleft(neighbor) visited.add(neighbor) parent[neighbor] = u # If we reached sink in BFS starting from source, then return # true, else false return (True, parent) if t in visited else (False, parent) # go from s to t, then go from y to z # keep alternating, until doing both yield False? max_flow = 0 # max flow variables for multiple source, sink finding_st_right_now = True source = None sink = None st_path = None yz_path = None while True: if (finding_st_right_now): # source = s # sink = t source = "A" sink = "B" finding_st_right_now = False else: # source = y # sink = z source = "A" sink = "B" finding_st_right_now = True print("source and sink are", (source, sink)) (result, bfs_tree) = BFS(source, sink) if (result == False): if DEBUG: print("COULD NOT FIND PATH. BREAK") return {"result": False} break # Add path flow to overall flow max_flow += 1 path = get_path_to_root(bfs_tree, sink)[::-1] if (source == s): st_path = path else: yz_path = path # update graph! # update residual capacities of the edges and reverse edges # along the path v = sink while (v != source): u = bfs_tree[v] residual[u][v] -= 1 # Do not create a backward edge from an Input to an outputself # Input edges should not have backward edges because they can ONLY Lead out of one output. # if backward edge isnt there, add it! if DEBUG: print("v[-1]", v[-1]) # only outs can connect to ins on the reverse side. reverse_residual_v = v reverse_residual_u = u if (v[-1] == "i"): # connect v-out to u-in # even if we are dealing with v-in reverse_residual_v = v[:-1] + "o" reverse_residual_u = u[:-1] + "i" if residual[reverse_residual_v].get(reverse_residual_u) is None: residual[reverse_residual_v][reverse_residual_u] = 0 residual[reverse_residual_v][reverse_residual_u] += 1 v = bfs_tree[v] if DEBUG: print("FLOW FOUND IS", path) if DEBUG: print("NORMALIZED FLOW", normalize_path(path)) if DEBUG: print("RESIDUAL AFTER FINDING A PATH from", (source, sink)) if DEBUG: pprint.pprint(residual) if (max_flow == 2): if DEBUG: print("FOUND BOTH PATHS! WE GUCCI") break return_output = { "result": True, # "S_to_T_path": normalize_path(st_path), # "Y_to_Z_path": normalize_path(yz_path) # get_path_to_root(Y_to_Z_dfs_2["parents"], z)[::-1] } # original graph has all edge relationships with postiive val. # if they now 0, its a min cut edge for i, neighbors in original_graph.items(): for j in neighbors: if (residual[i][j] == 0): print("MINIUM CUT EDGE IS ", (i, j)) if DEBUG: pprint.pprint(return_output) return return_output
def disjoint_graph(s, t, y, z, graph, DEBUG=False): S_to_T_bfs = bfs_with_restriction_set(graph=graph, start=s, end=t, restriction_set=set([y, z])) if (S_to_T_bfs["result"] == False): if (DEBUG): print("FAILED ON FIRST BFS. from s -> t", (s, t)) return {"result": False} S_to_T_bfs_path = get_path_to_root(S_to_T_bfs["parents"], t)[::-1] # Do second dfs if DEBUG: print("S_to_T_bfs_path_found: ", S_to_T_bfs_path) Y_to_Z_dfs = dfs_with_restriction_set(graph=graph, start=y, end=z, avoidance_set=set(S_to_T_bfs_path), restriction_set=set([s, t])) if (Y_to_Z_dfs["result"] == False): if (DEBUG): print("FAILED ON FIRST DFS FROM Y TO Z") return { "result": False, } Y_to_Z_dfs_path = get_path_to_root(Y_to_Z_dfs["parents"], z)[::-1] if DEBUG: print("Y to Z dfs path found: ", Y_to_Z_dfs_path) # Do third dfs S_to_T_dfs = dfs_with_restriction_set(graph=graph, start=s, end=t, avoidance_set=set(), restriction_set=set(Y_to_Z_dfs_path)) if (S_to_T_dfs["result"] == True): if (DEBUG): print("IT WAS MERGED WITHIN 2 DFS'S") return { "result": True, "S_to_T_path": get_path_to_root(S_to_T_dfs["parents"], t)[::-1], "Y_to_Z_path": Y_to_Z_dfs_path } # The second dfs failed, so move on to the third dfs: if DEBUG: print( "SECOND DFS FAILED. WE HAVE TO REPEAT BFS AGAIN. second was S->T", (s, t)) ############################################################################ #### TRY THE OTHER WAY -> 1 BFS, 2 DFS Y_to_Z_bfs = bfs_with_restriction_set(graph=graph, start=y, end=z, restriction_set=set([s, t])) if (Y_to_Z_bfs["result"] == False): if (DEBUG): print("FAILED ON SECOND BFS. from Y -> Z", (y, z)) return {"result": False} Y_to_Z_bfs_path = get_path_to_root(Y_to_Z_bfs["parents"], z)[::-1] if DEBUG: print("Y_to_Z_bfs_path_found: ", Y_to_Z_bfs_path) S_to_T_dfs_2 = dfs_with_restriction_set(graph=graph, start=s, end=t, avoidance_set=set(Y_to_Z_bfs_path), restriction_set=set([y, z])) if (S_to_T_dfs_2["result"] == False): if (DEBUG): print("FAILED ON FIRST DFS FROM Y TO Z") return { "result": False, } S_to_T_dfs_2_path = get_path_to_root(S_to_T_dfs_2["parents"], t)[::-1] if DEBUG: print("S_to_T_dfs_2_path: ", S_to_T_dfs_2_path) Y_to_Z_dfs_2 = dfs_with_restriction_set( graph=graph, start=y, end=z, avoidance_set=set(), restriction_set=set(S_to_T_dfs_2_path)) if (Y_to_Z_dfs_2["result"] == True): if (DEBUG): print("IT WAS MERGED WITHIN 4 DFS'S") return { "result": True, "S_to_T_path": S_to_T_dfs_2_path, "Y_to_Z_path": get_path_to_root(Y_to_Z_dfs_2["parents"], z)[::-1] } if DEBUG: print("FOURTH DFS FAILED. COMPLETE FAILURE", (y, z)) return {"result": False}