Skip to content

Commit 3e6e861

Browse files
authored
Added Serial BFS (#126)
1 parent d80e35e commit 3e6e861

File tree

6 files changed

+187
-6
lines changed

6 files changed

+187
-6
lines changed

pydatastructs/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
from .trees import *
33
from .miscellaneous_data_structures import *
44
from .utils import *
5+
from .graphs import *

pydatastructs/graphs/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,10 @@
55
Graph
66
)
77
__all__.extend(graph.__all__)
8+
9+
from . import algorithms
10+
from .algorithms import (
11+
breadth_first_search
12+
)
13+
14+
__all__.extend(algorithms.__all__)

pydatastructs/graphs/algorithms.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"""
2+
Contains all the algorithms associated with graph
3+
data structure.
4+
"""
5+
# TODO: REPLACE COLLECTIONS QUEUE WITH PYDATASTRUCTS QUEUE
6+
from collections import deque as Queue
7+
from pydatastructs.utils.misc_util import AdjacencyListGraphNode
8+
9+
__all__ = [
10+
'breadth_first_search',
11+
]
12+
13+
def breadth_first_search(
14+
graph, source_node, operation, *args, **kwargs):
15+
"""
16+
Implementation of serial breadth first search(BFS)
17+
algorithm.
18+
19+
Parameters
20+
==========
21+
22+
graph: Graph
23+
The graph on which BFS is to be performed.
24+
source_node: str
25+
The name of the source node from where the BFS is
26+
to be initiated.
27+
operation: function
28+
The function which is to be applied
29+
on every node when it is visited.
30+
The prototype which is to be followed is,
31+
`function_name(curr_node, next_node,
32+
arg_1, arg_2, . . ., arg_n)`.
33+
Here, the first two arguments denote, the
34+
current node and the node next to current node.
35+
The rest of the arguments are optional and you can
36+
provide your own stuff there.
37+
38+
Note
39+
====
40+
41+
You should pass all the arguments which you are going
42+
to use in the prototype of your `operation` after
43+
passing the operation function.
44+
45+
Examples
46+
========
47+
48+
>>> from pydatastructs import Graph, AdjacencyListGraphNode
49+
>>> V1 = AdjacencyListGraphNode("V1")
50+
>>> V2 = AdjacencyListGraphNode("V2")
51+
>>> V3 = AdjacencyListGraphNode("V3")
52+
>>> G = Graph(V1, V2, V3)
53+
>>> from pydatastructs import breadth_first_search
54+
>>> def f(curr_node, next_node, dest_node):
55+
... return curr_node != dest_node
56+
...
57+
>>> G.add_edge(V1.name, V2.name)
58+
>>> G.add_edge(V2.name, V3.name)
59+
>>> breadth_first_search(G, V1.name, f, V3.name)
60+
"""
61+
import pydatastructs.graphs.algorithms as algorithms
62+
func = "_breadth_first_search_" + graph._impl
63+
if not hasattr(algorithms, func):
64+
raise NotImplementedError(
65+
"Currently breadth first search isn't implemented for "
66+
"%s graphs."%(graph._impl))
67+
return getattr(algorithms, func)(
68+
graph, source_node, operation, *args, **kwargs)
69+
70+
def _breadth_first_search_adjacency_list(
71+
graph, source_node, operation, *args, **kwargs):
72+
bfs_queue = Queue()
73+
visited = dict()
74+
bfs_queue.append(source_node)
75+
visited[source_node] = True
76+
while len(bfs_queue) != 0:
77+
curr_node = bfs_queue.popleft()
78+
next_nodes = graph.neighbors(curr_node)
79+
if len(next_nodes) != 0:
80+
for next_node in next_nodes:
81+
if visited.get(next_node.name, False) is False:
82+
status = operation(curr_node, next_node.name, *args, **kwargs)
83+
if not status:
84+
return None
85+
bfs_queue.append(next_node.name)
86+
visited[next_node.name] = True
87+
else:
88+
status = operation(curr_node, "", *args, **kwargs)
89+
if not status:
90+
return None
91+
92+
_breadth_first_search_adjacency_matrix = _breadth_first_search_adjacency_list

