beam: Tarjan articulation points and bridges (multigraph-aware)
This commit is contained in:
1 parent
320474b1f9
commit
bb8aec7de9
2 files changed
+121
No files matched your search
@@ -0,0 +1,101 @@
|
|||||||
|
"""Tarjan articulation points and bridges in an undirected graph."""
|
||||||
|
from __future__ import annotations
|
||||||
|
import sys
|
||||||
|
from collections import defaultdict, deque
|
||||||
|
|
||||||
|
|
||||||
|
def _build_indexed(adj):
|
||||||
|
adj = {k: list(v) for k, v in adj.items()}
|
||||||
|
nodes = set(adj.keys())
|
||||||
|
for u, nbrs in adj.items():
|
||||||
|
for v in nbrs:
|
||||||
|
nodes.add(v)
|
||||||
|
for n in nodes:
|
||||||
|
adj.setdefault(n, [])
|
||||||
|
|
||||||
|
slots = []
|
||||||
|
for u in nodes:
|
||||||
|
for v in adj[u]:
|
||||||
|
if v != u:
|
||||||
|
slots.append((u, v))
|
||||||
|
|
||||||
|
pair_of = [-1] * len(slots)
|
||||||
|
remaining = defaultdict(deque)
|
||||||
|
for i, (u, v) in enumerate(slots):
|
||||||
|
remaining[(v, u)].append(i)
|
||||||
|
for i, (u, v) in enumerate(slots):
|
||||||
|
if pair_of[i] != -1:
|
||||||
|
continue
|
||||||
|
q = remaining[(u, v)]
|
||||||
|
while q and pair_of[q[0]] != -1:
|
||||||
|
q.popleft()
|
||||||
|
if q:
|
||||||
|
r = q.popleft()
|
||||||
|
pair_of[i] = r
|
||||||
|
pair_of[r] = i
|
||||||
|
|
||||||
|
# shared uid per undirected edge
|
||||||
|
uid_of = [None] * len(slots)
|
||||||
|
uid = 0
|
||||||
|
for i in range(len(slots)):
|
||||||
|
if uid_of[i] is not None:
|
||||||
|
continue
|
||||||
|
if pair_of[i] != -1 and pair_of[i] != i:
|
||||||
|
uid_of[i] = uid
|
||||||
|
uid_of[pair_of[i]] = uid
|
||||||
|
uid += 1
|
||||||
|
else:
|
||||||
|
uid_of[i] = uid
|
||||||
|
uid += 1
|
||||||
|
|
||||||
|
idx_adj = {n: [] for n in nodes}
|
||||||
|
for i, (u, v) in enumerate(slots):
|
||||||
|
idx_adj[u].append((v, uid_of[i]))
|
||||||
|
|
||||||
|
return nodes, idx_adj
|
||||||
|
|
||||||
|
|
||||||
|
def articulation_points_and_bridges(adj):
|
||||||
|
adj = {k: list(v) for k, v in adj.items()}
|
||||||
|
nodes, idx_adj = _build_indexed(adj)
|
||||||
|
|
||||||
|
sys.setrecursionlimit(max(10000, len(nodes) + 100))
|
||||||
|
disc = {}
|
||||||
|
low = {}
|
||||||
|
timer = [0]
|
||||||
|
ap = set()
|
||||||
|
bridges = set()
|
||||||
|
|
||||||
|
def dfs(u, parent_uid, is_root):
|
||||||
|
disc[u] = low[u] = timer[0]
|
||||||
|
timer[0] += 1
|
||||||
|
children = 0
|
||||||
|
for v, uid in idx_adj[u]:
|
||||||
|
if uid == parent_uid:
|
||||||
|
continue
|
||||||
|
if v not in disc:
|
||||||
|
children += 1
|
||||||
|
dfs(v, uid, False)
|
||||||
|
low[u] = min(low[u], low[v])
|
||||||
|
if (not is_root) and low[v] >= disc[u]:
|
||||||
|
ap.add(u)
|
||||||
|
if low[v] > disc[u]:
|
||||||
|
bridges.add((min(u, v), max(u, v)))
|
||||||
|
else:
|
||||||
|
low[u] = min(low[u], disc[v])
|
||||||
|
if is_root and children > 1:
|
||||||
|
ap.add(u)
|
||||||
|
|
||||||
|
for start in sorted(nodes, key=lambda x: str(x)):
|
||||||
|
if start not in disc:
|
||||||
|
dfs(start, -1, True)
|
||||||
|
|
||||||
|
return ap, bridges
|
||||||
|
|
||||||
|
|
||||||
|
def articulation_points(adj):
|
||||||
|
return articulation_points_and_bridges(adj)[0]
|
||||||
|
|
||||||
|
|
||||||
|
def bridges(adj):
|
||||||
|
return articulation_points_and_bridges(adj)[1]
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import sys
|
||||||
|
sys.path.insert(0, '/root/.hermes/wizard-outputs/beam')
|
||||||
|
from tarjan_articulation import articulation_points, bridges
|
||||||
|
|
||||||
|
adj = {'1': ['2'], '2': ['1', '3'], '3': ['2', '4'], '4': ['3']}
|
||||||
|
assert sorted(articulation_points(adj)) == ['2', '3'], articulation_points(adj)
|
||||||
|
assert sorted(bridges(adj)) == [('1', '2'), ('2', '3'), ('3', '4')], bridges(adj)
|
||||||
|
|
||||||
|
adj = {'a': ['b', 'c'], 'b': ['a', 'c'], 'c': ['a', 'b']}
|
||||||
|
assert articulation_points(adj) == set(), articulation_points(adj)
|
||||||
|
assert bridges(adj) == set(), bridges(adj)
|
||||||
|
|
||||||
|
adj = {'1': ['2', '2'], '2': ['1', '1']}
|
||||||
|
assert bridges(adj) == set(), bridges(adj)
|
||||||
|
|
||||||
|
adj = {'c': ['a', 'b', 'd'], 'a': ['c'], 'b': ['c'], 'd': ['c']}
|
||||||
|
assert articulation_points(adj) == {'c'}, articulation_points(adj)
|
||||||
|
|
||||||
|
print("ALL TESTS PASS")
|
||||||
|
print("written")
|
||||||
Reference in new issue
Block a user