Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Bron Kerbosch algorithm to find maximal cliques #658

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/source/pydatastructs/graphs/algorithms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ Algorithms

.. autofunction:: pydatastructs.topological_sort_parallel

.. autofunction:: pydatastructs.find_bridges
.. autofunction:: pydatastructs.find_bridges

.. autofunction:: pydatastructs.find_maximal_cliques
3 changes: 2 additions & 1 deletion pydatastructs/graphs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
topological_sort,
topological_sort_parallel,
max_flow,
find_bridges
find_bridges,
find_maximal_cliques
)

__all__.extend(algorithms.__all__)
93 changes: 86 additions & 7 deletions pydatastructs/graphs/algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
'topological_sort',
'topological_sort_parallel',
'max_flow',
'find_bridges'
'find_bridges',
'find_maximal_cliques'
]

Stack = Queue = deque
Expand Down Expand Up @@ -1169,7 +1170,6 @@ def _job(graph: Graph, u: str):
raise ValueError("Graph is not acyclic.")
return L


def _breadth_first_search_max_flow(graph: Graph, source_node, sink_node, flow_passed, for_dinic=False):
bfs_queue = Queue()
parent, currentPathC = {}, {}
Expand All @@ -1191,7 +1191,6 @@ def _breadth_first_search_max_flow(graph: Graph, source_node, sink_node, flow_pa
bfs_queue.append(next_node.name)
return (0, parent)


def _max_flow_edmonds_karp_(graph: Graph, source, sink):
m_flow = 0
flow_passed = {}
Expand All @@ -1209,7 +1208,6 @@ def _max_flow_edmonds_karp_(graph: Graph, source, sink):
new_flow, parent = _breadth_first_search_max_flow(graph, source, sink, flow_passed)
return m_flow


def _depth_first_search_max_flow_dinic(graph: Graph, u, parent, sink_node, flow, flow_passed):
if u == sink_node:
return flow
Expand All @@ -1233,7 +1231,6 @@ def _depth_first_search_max_flow_dinic(graph: Graph, u, parent, sink_node, flow,
return path_flow
return 0


def _max_flow_dinic_(graph: Graph, source, sink):
max_flow = 0
flow_passed = {}
Expand All @@ -1253,7 +1250,6 @@ def _max_flow_dinic_(graph: Graph, source, sink):

return max_flow


def max_flow(graph, source, sink, algorithm='edmonds_karp', **kwargs):
raise_if_backend_is_not_python(
max_flow, kwargs.get('backend', Backend.PYTHON))
Expand All @@ -1266,7 +1262,6 @@ def max_flow(graph, source, sink, algorithm='edmonds_karp', **kwargs):
"performing max flow on graphs.")
return getattr(algorithms, func)(graph, source, sink)


def find_bridges(graph):
"""
Finds all bridges in an undirected graph using Tarjan's Algorithm.
Expand Down Expand Up @@ -1368,3 +1363,87 @@ def dfs(u):
bridges.append((b, a))
bridges.sort()
return bridges

def _bron_kerbosc(graph: Graph, set_r: set, set_p: set, set_x: set, cliques: list):
if not set_p and not set_x:
cliques.append(sorted(list(set_r)))
return

for v in list(set_p):
neighbor_nodes = graph.neighbors(v)
neighbors = set(n.name for n in neighbor_nodes)
_bron_kerbosc(graph, set_r.union({v}), set_p.intersection(neighbors),
set_x.intersection(neighbors), cliques)
set_p.remove(v)
set_x.add(v)

def _find_maximal_cliques_bron_kerbosc_adjacency_list(graph: Graph) -> list:
cliques = []
vertices = set(graph.vertices)
_bron_kerbosc(graph, set(), vertices, set(), cliques)
return sorted(cliques)

_find_maximal_cliques_bron_kerbosc_adjacency_matrix = \
_find_maximal_cliques_bron_kerbosc_adjacency_list

def find_maximal_cliques(graph: Graph, algorithm: str, **kwargs) -> list:
"""
Finds maximal cliques for an undirected graph.

Parameters
==========

graph: Graph
The graph under consideration.
algorithm: str
The algorithm to be used. Currently, the following algorithms
are implemented,

'bron_kerbosc' -> Bron Kerbosc algorithm as given in [1].
backend: pydatastructs.Backend
The backend to be used.
Optional, by default, the best available
backend is used.

Returns
=======

cliques: list
Python list with each element as list of vertices.

Examples
========

>>> from pydatastructs import Graph, AdjacencyListGraphNode
>>> from pydatastructs import find_maximal_cliques
>>> V1 = AdjacencyListGraphNode('V1')
>>> V2 = AdjacencyListGraphNode('V2')
>>> V3 = AdjacencyListGraphNode('V3')
>>> V4 = AdjacencyListGraphNode('V4')
>>> G = Graph(V1, V2, V3, V4)
>>> G.add_edge('V1', 'V2')
>>> G.add_edge('V2', 'V1')
>>> G.add_edge('V2', 'V3')
>>> G.add_edge('V3', 'V2')
>>> G.add_edge('V1', 'V3')
>>> G.add_edge('V3', 'V1')
>>> G.add_edge('V1', 'V4')
>>> G.add_edge('V4', 'V1')
>>> find_maximal_cliques(G, 'bron_kerbosc')
[['V1', 'V2', 'V3'], ['V1', 'V4']]

References
==========

.. [1] https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm
"""
raise_if_backend_is_not_python(
find_maximal_cliques, kwargs.get('backend', Backend.PYTHON))

import pydatastructs.graphs.algorithms as algorithms
func = "_find_maximal_cliques_" + algorithm + "_" + graph._impl
if not hasattr(algorithms, func):
raise NotImplementedError(
f"Currently {algorithm} algorithm isn't implemented for "
"finding maximal cliques on graphs.")
return getattr(algorithms, func)(graph)
38 changes: 35 additions & 3 deletions pydatastructs/graphs/tests/test_algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, find_bridges)
topological_sort_parallel, max_flow, find_bridges, find_maximal_cliques)
from pydatastructs.utils.raises_util import raises

def test_breadth_first_search():
Expand Down Expand Up @@ -382,8 +382,8 @@ def _test_topological_sort(func, ds, algorithm, threads=None):
_test_topological_sort(topological_sort, "List", "kahn")
_test_topological_sort(topological_sort_parallel, "List", "kahn", 3)


def test_max_flow():

def _test_max_flow(ds, algorithm):
import pydatastructs.utils.misc_util as utils
GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode")
Expand Down Expand Up @@ -451,8 +451,8 @@ def _test_max_flow(ds, algorithm):
_test_max_flow("List", "dinic")
_test_max_flow("Matrix", "dinic")


def test_find_bridges():

def _test_find_bridges(ds):
import pydatastructs.utils.misc_util as utils
GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode")
Expand Down Expand Up @@ -504,3 +504,35 @@ def _test_find_bridges(ds):

_test_find_bridges("List")
_test_find_bridges("Matrix")

def test_maximal_cliques():

def _test_maximal_cliques(ds, algorithm):
import pydatastructs.utils.misc_util as utils
GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode")
a, b, c, d, e, f = \
[GraphNode(chr(x)) for x in range(ord('a'), ord('f') + 1)]

graph = Graph(a, b, c, d, e, f)

graph.add_edge(a.name, b.name)
graph.add_edge(a.name, e.name)
graph.add_edge(b.name, a.name)
graph.add_edge(b.name, c.name)
graph.add_edge(b.name, e.name)
graph.add_edge(c.name, b.name)
graph.add_edge(c.name, d.name)
graph.add_edge(d.name, c.name)
graph.add_edge(d.name, e.name)
graph.add_edge(d.name, f.name)
graph.add_edge(e.name, a.name)
graph.add_edge(e.name, b.name)
graph.add_edge(e.name, d.name)
graph.add_edge(f.name, d.name)

cliques = find_maximal_cliques(graph, algorithm)
expected_cliques = [['a', 'b', 'e'], ['b', 'c'], ['c', 'd'], ['d', 'e'], ['d', 'f']]
assert cliques == expected_cliques

_test_maximal_cliques("List", "bron_kerbosc")
_test_maximal_cliques("Matrix", "bron_kerbosc")
Loading