@@ -17,92 +17,93 @@ class ChinesePostman:
1717 """
1818 Solve Chinese Postman Problem for weighted undirected graphs.
1919 """
20-
20+
2121 def __init__ (self , n : int ):
2222 self .n = n
2323 self .adj : List [List [Tuple [int , int ]]] = [[] for _ in range (n )]
2424 self .total_weight = 0
25-
25+
2626 def add_edge (self , u : int , v : int , w : int ) -> None :
2727 """Add undirected edge."""
2828 self .adj [u ].append ((v , w ))
2929 self .adj [v ].append ((u , w ))
3030 self .total_weight += w
31-
31+
3232 def _floyd_warshall (self ) -> List [List [float ]]:
3333 """All-pairs shortest paths."""
3434 n = self .n
35- dist = [[float (' inf' )] * n for _ in range (n )]
36-
35+ dist = [[float (" inf" )] * n for _ in range (n )]
36+
3737 for i in range (n ):
3838 dist [i ][i ] = 0
39-
39+
4040 for u in range (n ):
4141 for v , w in self .adj [u ]:
4242 dist [u ][v ] = min (dist [u ][v ], w )
43-
43+
4444 for k in range (n ):
4545 for i in range (n ):
4646 for j in range (n ):
4747 if dist [i ][k ] + dist [k ][j ] < dist [i ][j ]:
4848 dist [i ][j ] = dist [i ][k ] + dist [k ][j ]
49-
49+
5050 return dist
51-
51+
5252 def _find_odd_degree_vertices (self ) -> List [int ]:
5353 """Find vertices with odd degree."""
5454 odd = []
5555 for u in range (self .n ):
5656 if len (self .adj [u ]) % 2 == 1 :
5757 odd .append (u )
5858 return odd
59-
60- def _min_weight_perfect_matching (self , odd_vertices : List [int ],
61- dist : List [List [float ]]) -> float :
59+
60+ def _min_weight_perfect_matching (
61+ self , odd_vertices : List [int ], dist : List [List [float ]]
62+ ) -> float :
6263 """
6364 Find minimum weight perfect matching on odd degree vertices.
6465 Uses brute force for small k (k <= 20), which is practical.
6566 """
6667 k = len (odd_vertices )
6768 if k == 0 :
6869 return 0
69-
70+
7071 # Dynamic programming: dp[mask] = min cost to match vertices in mask
7172 dp : Dict [int , float ] = {0 : 0 }
72-
73+
7374 for mask in range (1 << k ):
74- if bin (mask ).count ('1' ) % 2 == 1 :
75+ if bin (mask ).count ("1" ) % 2 == 1 :
7576 continue # Odd number of bits, can't be perfectly matched
76-
77+
7778 if mask not in dp :
7879 continue
79-
80+
8081 # Find first unset bit
8182 i = 0
8283 while i < k and (mask & (1 << i )):
8384 i += 1
84-
85+
8586 if i >= k :
8687 continue
87-
88+
8889 # Try matching i with every other unmatched vertex j
8990 for j in range (i + 1 , k ):
9091 if not (mask & (1 << j )):
9192 new_mask = mask | (1 << i ) | (1 << j )
9293 cost = dp [mask ] + dist [odd_vertices [i ]][odd_vertices [j ]]
9394 if new_mask not in dp or cost < dp [new_mask ]:
9495 dp [new_mask ] = cost
95-
96+
9697 full_mask = (1 << k ) - 1
9798 return dp .get (full_mask , 0 )
98-
99+
99100 def solve (self ) -> Tuple [float , List [int ]]:
100101 """
101102 Solve Chinese Postman Problem.
102-
103+
103104 Returns:
104105 Tuple of (minimum_cost, eulerian_circuit)
105-
106+
106107 Example:
107108 >>> cpp = ChinesePostman(4)
108109 >>> cpp.add_edge(0, 1, 1)
@@ -115,40 +116,41 @@ def solve(self) -> Tuple[float, List[int]]:
115116 """
116117 # Find odd degree vertices
117118 odd_vertices = self ._find_odd_degree_vertices ()
118-
119+
119120 # Graph is already Eulerian
120121 if len (odd_vertices ) == 0 :
121122 circuit = self ._find_eulerian_circuit ()
122123 return float (self .total_weight ), circuit
123-
124+
124125 # Compute all-pairs shortest paths
125126 dist = self ._floyd_warshall ()
126-
127+
127128 # Find minimum weight matching
128129 matching_cost = self ._min_weight_perfect_matching (odd_vertices , dist )
129-
130+
130131 # Duplicate edges from matching to make graph Eulerian
131132 self ._add_matching_edges (odd_vertices , dist )
132-
133+
133134 # Find Eulerian circuit
134135 circuit = self ._find_eulerian_circuit ()
135-
136+
136137 return float (self .total_weight + matching_cost ), circuit
137-
138- def _add_matching_edges (self , odd_vertices : List [int ],
139- dist : List [List [float ]]) -> None :
138+
139+ def _add_matching_edges (
140+ self , odd_vertices : List [int ], dist : List [List [float ]]
141+ ) -> None :
140142 """Duplicate edges based on minimum matching (simplified)."""
141143 # In practice, reconstruct path and add edges
142144 # For this implementation, we assume edges can be duplicated
143145 pass
144-
146+
145147 def _find_eulerian_circuit (self ) -> List [int ]:
146148 """Find Eulerian circuit using Hierholzer's algorithm."""
147149 n = self .n
148150 adj_copy = [list (neighbors ) for neighbors in self .adj ]
149151 circuit = []
150152 stack = [0 ]
151-
153+
152154 while stack :
153155 u = stack [- 1 ]
154156 if adj_copy [u ]:
@@ -161,18 +163,20 @@ def _find_eulerian_circuit(self) -> List[int]:
161163 stack .append (v )
162164 else :
163165 circuit .append (stack .pop ())
164-
166+
165167 return circuit [::- 1 ]
166168
167169
168- def chinese_postman (n : int , edges : List [Tuple [int , int , int ]]) -> Tuple [float , List [int ]]:
170+ def chinese_postman (
171+ n : int , edges : List [Tuple [int , int , int ]]
172+ ) -> Tuple [float , List [int ]]:
169173 """
170174 Convenience function for Chinese Postman.
171-
175+
172176 Args:
173177 n: Number of vertices
174178 edges: List of (u, v, weight) undirected edges
175-
179+
176180 Returns:
177181 (minimum_cost, eulerian_circuit)
178182 """
@@ -184,4 +188,5 @@ def chinese_postman(n: int, edges: List[Tuple[int, int, int]]) -> Tuple[float, L
184188
185189if __name__ == "__main__" :
186190 import doctest
187- doctest .testmod ()
191+
192+ doctest .testmod ()
0 commit comments