def test_degeneracy_ordering_nonempty(adjacencies: List[Set[Vertex]]) -> None: g = UndirectedGraph(adjacencies=adjacencies) connected_vertices = g.connected_vertices() ordering = list(degeneracy_ordering(g)) assert set(ordering) == connected_vertices assert all(g.degree(ordering[0]) <= g.degree(v) for v in ordering[1:]) ordering_min1 = list(degeneracy_ordering(g, drop=1)) assert ordering_min1 == ordering[:-1]
def degeneracy_ordering(graph: UndirectedGraph, drop: int = 0) -> Generator[Vertex, None, None]: """ Iterate connected vertices, lowest degree first. drop=N: omit last N vertices """ assert drop >= 0 priority_per_node = [-2] * graph.order max_degree = 0 num_candidates = 0 for c in range(graph.order): if degree := graph.degree(c): priority_per_node[c] = degree max_degree = max(max_degree, degree) num_candidates += 1
def visit(graph: UndirectedGraph, reporter: Reporter, pivot_choice_X: bool, candidates: Set[Vertex], excluded: Set[Vertex], clique: List[Vertex]) -> None: assert all(graph.degree(v) > 0 for v in candidates) assert all(graph.degree(v) > 0 for v in excluded) assert candidates.isdisjoint(excluded) assert len(candidates) >= 1 if len(candidates) == 1: # Same logic as below, stripped down for this common case for v in candidates: neighbours = graph.adjacencies[v] assert neighbours if excluded.isdisjoint(neighbours): reporter.record(clique + [v]) return # Quickly handle locally unconnected candidates while finding pivot remaining_candidates = [] seen_local_degree = 0 for v in candidates: neighbours = graph.adjacencies[v] local_degree = len(candidates.intersection(neighbours)) if local_degree == 0: # Same logic as below, stripped down if neighbours.isdisjoint(excluded): reporter.record(clique + [v]) else: if seen_local_degree < local_degree: seen_local_degree = local_degree pivot = v remaining_candidates.append(v) if seen_local_degree == 0: return if pivot_choice_X: for v in excluded: neighbours = graph.adjacencies[v] local_degree = len(candidates.intersection(neighbours)) if seen_local_degree < local_degree: seen_local_degree = local_degree pivot = v for v in remaining_candidates: neighbours = graph.adjacencies[v] assert neighbours if pivot not in neighbours: candidates.remove(v) if neighbouring_candidates := candidates.intersection(neighbours): neighbouring_excluded = excluded.intersection(neighbours) visit(graph=graph, reporter=reporter, pivot_choice_X=pivot_choice_X, candidates=neighbouring_candidates, excluded=neighbouring_excluded, clique=clique + [v]) elif excluded.isdisjoint(neighbours): reporter.record(clique + [v]) excluded.add(v)
def visit(graph: UndirectedGraph, reporter: Reporter, candidates: Set[Vertex], excluded: Set[Vertex], clique: List[Vertex]) -> None: assert all(graph.degree(v) > 0 for v in candidates) assert all(graph.degree(v) > 0 for v in excluded) assert candidates.isdisjoint(excluded) assert candidates while candidates: v = candidates.pop() neighbours = graph.adjacencies[v] neighbouring_candidates = candidates.intersection(neighbours) if neighbouring_candidates: neighbouring_excluded = excluded.intersection(neighbours) visit(graph, reporter, candidates=neighbouring_candidates, excluded=neighbouring_excluded, clique=clique + [v]) elif excluded.isdisjoint(neighbours): reporter.record(clique + [v]) excluded.add(v)