Skip to content

Commit 5a78527

Browse files
feat: Add Bron Kerbosch algorithm to find maximal cliques
1 parent eb5fab1 commit 5a78527

File tree

4 files changed

+126
-12
lines changed

4 files changed

+126
-12
lines changed

docs/source/pydatastructs/graphs/algorithms.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ Algorithms
2121

2222
.. autofunction:: pydatastructs.topological_sort_parallel
2323

24-
.. autofunction:: pydatastructs.find_bridges
24+
.. autofunction:: pydatastructs.find_bridges
25+
26+
.. autofunction:: pydatastructs.find_maximal_cliques

pydatastructs/graphs/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
topological_sort,
2323
topological_sort_parallel,
2424
max_flow,
25-
find_bridges
25+
find_bridges,
26+
find_maximal_cliques
2627
)
2728

2829
__all__.extend(algorithms.__all__)

pydatastructs/graphs/algorithms.py

Lines changed: 86 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
'topological_sort',
2525
'topological_sort_parallel',
2626
'max_flow',
27-
'find_bridges'
27+
'find_bridges',
28+
'find_maximal_cliques'
2829
]
2930

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

1172-
11731173
def _breadth_first_search_max_flow(graph: Graph, source_node, sink_node, flow_passed, for_dinic=False):
11741174
bfs_queue = Queue()
11751175
parent, currentPathC = {}, {}
@@ -1191,7 +1191,6 @@ def _breadth_first_search_max_flow(graph: Graph, source_node, sink_node, flow_pa
11911191
bfs_queue.append(next_node.name)
11921192
return (0, parent)
11931193

1194-
11951194
def _max_flow_edmonds_karp_(graph: Graph, source, sink):
11961195
m_flow = 0
11971196
flow_passed = {}
@@ -1209,7 +1208,6 @@ def _max_flow_edmonds_karp_(graph: Graph, source, sink):
12091208
new_flow, parent = _breadth_first_search_max_flow(graph, source, sink, flow_passed)
12101209
return m_flow
12111210

1212-
12131211
def _depth_first_search_max_flow_dinic(graph: Graph, u, parent, sink_node, flow, flow_passed):
12141212
if u == sink_node:
12151213
return flow
@@ -1233,7 +1231,6 @@ def _depth_first_search_max_flow_dinic(graph: Graph, u, parent, sink_node, flow,
12331231
return path_flow
12341232
return 0
12351233

1236-
12371234
def _max_flow_dinic_(graph: Graph, source, sink):
12381235
max_flow = 0
12391236
flow_passed = {}
@@ -1253,7 +1250,6 @@ def _max_flow_dinic_(graph: Graph, source, sink):
12531250

12541251
return max_flow
12551252

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

1269-
12701265
def find_bridges(graph):
12711266
"""
12721267
Finds all bridges in an undirected graph using Tarjan's Algorithm.
@@ -1368,3 +1363,87 @@ def dfs(u):
13681363
bridges.append((b, a))
13691364
bridges.sort()
13701365
return bridges
1366+
1367+
def _bron_kerbosc(graph: Graph, set_r: set, set_p: set, set_x: set, cliques: list):
1368+
if not set_p and not set_x:
1369+
cliques.append(sorted(list(set_r)))
1370+
return
1371+
1372+
for v in list(set_p):
1373+
neighbor_nodes = graph.neighbors(v)
1374+
neighbors = set(n.name for n in neighbor_nodes)
1375+
_bron_kerbosc(graph, set_r.union({v}), set_p.intersection(neighbors),
1376+
set_x.intersection(neighbors), cliques)
1377+
set_p.remove(v)
1378+
set_x.add(v)
1379+
1380+
def _find_maximal_cliques_bron_kerbosc_adjacency_list(graph: Graph) -> list:
1381+
cliques = []
1382+
vertices = set(graph.vertices)
1383+
_bron_kerbosc(graph, set(), vertices, set(), cliques)
1384+
return sorted(cliques)
1385+
1386+
_find_maximal_cliques_bron_kerbosc_adjacency_matrix = \
1387+
_find_maximal_cliques_bron_kerbosc_adjacency_list
1388+
1389+
def find_maximal_cliques(graph: Graph, algorithm: str, **kwargs) -> list:
1390+
"""
1391+
Finds maximal cliques for an undirected graph.
1392+
1393+
Parameters
1394+
==========
1395+
1396+
graph: Graph
1397+
The graph under consideration.
1398+
algorithm: str
1399+
The algorithm to be used. Currently, the following algorithms
1400+
are implemented,
1401+
1402+
'bron_kerbosc' -> Bron Kerbosc algorithm as given in [1].
1403+
backend: pydatastructs.Backend
1404+
The backend to be used.
1405+
Optional, by default, the best available
1406+
backend is used.
1407+
1408+
Returns
1409+
=======
1410+
1411+
cliques: list
1412+
Python list with each element as list of vertices.
1413+
1414+
Examples
1415+
========
1416+
1417+
>>> from pydatastructs import Graph, AdjacencyListGraphNode
1418+
>>> from pydatastructs import find_maximal_cliques
1419+
>>> V1 = AdjacencyListGraphNode('V1')
1420+
>>> V2 = AdjacencyListGraphNode('V2')
1421+
>>> V3 = AdjacencyListGraphNode('V3')
1422+
>>> V4 = AdjacencyListGraphNode('V4')
1423+
>>> G = Graph(V1, V2, V3, V4)
1424+
>>> G.add_edge('V1', 'V2')
1425+
>>> G.add_edge('V2', 'V1')
1426+
>>> G.add_edge('V2', 'V3')
1427+
>>> G.add_edge('V3', 'V2')
1428+
>>> G.add_edge('V1', 'V3')
1429+
>>> G.add_edge('V3', 'V1')
1430+
>>> G.add_edge('V1', 'V4')
1431+
>>> G.add_edge('V4', 'V1')
1432+
>>> find_maximal_cliques(G, 'bron_kerbosc')
1433+
[['V1', 'V2', 'V3'], ['V1', 'V4']]
1434+
1435+
References
1436+
==========
1437+
1438+
.. [1] https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm
1439+
"""
1440+
raise_if_backend_is_not_python(
1441+
find_maximal_cliques, kwargs.get('backend', Backend.PYTHON))
1442+
1443+
import pydatastructs.graphs.algorithms as algorithms
1444+
func = "_find_maximal_cliques_" + algorithm + "_" + graph._impl
1445+
if not hasattr(algorithms, func):
1446+
raise NotImplementedError(
1447+
f"Currently {algorithm} algorithm isn't implemented for "
1448+
"finding maximal cliques on graphs.")
1449+
return getattr(algorithms, func)(graph)

pydatastructs/graphs/tests/test_algorithms.py

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
breadth_first_search_parallel, minimum_spanning_tree,
33
minimum_spanning_tree_parallel, strongly_connected_components,
44
depth_first_search, shortest_paths,all_pair_shortest_paths, topological_sort,
5-
topological_sort_parallel, max_flow, find_bridges)
5+
topological_sort_parallel, max_flow, find_bridges, find_maximal_cliques)
66
from pydatastructs.utils.raises_util import raises
77

88
def test_breadth_first_search():
@@ -382,8 +382,8 @@ def _test_topological_sort(func, ds, algorithm, threads=None):
382382
_test_topological_sort(topological_sort, "List", "kahn")
383383
_test_topological_sort(topological_sort_parallel, "List", "kahn", 3)
384384

385-
386385
def test_max_flow():
386+
387387
def _test_max_flow(ds, algorithm):
388388
import pydatastructs.utils.misc_util as utils
389389
GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode")
@@ -451,8 +451,8 @@ def _test_max_flow(ds, algorithm):
451451
_test_max_flow("List", "dinic")
452452
_test_max_flow("Matrix", "dinic")
453453

454-
455454
def test_find_bridges():
455+
456456
def _test_find_bridges(ds):
457457
import pydatastructs.utils.misc_util as utils
458458
GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode")
@@ -504,3 +504,35 @@ def _test_find_bridges(ds):
504504

505505
_test_find_bridges("List")
506506
_test_find_bridges("Matrix")
507+
508+
def test_maximal_cliques():
509+
510+
def _test_maximal_cliques(ds, algorithm):
511+
import pydatastructs.utils.misc_util as utils
512+
GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode")
513+
a, b, c, d, e, f = \
514+
[GraphNode(chr(x)) for x in range(ord('a'), ord('f') + 1)]
515+
516+
graph = Graph(a, b, c, d, e, f)
517+
518+
graph.add_edge(a.name, b.name)
519+
graph.add_edge(a.name, e.name)
520+
graph.add_edge(b.name, a.name)
521+
graph.add_edge(b.name, c.name)
522+
graph.add_edge(b.name, e.name)
523+
graph.add_edge(c.name, b.name)
524+
graph.add_edge(c.name, d.name)
525+
graph.add_edge(d.name, c.name)
526+
graph.add_edge(d.name, e.name)
527+
graph.add_edge(d.name, f.name)
528+
graph.add_edge(e.name, a.name)
529+
graph.add_edge(e.name, b.name)
530+
graph.add_edge(e.name, d.name)
531+
graph.add_edge(f.name, d.name)
532+
533+
cliques = find_maximal_cliques(graph, algorithm)
534+
expected_cliques = [['a', 'b', 'e'], ['b', 'c'], ['c', 'd'], ['d', 'e'], ['d', 'f']]
535+
assert cliques == expected_cliques
536+
537+
_test_maximal_cliques("List", "bron_kerbosc")
538+
_test_maximal_cliques("Matrix", "bron_kerbosc")

0 commit comments

Comments
 (0)