| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
- #!/usr/bin/env python3
- """课前零食:用很小的图观察欧拉路径和 Hierholzer 栈。"""
- from __future__ import annotations
- from collections import defaultdict, deque
- DEFAULT_EDGES = [
- ("A", "B"), ("B", "C"), ("C", "A"),
- ("A", "D"), ("D", "E"), ("E", "A"),
- ]
- def ask(prompt: str, default: str = "") -> str:
- try:
- value = input(prompt).strip()
- except EOFError:
- value = ""
- return value or default
- def parse_edges(raw: str) -> list[tuple[str, str]]:
- edges: list[tuple[str, str]] = []
- for token in raw.replace(",", ",").split(","):
- token = token.strip()
- if not token:
- continue
- if "-" not in token:
- raise ValueError(f"{token!r} 缺少连字符,例如 A-B")
- left, right = (part.strip() for part in token.split("-", 1))
- if not left or not right:
- raise ValueError(f"{token!r} 的端点不能为空")
- edges.append((left, right))
- if not edges:
- raise ValueError("至少输入一条边")
- return edges
- def build_graph(edges: list[tuple[str, str]]) -> dict[str, list[tuple[str, int]]]:
- graph: dict[str, list[tuple[str, int]]] = defaultdict(list)
- for edge_id, (u, v) in enumerate(edges):
- graph[u].append((v, edge_id))
- graph[v].append((u, edge_id))
- return graph
- def nonzero_connected(graph: dict[str, list[tuple[str, int]]]) -> bool:
- active = [vertex for vertex, neighbors in graph.items() if neighbors]
- if not active:
- return True
- seen = {active[0]}
- queue: deque[str] = deque([active[0]])
- while queue:
- u = queue.popleft()
- for v, _ in graph[u]:
- if v not in seen:
- seen.add(v)
- queue.append(v)
- return all(vertex in seen for vertex in active)
- def hierholzer_trace(
- graph: dict[str, list[tuple[str, int]]], start: str, edge_count: int
- ) -> list[str]:
- used = [False] * edge_count
- pointer = {vertex: 0 for vertex in graph}
- stack = [start]
- reversed_answer: list[str] = []
- print("\n--- 栈的动作日志 ---")
- while stack:
- u = stack[-1]
- while (
- pointer[u] < len(graph[u])
- and used[graph[u][pointer[u]][1]]
- ):
- pointer[u] += 1
- if pointer[u] == len(graph[u]):
- finished = stack.pop()
- reversed_answer.append(finished)
- print(f"{finished} 没有未用边:弹栈,倒序答案为 {reversed_answer}")
- continue
- v, edge_id = graph[u][pointer[u]]
- used[edge_id] = True
- stack.append(v)
- print(f"走边 #{edge_id}: {u}-{v},栈变成 {' -> '.join(stack)}")
- answer = list(reversed(reversed_answer))
- return answer
- def main() -> None:
- print("欧拉小散步:输入边时用 A-B,B-C 这种格式。")
- raw = ask("直接回车用双三角形;或输入自己的边: ")
- try:
- edges = DEFAULT_EDGES if not raw else parse_edges(raw)
- except ValueError as error:
- print(f"输入不太像一张图:{error}。这次先用预设图。")
- edges = DEFAULT_EDGES
- graph = build_graph(edges)
- degree = {vertex: len(neighbors) for vertex, neighbors in graph.items()}
- odd = [vertex for vertex, value in degree.items() if value % 2 == 1]
- print("\n边:", ", ".join(f"{u}-{v}" for u, v in edges))
- print("度数:", ", ".join(f"{v}:{degree[v]}" for v in sorted(degree)))
- print("奇度点:", odd or "没有")
- connected = nonzero_connected(graph)
- possible = connected and len(odd) in (0, 2)
- print("非孤立点连通:", "是" if connected else "否")
- if not possible:
- print("\n这张图没有欧拉路径:先别让栈背锅,条件已经不成立。")
- return
- start = odd[0] if odd else min(graph)
- print(f"\n条件成立,从 {start} 开始。注意:收集答案的顺序会反过来。")
- answer = hierholzer_trace(graph, start, len(edges))
- print("\n最终欧拉路线:", " -> ".join(answer))
- print(
- f"长度检查:{len(answer)} 个顶点,应该是边数 + 1 = {len(edges) + 1}。"
- )
- if __name__ == "__main__":
- main()
|