00-从互相绕路到缩点.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. #!/usr/bin/env python3
  2. """课前零食:在小图上追踪 SCC、桥和割点。"""
  3. from __future__ import annotations
  4. from collections import defaultdict
  5. DIRECTED = [("A", "B"), ("B", "C"), ("C", "A"), ("C", "D"), ("D", "E"), ("E", "D"), ("E", "F")]
  6. UNDIRECTED = [("A", "B"), ("B", "C"), ("C", "A"), ("C", "D"), ("D", "E"), ("E", "F"), ("F", "D")]
  7. def ask(prompt: str, default: str) -> str:
  8. try:
  9. return input(prompt).strip() or default
  10. except EOFError:
  11. return default
  12. def scc_lab() -> None:
  13. graph: dict[str, list[str]] = defaultdict(list)
  14. reverse: dict[str, list[str]] = defaultdict(list)
  15. vertices = set()
  16. for u, v in DIRECTED:
  17. graph[u].append(v)
  18. reverse[v].append(u)
  19. vertices.update((u, v))
  20. finish: list[str] = []
  21. seen: set[str] = set()
  22. def first_dfs(u: str) -> None:
  23. seen.add(u)
  24. print(f"原图进入 {u}")
  25. for v in graph[u]:
  26. if v not in seen:
  27. first_dfs(v)
  28. finish.append(u)
  29. print(f" {u} 的后继都处理完,完成序列:{finish}")
  30. print("\n原图边:", ", ".join(f"{u}->{v}" for u, v in DIRECTED))
  31. for u in sorted(vertices):
  32. if u not in seen:
  33. first_dfs(u)
  34. print("\n反向完成顺序:", list(reversed(finish)))
  35. assigned: set[str] = set()
  36. groups: list[list[str]] = []
  37. def second_dfs(u: str, group: list[str]) -> None:
  38. assigned.add(u)
  39. group.append(u)
  40. for v in reverse[u]:
  41. if v not in assigned:
  42. second_dfs(v, group)
  43. for u in reversed(finish):
  44. if u not in assigned:
  45. group: list[str] = []
  46. second_dfs(u, group)
  47. groups.append(sorted(group))
  48. print("反图收下一团:", sorted(group))
  49. print("缩点后只剩单向关系;团内部的绕路已经不必重复计算。")
  50. def fragility_lab() -> None:
  51. graph: dict[str, list[tuple[str, int]]] = defaultdict(list)
  52. vertices = set()
  53. for edge_id, (u, v) in enumerate(UNDIRECTED):
  54. graph[u].append((v, edge_id))
  55. graph[v].append((u, edge_id))
  56. vertices.update((u, v))
  57. dfn: dict[str, int] = {}
  58. low: dict[str, int] = {}
  59. bridges: list[tuple[str, str]] = []
  60. cuts: set[str] = set()
  61. timer = 0
  62. def dfs(u: str, parent_edge: int | None) -> None:
  63. nonlocal timer
  64. timer += 1
  65. dfn[u] = low[u] = timer
  66. children = 0
  67. for v, edge_id in graph[u]:
  68. if edge_id == parent_edge:
  69. continue
  70. if v not in dfn:
  71. children += 1
  72. dfs(v, edge_id)
  73. low[u] = min(low[u], low[v])
  74. print(f"回到 {u}:孩子 {v} 的 low={low[v]},所以 low[{u}]={low[u]}")
  75. if low[v] > dfn[u]:
  76. bridges.append((u, v))
  77. if parent_edge is not None and low[v] >= dfn[u]:
  78. cuts.add(u)
  79. else:
  80. low[u] = min(low[u], dfn[v])
  81. print(f"{u} 看见返祖/已访问边到 {v}:low[{u}] 更新为 {low[u]}")
  82. if parent_edge is None and children >= 2:
  83. cuts.add(u)
  84. print("\n无向图边:", ", ".join(f"{u}-{v}" for u, v in UNDIRECTED))
  85. for u in sorted(vertices):
  86. if u not in dfn:
  87. dfs(u, None)
  88. print("\ndfn:", dfn)
  89. print("low:", low)
  90. print("桥:", bridges or "没有")
  91. print("割点:", sorted(cuts) or "没有")
  92. def main() -> None:
  93. print("连通小实验室:1=SCC 缩点,2=桥与割点。")
  94. choice = ask("选择(直接回车为 1): ", "1")
  95. if choice == "2":
  96. fragility_lab()
  97. else:
  98. scc_lab()
  99. if __name__ == "__main__":
  100. main()