|
23 | 23 | 'all_pair_shortest_paths',
|
24 | 24 | 'topological_sort',
|
25 | 25 | 'topological_sort_parallel',
|
26 |
| - 'max_flow' |
| 26 | + 'max_flow', |
| 27 | + 'maximum_matching', |
| 28 | + 'maximum_matching_parallel', |
| 29 | + 'bipartite_coloring' |
27 | 30 | ]
|
28 | 31 |
|
29 | 32 | Stack = Queue = deque
|
@@ -1216,3 +1219,322 @@ def max_flow(graph, source, sink, algorithm='edmonds_karp', **kwargs):
|
1216 | 1219 | f"Currently {algorithm} algorithm isn't implemented for "
|
1217 | 1220 | "performing max flow on graphs.")
|
1218 | 1221 | return getattr(algorithms, func)(graph, source, sink)
|
| 1222 | + |
| 1223 | +def bipartite_coloring(graph: Graph, **kwargs) -> tuple[bool, dict]: |
| 1224 | + """ |
| 1225 | + Finds a 2-coloring of the given graph if it is bipartite. |
| 1226 | +
|
| 1227 | + Parameters |
| 1228 | + ========== |
| 1229 | +
|
| 1230 | + graph: Graph |
| 1231 | + The graph under consideration. |
| 1232 | + invert: bool |
| 1233 | + If True, the colors are inverted. |
| 1234 | + make_undirected: bool |
| 1235 | + If False, the input graph should be undirected else it can be made undirected by setting this to True |
| 1236 | + backend: pydatastructs.Backend |
| 1237 | + The backend to be used. |
| 1238 | + Optional, by default, the best available |
| 1239 | + backend is used. |
| 1240 | +
|
| 1241 | + Returns |
| 1242 | + ======= |
| 1243 | +
|
| 1244 | + tuple |
| 1245 | + A tuple containing a boolean value and a dictionary. |
| 1246 | + The boolean value is True if the graph is bipartite |
| 1247 | + and False otherwise. The dictionary contains the |
| 1248 | + color assigned to each vertex. |
| 1249 | +
|
| 1250 | + Examples |
| 1251 | + ======== |
| 1252 | +
|
| 1253 | + >>> from pydatastructs import Graph, AdjacencyListGraphNode, bipartite_coloring |
| 1254 | + >>> v_1 = AdjacencyListGraphNode('v_1') |
| 1255 | + >>> v_2 = AdjacencyListGraphNode('v_2') |
| 1256 | + >>> v_3 = AdjacencyListGraphNode('v_3') |
| 1257 | + >>> v_4 = AdjacencyListGraphNode('v_4') |
| 1258 | + >>> graph = Graph(v_1, v_2, v_3, v_4) |
| 1259 | + >>> graph.add_edge('v_1', 'v_2') |
| 1260 | + >>> graph.add_edge('v_2', 'v_3') |
| 1261 | + >>> graph.add_edge('v_4', 'v_1') |
| 1262 | + >>> bipartite_coloring(graph) |
| 1263 | + >>> (True, {'v_1': 0, 'v_2': 1, 'v_4': 1, 'v_3': 0}) |
| 1264 | +
|
| 1265 | + References |
| 1266 | + ========== |
| 1267 | +
|
| 1268 | + .. [1] https://en.wikipedia.org/wiki/Bipartite_graph |
| 1269 | + """ |
| 1270 | + |
| 1271 | + color = {} |
| 1272 | + queue = Queue() |
| 1273 | + invert = kwargs.get('invert', False) |
| 1274 | + make_unidirected = kwargs.get('make_undirected', False) |
| 1275 | + |
| 1276 | + if make_unidirected: |
| 1277 | + graph = graph.to_undirected_adjacency_list() |
| 1278 | + |
| 1279 | + for start in graph.vertices: |
| 1280 | + if start not in color: |
| 1281 | + queue.append(start) |
| 1282 | + color[start] = 1 if invert else 0 |
| 1283 | + |
| 1284 | + while queue: |
| 1285 | + u = queue.popleft() |
| 1286 | + for v in graph.neighbors(u): |
| 1287 | + v_name = v.name |
| 1288 | + if v_name not in color: |
| 1289 | + color[v_name] = 1 - color[u] |
| 1290 | + queue.append(v_name) |
| 1291 | + elif color[v_name] == color[u]: |
| 1292 | + return (False, {}) |
| 1293 | + |
| 1294 | + return (True, color) |
| 1295 | + |
| 1296 | + |
| 1297 | +def _maximum_matching_hopcroft_karp_(graph: Graph): |
| 1298 | + U = set() |
| 1299 | + V = set() |
| 1300 | + bipartiteness, coloring = bipartite_coloring(graph) |
| 1301 | + |
| 1302 | + if not bipartiteness: |
| 1303 | + raise ValueError("Graph is not bipartite.") |
| 1304 | + |
| 1305 | + for node, c in coloring.items(): |
| 1306 | + if c == 0: |
| 1307 | + U.add(node) |
| 1308 | + else: |
| 1309 | + V.add(node) |
| 1310 | + |
| 1311 | + |
| 1312 | + pair_U = {u: None for u in U} |
| 1313 | + pair_V = {v: None for v in V} |
| 1314 | + dist = {} |
| 1315 | + |
| 1316 | + def bfs(): |
| 1317 | + queue = Queue() |
| 1318 | + for u in U: |
| 1319 | + if pair_U[u] is None: |
| 1320 | + dist[u] = 0 |
| 1321 | + queue.append(u) |
| 1322 | + else: |
| 1323 | + dist[u] = float('inf') |
| 1324 | + dist[None] = float('inf') |
| 1325 | + while queue: |
| 1326 | + u = queue.popleft() |
| 1327 | + if dist[u] < dist[None]: |
| 1328 | + for v in graph.neighbors(u): |
| 1329 | + if dist[pair_V[v.name]] == float('inf'): |
| 1330 | + dist[pair_V[v.name]] = dist[u] + 1 |
| 1331 | + queue.append(pair_V[v.name]) |
| 1332 | + return dist[None] != float('inf') |
| 1333 | + |
| 1334 | + def dfs(u): |
| 1335 | + if u is not None: |
| 1336 | + for v in graph.neighbors(u): |
| 1337 | + if dist[pair_V[v.name]] == dist[u] + 1: |
| 1338 | + if dfs(pair_V[v.name]): |
| 1339 | + pair_V[v.name] = u |
| 1340 | + pair_U[u] = v.name |
| 1341 | + return True |
| 1342 | + dist[u] = float('inf') |
| 1343 | + return False |
| 1344 | + return True |
| 1345 | + |
| 1346 | + matching = set() |
| 1347 | + while bfs(): |
| 1348 | + for u in U: |
| 1349 | + if pair_U[u] is None: |
| 1350 | + if dfs(u): |
| 1351 | + matching.add((u, pair_U[u])) |
| 1352 | + |
| 1353 | + return matching |
| 1354 | + |
| 1355 | +def maximum_matching(graph: Graph, algorithm: str, **kwargs) -> set: |
| 1356 | + """ |
| 1357 | + Finds the maximum matching in the given undirected using the given algorithm. |
| 1358 | +
|
| 1359 | + Parameters |
| 1360 | + ========== |
| 1361 | +
|
| 1362 | + graph: Graph |
| 1363 | + The graph under consideration. |
| 1364 | + algorithm: str |
| 1365 | + The algorithm to be used. |
| 1366 | + Currently, following are supported, |
| 1367 | +
|
| 1368 | + 'hopcroft_karp' -> Hopcroft-Karp algorithm for Bipartite Graphs as given in [1]. |
| 1369 | + make_undirected: bool |
| 1370 | + If False, the graph should be undirected or unwanted results may be obtained. The graph can be made undirected by setting this to true. |
| 1371 | + backend: pydatastructs.Backend |
| 1372 | + The backend to be used. |
| 1373 | + Optional, by default, the best available |
| 1374 | + backend is used. |
| 1375 | +
|
| 1376 | + Returns |
| 1377 | + ======= |
| 1378 | +
|
| 1379 | + set |
| 1380 | + The set of edges which form the maximum matching. |
| 1381 | +
|
| 1382 | + Examples |
| 1383 | + ======== |
| 1384 | +
|
| 1385 | + >>> from pydatastructs import Graph, AdjacencyListGraphNode, maximum_matching |
| 1386 | + >>> v_1 = AdjacencyListGraphNode('v_1') |
| 1387 | + >>> v_2 = AdjacencyListGraphNode('v_2') |
| 1388 | + >>> v_3 = AdjacencyListGraphNode('v_3') |
| 1389 | + >>> v_4 = AdjacencyListGraphNode('v_4') |
| 1390 | + >>> graph = Graph(v_1, v_2, v_3, v_4) |
| 1391 | + >>> graph.add_edge('v_1', 'v_2') |
| 1392 | + >>> graph.add_edge('v_2', 'v_3') |
| 1393 | + >>> graph.add_edge('v_4', 'v_1') |
| 1394 | + >>> maximum_matching(graph, 'hopcroft_karp') |
| 1395 | + >>> {('v_1', 'v_4'), ('v_3', 'v_2')} |
| 1396 | +
|
| 1397 | + References |
| 1398 | + ========== |
| 1399 | +
|
| 1400 | + .. [1] https://en.wikipedia.org/wiki/Hopcroft%E2%80%93Karp_algorithm |
| 1401 | + """ |
| 1402 | + |
| 1403 | + |
| 1404 | + raise_if_backend_is_not_python( |
| 1405 | + maximum_matching, kwargs.get('backend', Backend.PYTHON)) |
| 1406 | + make_undirected = kwargs.get('make_undirected', False) |
| 1407 | + if make_undirected: |
| 1408 | + graph = graph.to_undirected_adjacency_list() |
| 1409 | + |
| 1410 | + import pydatastructs.graphs.algorithms as algorithms |
| 1411 | + func = "_maximum_matching_" + algorithm + "_" |
| 1412 | + if not hasattr(algorithms, func): |
| 1413 | + raise NotImplementedError( |
| 1414 | + f"Currently {algorithm} algorithm isn't implemented for " |
| 1415 | + "finding maximum matching in graphs.") |
| 1416 | + return getattr(algorithms, func)(graph) |
| 1417 | + |
| 1418 | +def _maximum_matching_hopcroft_karp_parallel(graph: Graph, num_threads: int) -> set: |
| 1419 | + U = set() |
| 1420 | + V = set() |
| 1421 | + bipartiteness, coloring = bipartite_coloring(graph) |
| 1422 | + |
| 1423 | + if not bipartiteness: |
| 1424 | + raise ValueError("Graph is not bipartite.") |
| 1425 | + |
| 1426 | + for node, c in coloring.items(): |
| 1427 | + if c == 0: |
| 1428 | + U.add(node) |
| 1429 | + else: |
| 1430 | + V.add(node) |
| 1431 | + |
| 1432 | + |
| 1433 | + pair_U = {u: None for u in U} |
| 1434 | + pair_V = {v: None for v in V} |
| 1435 | + dist = {} |
| 1436 | + |
| 1437 | + def bfs(): |
| 1438 | + queue = Queue() |
| 1439 | + for u in U: |
| 1440 | + if pair_U[u] is None: |
| 1441 | + dist[u] = 0 |
| 1442 | + queue.append(u) |
| 1443 | + else: |
| 1444 | + dist[u] = float('inf') |
| 1445 | + dist[None] = float('inf') |
| 1446 | + while queue: |
| 1447 | + u = queue.popleft() |
| 1448 | + if dist[u] < dist[None]: |
| 1449 | + for v in graph.neighbors(u): |
| 1450 | + if dist[pair_V[v.name]] == float('inf'): |
| 1451 | + dist[pair_V[v.name]] = dist[u] + 1 |
| 1452 | + queue.append(pair_V[v.name]) |
| 1453 | + return dist[None] != float('inf') |
| 1454 | + |
| 1455 | + def dfs(u): |
| 1456 | + if u is not None: |
| 1457 | + for v in graph.neighbors(u): |
| 1458 | + if dist[pair_V[v.name]] == dist[u] + 1: |
| 1459 | + if dfs(pair_V[v.name]): |
| 1460 | + pair_V[v.name] = u |
| 1461 | + pair_U[u] = v.name |
| 1462 | + return True |
| 1463 | + dist[u] = float('inf') |
| 1464 | + return False |
| 1465 | + return True |
| 1466 | + |
| 1467 | + matching = set() |
| 1468 | + |
| 1469 | + while bfs(): |
| 1470 | + unmatched_nodes = [u for u in U if pair_U[u] is None] |
| 1471 | + |
| 1472 | + with ThreadPoolExecutor(max_workers=num_threads) as Executor: |
| 1473 | + results = Executor.map(dfs, unmatched_nodes) |
| 1474 | + |
| 1475 | + for u, success in zip(unmatched_nodes, results): |
| 1476 | + if success: |
| 1477 | + matching.add((u, pair_U[u])) |
| 1478 | + |
| 1479 | + return matching |
| 1480 | + |
| 1481 | + |
| 1482 | +def maximum_matching_parallel(graph: Graph, algorithm: str, num_threads: int, **kwargs): |
| 1483 | + """ |
| 1484 | + Finds the maximum matching in the given graph using the given algorithm using |
| 1485 | + the given number of threads. |
| 1486 | +
|
| 1487 | + Parameters |
| 1488 | + ========== |
| 1489 | +
|
| 1490 | + graph: Graph |
| 1491 | + The graph under consideration. |
| 1492 | + algorithm: str |
| 1493 | + The algorithm to be used. |
| 1494 | + Currently, following are supported, |
| 1495 | +
|
| 1496 | + 'hopcroft_karp' -> Hopcroft-Karp algorithm for Bipartite Graphs as given in [1]. |
| 1497 | + num_threads: int |
| 1498 | + The maximum number of threads to be used. |
| 1499 | + backend: pydatastructs.Backend |
| 1500 | + The backend to be used. |
| 1501 | + Optional, by default, the best available |
| 1502 | + backend is used. |
| 1503 | +
|
| 1504 | + Returns |
| 1505 | + ======= |
| 1506 | +
|
| 1507 | + set |
| 1508 | + The set of edges which form the maximum matching. |
| 1509 | +
|
| 1510 | + Examples |
| 1511 | + ======== |
| 1512 | +
|
| 1513 | + >>> from pydatastructs import Graph, AdjacencyListGraphNode, maximum_matching_parallel |
| 1514 | + >>> v_1 = AdjacencyListGraphNode('v_1') |
| 1515 | + >>> v_2 = AdjacencyListGraphNode('v_2') |
| 1516 | + >>> v_3 = AdjacencyListGraphNode('v_3') |
| 1517 | + >>> v_4 = AdjacencyListGraphNode('v_4') |
| 1518 | + >>> graph = Graph(v_1, v_2, v_3, v_4) |
| 1519 | + >>> graph.add_bidirectional_edge('v_1', 'v_2') |
| 1520 | + >>> graph.add_bidirectional_edge('v_2', 'v_3') |
| 1521 | + >>> graph.add_bidirectional_edge('v_4', 'v_1') |
| 1522 | + >>> maximum_matching_parallel(graph, 'hopcroft_karp', 1) |
| 1523 | + >>> {('v_1', 'v_4'), ('v_3', 'v_2')} |
| 1524 | +
|
| 1525 | + References |
| 1526 | + ========== |
| 1527 | +
|
| 1528 | + .. [1] https://en.wikipedia.org/wiki/Hopcroft%E2%80%93Karp_algorithm |
| 1529 | + """ |
| 1530 | + |
| 1531 | + raise_if_backend_is_not_python( |
| 1532 | + maximum_matching_parallel, kwargs.get('backend', Backend.PYTHON)) |
| 1533 | + |
| 1534 | + import pydatastructs.graphs.algorithms as algorithms |
| 1535 | + func = "_maximum_matching_" + algorithm + "_parallel" |
| 1536 | + if not hasattr(algorithms, func): |
| 1537 | + raise NotImplementedError( |
| 1538 | + f"Currently {algorithm} algorithm isn't implemented for " |
| 1539 | + "finding maximum matching in graphs.") |
| 1540 | + return getattr(algorithms, func)(graph, num_threads) |
0 commit comments