pydatastructs/graphs/graph.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,21 @@ class Graph(object):
4343
4444
.. [1] https://en.wikipedia.org/wiki/Graph_(abstract_data_type)
4545
"""
46+
47+
__slots__ = ['_impl']
48+
4649
def __new__(cls, *args, **kwargs):
4750
implementation = kwargs.get('implementation', 'adjacency_list')
4851
if implementation is 'adjacency_list':
4952
from pydatastructs.graphs.adjacency_list import AdjacencyList
50-
return AdjacencyList(*args)
53+
obj = AdjacencyList(*args)
54+
obj._impl = implementation
55+
return obj
5156
elif implementation is 'adjacency_matrix':
5257
from pydatastructs.graphs.adjacency_matrix import AdjacencyMatrix
53-
return AdjacencyMatrix(*args)
58+
obj = AdjacencyMatrix(*args)
59+
obj._impl = implementation
60+
return obj
5461
else:
5562
raise NotImplementedError("%s implementation is not a part "
5663
"of the library currently."%(implementation))
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from pydatastructs import breadth_first_search, Graph
2+
3+
4+
def test_breadth_first_search():
5+
6+
def _test_breadth_first_search(ds, impl):
7+
import pydatastructs.utils.misc_util as utils
8+
GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode")
9+
10+
V1 = GraphNode(0)
11+
V2 = GraphNode(1)
12+
V3 = GraphNode(2)
13+
14+
G1 = Graph(V1, V2, V3, implementation=impl)
15+
16+
edges = [
17+
(V1.name, V2.name),
18+
(V2.name, V3.name),
19+
(V1.name, V3.name)
20+
]
21+
22+
for edge in edges:
23+
G1.add_edge(*edge)
24+
25+
parent = dict()
26+
def bfs_tree(curr_node, next_node, parent):
27+
if next_node != "":
28+
parent[next_node] = curr_node
29+
return True
30+
31+
breadth_first_search(G1, V1.name, bfs_tree, parent)
32+
assert (parent[V3.name] == V1.name and parent[V2.name] == V1.name) or \
33+
(parent[V3.name] == V2.name and parent[V2.name] == V1.name)
34+
35+
V4 = GraphNode(0)
36+
V5 = GraphNode(1)
37+
V6 = GraphNode(2)
38+
V7 = GraphNode(3)
39+
V8 = GraphNode(4)
40+
41+
edges = [
42+
(V4.name, V5.name),
43+
(V5.name, V6.name),
44+
(V6.name, V7.name),
45+
(V6.name, V4.name),
46+
(V7.name, V8.name)
47+
]
48+
49+
G2 = Graph(V4, V5, V6, V7, V8, implementation=impl)
50+
51+
for edge in edges:
52+
G2.add_edge(*edge)
53+
54+
path = []
55+
def path_finder(curr_node, next_node, dest_node, parent, path):
56+
if next_node != "":
57+
parent[next_node] = curr_node
58+
if curr_node == dest_node:
59+
node = curr_node
60+
path.append(node)
61+
while node is not None:
62+
if parent.get(node, None) is not None:
63+
path.append(parent[node])
64+
node = parent.get(node, None)
65+
path.reverse()
66+
return False
67+
return True
68+
69+
parent.clear()
70+
breadth_first_search(G2, V4.name, path_finder, V7.name, parent, path)
71+
assert path == [V4.name, V5.name, V6.name, V7.name]
72+
73+
_test_breadth_first_search("List", "adjacency_list")
74+
_test_breadth_first_search("Matrix", "adjacency_matrix")

pydatastructs/utils/misc_util.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,9 @@ class AdjacencyListGraphNode(GraphNode):
150150
nodes of the current node.
151151
Optional, by default, None
152152
"""
153-
def __new__(cls, name, data, adjacency_list=None):
153+
def __new__(cls, name, data=None, adjacency_list=None):
154154
obj = GraphNode.__new__(cls)
155-
obj.name, obj.data = name, data
155+
obj.name, obj.data = str(name), data
156156
if adjacency_list is not None:
157157
for node in adjacency_list:
158158
obj.__setattr__(node.name, node)
@@ -197,10 +197,10 @@ class AdjacencyMatrixGraphNode(GraphNode):
197197
"""
198198
__slots__ = ['name', 'data']
199199

200-
def __new__(cls, name, data):
200+
def __new__(cls, name, data=None):
201201
obj = GraphNode.__new__(cls)
202202
obj.name, obj.data, obj.is_connected = \
203-
name, data, None
203+
int(name), data, None
204204
return obj
205205

206206
class GraphEdge(object):

0 commit comments

Comments
 (0)