From 6fbdf2da5bcac53c16f400f89ea83098581c02ba Mon Sep 17 00:00:00 2001 From: Hargun Kaur Date: Sun, 16 Mar 2025 03:18:34 +0530 Subject: [PATCH] Added Bipartite Algorithm Signed-off-by: Hargun Kaur --- pydatastructs/graphs/__init__.py | 3 +- pydatastructs/graphs/algorithms.py | 57 ++++++++++++++++++- pydatastructs/graphs/tests/test_algorithms.py | 37 +++++++++++- 3 files changed, 94 insertions(+), 3 deletions(-) diff --git a/pydatastructs/graphs/__init__.py b/pydatastructs/graphs/__init__.py index 9c00ca0aa..2cc69a52d 100644 --- a/pydatastructs/graphs/__init__.py +++ b/pydatastructs/graphs/__init__.py @@ -21,7 +21,8 @@ all_pair_shortest_paths, topological_sort, topological_sort_parallel, - max_flow + max_flow, + is_bipartite ) __all__.extend(algorithms.__all__) diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 9de50e7cc..bf063540b 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -23,7 +23,8 @@ 'all_pair_shortest_paths', 'topological_sort', 'topological_sort_parallel', - 'max_flow' + 'max_flow', + 'is_bipartite' ] Stack = Queue = deque @@ -1216,3 +1217,57 @@ def max_flow(graph, source, sink, algorithm='edmonds_karp', **kwargs): f"Currently {algorithm} algorithm isn't implemented for " "performing max flow on graphs.") return getattr(algorithms, func)(graph, source, sink) + +def is_bipartite(graph): + """ + Determines whether the given undirected graph is bipartite using BFS. + + Parameters + ========== + graph : Graph + An undirected graph instance. + + Returns + ======= + (bool, dict) + A tuple where the first element is True if the graph is bipartite and False otherwise. + The second element is a dictionary mapping each vertex (its name) to a color (0 or 1) + if the graph is bipartite; if not, the dictionary may be partially filled. + + Examples + ======== + >>> from pydatastructs import Graph, AdjacencyListGraphNode, is_bipartite + >>> v0 = AdjacencyListGraphNode(0) + >>> v1 = AdjacencyListGraphNode(1) + >>> v2 = AdjacencyListGraphNode(2) + >>> v3 = AdjacencyListGraphNode(3) + >>> graph = Graph(v0, v1, v2, v3, implementation='adjacency_list') + >>> graph.add_edge(v0.name, v1.name) + >>> graph.add_edge(v1.name, v2.name) + >>> graph.add_edge(v2.name, v3.name) + >>> graph.add_edge(v3.name, v0.name) + >>> is_bipartite(graph) + (True, {'0': 0, '1': 1, '2': 0, '3': 1}) + + References + ========== + .. [1] https://en.wikipedia.org/wiki/Bipartite_graph + """ + from collections import deque + color = {} + + for vertex in graph.vertices: + vertex_name = vertex.name if hasattr(vertex, "name") else vertex + if vertex_name not in color: + color[vertex_name] = 0 + queue = deque([vertex_name]) + while queue: + u = queue.popleft() + for neighbor in graph.neighbors(u): + v = neighbor.name if hasattr(neighbor, "name") else neighbor + if v not in color: + color[v] = 1 - color[u] + queue.append(v) + elif color[v] == color[u]: + return (False, color) + return (True, color) diff --git a/pydatastructs/graphs/tests/test_algorithms.py b/pydatastructs/graphs/tests/test_algorithms.py index f1586f512..5feefcc30 100644 --- a/pydatastructs/graphs/tests/test_algorithms.py +++ b/pydatastructs/graphs/tests/test_algorithms.py @@ -2,7 +2,7 @@ breadth_first_search_parallel, minimum_spanning_tree, minimum_spanning_tree_parallel, strongly_connected_components, depth_first_search, shortest_paths,all_pair_shortest_paths, topological_sort, -topological_sort_parallel, max_flow) +topological_sort_parallel, max_flow, is_bipartite) from pydatastructs.utils.raises_util import raises def test_breadth_first_search(): @@ -448,3 +448,38 @@ def _test_max_flow(ds, algorithm): _test_max_flow("Matrix", "edmonds_karp") _test_max_flow("List", "dinic") _test_max_flow("Matrix", "dinic") + +def test_is_bipartite(): + import pydatastructs.utils.misc_util as utils + def _test_bipartite(ds): + GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode") + impl = 'adjacency_list' if ds == "List" else 'adjacency_matrix' + + v0 = GraphNode(0) + v1 = GraphNode(1) + v2 = GraphNode(2) + v3 = GraphNode(3) + graph = Graph(v0, v1, v2, v3, implementation=impl) + graph.add_edge(v0.name, v1.name) + graph.add_edge(v1.name, v2.name) + graph.add_edge(v2.name, v3.name) + graph.add_edge(v3.name, v0.name) + bip, colors = is_bipartite(graph) + assert bip is True + assert colors[v0.name] != colors[v1.name] + assert colors[v1.name] != colors[v2.name] + assert colors[v2.name] != colors[v3.name] + assert colors[v3.name] != colors[v0.name] + + u0 = GraphNode(0) + u1 = GraphNode(1) + u2 = GraphNode(2) + graph = Graph(u0, u1, u2, implementation=impl) + graph.add_edge(u0.name, u1.name) + graph.add_edge(u1.name, u2.name) + graph.add_edge(u2.name, u0.name) + bip, _ = is_bipartite(graph) + assert bip is False + + _test_bipartite("List") + _test_bipartite("Matrix")