Skip to content

Commit f6e5109

Browse files
authored
Merge branch 'master' into dijkstra_e_log_v
2 parents 99d577a + d5760a1 commit f6e5109

6 files changed

Lines changed: 124 additions & 22 deletions

File tree

core/pygraph/algorithms/minmax.py

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,35 +31,38 @@
3131
"""
3232
Minimization and maximization algorithms.
3333
34-
@sort: heuristic_search, minimal_spanning_tree, shortest_path,
35-
shortest_path_bellman_ford
34+
@sort: heuristic_search, minimal_spanning_tree_prim,
35+
minimal_spanning_tree_kruskal, shortest_path, shortest_path_bellman_ford
3636
"""
3737

3838
from pygraph.algorithms.utils import heappush, heappop
3939
from pygraph.classes.exceptions import NodeUnreachable
4040
from pygraph.classes.exceptions import NegativeWeightCycleError
4141
from pygraph.classes.digraph import digraph
42+
from pygraph.classes.unionfind import UnionFind
4243
import heapq
44+
import bisect
4345

4446
# Minimal spanning tree
4547

46-
def minimal_spanning_tree(graph, root=None):
48+
49+
def minimal_spanning_tree_prim(graph, root=None, parallel=None):
4750
"""
48-
Minimal spanning tree.
51+
Minimal spanning tree constructed with prim's algorithm.
4952
5053
@attention: Minimal spanning tree is meaningful only for weighted graphs.
5154
5255
@type graph: graph
5356
@param graph: Graph.
54-
57+
5558
@type root: node
5659
@param root: Optional root node (will explore only root's connected component)
5760
5861
@rtype: dictionary
5962
@return: Generated spanning tree.
6063
"""
6164
visited = [] # List for marking visited and non-visited nodes
62-
spanning_tree = {} # MInimal Spanning tree
65+
spanning_tree = {} # Minimal Spanning tree
6366

6467
# Initialization
6568
if (root is not None):
@@ -68,11 +71,11 @@ def minimal_spanning_tree(graph, root=None):
6871
spanning_tree[root] = None
6972
else:
7073
nroot = 1
71-
74+
7275
# Algorithm loop
7376
while (nroot is not None):
7477
ledge = _lightest_edge(graph, visited)
75-
if (ledge == None):
78+
if (ledge is None):
7679
if (root is not None):
7780
break
7881
nroot = _first_unvisited(graph, visited)
@@ -81,11 +84,59 @@ def minimal_spanning_tree(graph, root=None):
8184
visited.append(nroot)
8285
else:
8386
spanning_tree[ledge[1]] = ledge[0]
87+
spanning_tree[ledge[0]] = ledge[1]
8488
visited.append(ledge[1])
8589

8690
return spanning_tree
8791

8892

93+
def minimal_spanning_tree_kruskal(graph, root=None, parallel=None):
94+
"""
95+
Minimal spanning tree constructed with kruskal's algorithm.
96+
97+
@attention: Minimal spanning tree is meaningful only for weighted graphs.
98+
99+
@type graph: graph
100+
@param graph: Graph.
101+
102+
@type root: node
103+
@param root: Optional root node (will explore only root's connected component)
104+
105+
@rtype: dictionary
106+
@return: Generated spanning tree.
107+
"""
108+
109+
EDGE_WEIGHT = 0
110+
EDGE_OBJ = 1
111+
112+
if root is not None:
113+
# Will be implemented later
114+
pass
115+
else:
116+
root = 0
117+
118+
spanning_tree = {}
119+
edges = graph.edges()
120+
num_edges = len(edges)
121+
edges_heap = []
122+
123+
for edge in [(graph.edge_weight(edge), edge) for edge in edges]:
124+
heapq.heappush(edges_heap, edge)
125+
heapq.heapify(edges_heap)
126+
127+
spanning_tree[root] = None
128+
cycle_checker = UnionFind(num_edges)
129+
for min_edge in range(num_edges):
130+
min_elem = heapq.heappop(edges_heap)
131+
min_edge = min_elem[EDGE_OBJ]
132+
if not cycle_checker.find(min_edge[0], min_edge[1]):
133+
cycle_checker.union(min_edge[0], min_edge[1])
134+
spanning_tree[min_edge[1]] = min_edge[0]
135+
spanning_tree[min_edge[0]] = min_edge[1]
136+
137+
return spanning_tree
138+
139+
89140
def _first_unvisited(graph, visited):
90141
"""
91142
Return first unvisited node.
@@ -138,7 +189,7 @@ def shortest_path(graph, source):
138189
algorithm.
139190
140191
@attention: All weights must be nonnegative.
141-
192+
142193
@see: shortest_path_bellman_ford
143194
144195
@type graph: graph, digraph

core/pygraph/classes/graph.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def neighbors(self, node):
7878
@rtype: list
7979
@return: List of nodes directly accessible from given node.
8080
"""
81-
return self.node_neighbors[node]
81+
return list(self.node_neighbors[node])
8282

8383
def edges(self):
8484
"""
@@ -118,7 +118,7 @@ def add_node(self, node, attrs=None):
118118
if attrs is None:
119119
attrs = []
120120
if (not node in self.node_neighbors):
121-
self.node_neighbors[node] = []
121+
self.node_neighbors[node] = set()
122122
self.node_attr[node] = attrs
123123
else:
124124
raise AdditionError("Node %s already in graph" % node)
@@ -143,9 +143,9 @@ def add_edge(self, edge, wt=1, label='', attrs=[]):
143143
"""
144144
u, v = edge
145145
if (v not in self.node_neighbors[u] and u not in self.node_neighbors[v]):
146-
self.node_neighbors[u].append(v)
146+
self.node_neighbors[u].add(v)
147147
if (u != v):
148-
self.node_neighbors[v].append(u)
148+
self.node_neighbors[v].add(u)
149149

150150
self.add_edge_attributes((u,v), attrs)
151151
self.set_edge_properties((u, v), label=label, weight=wt)

core/pygraph/classes/unionfind.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
class UnionFind:
2+
"""
3+
Weighted Union-Find with Path Compression
4+
"""
5+
6+
def __init__(self, n):
7+
self._id = list(range(n))
8+
self._sz = [1] * n
9+
10+
def _root(self, i):
11+
j = i
12+
while (j != self._id[j]):
13+
self._id[j] = self._id[self._id[j]]
14+
j = self._id[j]
15+
return j
16+
17+
def find(self, p, q):
18+
return self._root(p) == self._root(q)
19+
20+
def union(self, p, q):
21+
i = self._root(p)
22+
j = self._root(q)
23+
if i == j:
24+
return
25+
if (self._sz[i] < self._sz[j]):
26+
self._id[i] = j
27+
self._sz[j] += self._sz[i]
28+
else:
29+
self._id[j] = i
30+
self._sz[i] += self._sz[j]

core/pygraph/mixins/labeling.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class labeling( object ):
3939

4040
def __init__(self):
4141
# Metadata bout edges
42-
self.edge_properties = {} # Mapping: Edge -> Dict mapping, lablel-> str, wt->num
42+
self.edge_properties = {} # Mapping: Edge -> Dict mapping, label-> str, wt->num
4343
self.edge_attr = {} # Key value pairs: (Edge -> Attributes)
4444

4545
# Metadata bout nodes
@@ -224,4 +224,4 @@ def nodes_eq():
224224
if (not attrs_eq(self.node_attributes(node), other.node_attributes(node))): return False
225225
return True
226226

227-
return nodes_eq() and edges_eq()
227+
return nodes_eq() and edges_eq()

tests/unittests-graph.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def test_raise_exception_when_edge_added_from_non_existing_node(self):
7070
pass
7171
else:
7272
fail()
73-
assert gr.node_neighbors == {0: [], 1: []}
73+
assert gr.node_neighbors == {0: set(), 1: set()}
7474

7575
def test_raise_exception_when_edge_added_to_non_existing_node(self):
7676
gr = graph()
@@ -81,7 +81,7 @@ def test_raise_exception_when_edge_added_to_non_existing_node(self):
8181
pass
8282
else:
8383
fail()
84-
assert gr.node_neighbors == {0: [], 1: []}
84+
assert gr.node_neighbors == {0: set(), 1: set()}
8585

8686
def test_remove_node(self):
8787
gr = testlib.new_graph()

tests/unittests-minmax.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434
from pygraph.classes.digraph import digraph
3535

3636
from pygraph.algorithms.searching import depth_first_search
37-
from pygraph.algorithms.minmax import minimal_spanning_tree,\
38-
shortest_path, heuristic_search, shortest_path_bellman_ford, maximum_flow, cut_tree
37+
from pygraph.algorithms.minmax import minimal_spanning_tree_kruskal,\
38+
minimal_spanning_tree_prim, shortest_path, heuristic_search, shortest_path_bellman_ford, maximum_flow, cut_tree
3939
from pygraph.algorithms.heuristics.chow import chow
4040
from pygraph.classes.exceptions import NegativeWeightCycleError
4141

@@ -97,11 +97,31 @@ def generate_fixture_digraph_unconnected():
9797

9898
# minimal spanning tree tests
9999

100-
class test_minimal_spanning_tree(unittest.TestCase):
100+
class test_minimal_spanning_tree_kruskal(unittest.TestCase):
101101

102-
def test_minimal_spanning_tree_on_graph(self):
102+
def test_minimal_spanning_tree_kruskal_on_graph(self):
103103
gr = testlib.new_graph(wt_range=(1,10))
104-
mst = minimal_spanning_tree(gr, root=0)
104+
mst = minimal_spanning_tree_kruskal(gr, root=0)
105+
print(mst)
106+
wt = tree_weight(gr, mst)
107+
len_dfs = len(depth_first_search(gr, root=0)[0])
108+
for each in mst:
109+
if (mst[each] != None):
110+
mst_copy = deepcopy(mst)
111+
del(mst_copy[each])
112+
for other in gr[each]:
113+
mst_copy[each] = other
114+
if (tree_weight(gr, mst_copy) < wt):
115+
gr2 = graph()
116+
add_spanning_tree(gr2, mst_copy)
117+
assert len(depth_first_search(gr2, root=0)[0]) < len_dfs
118+
119+
120+
class test_minimal_spanning_tree_prim(unittest.TestCase):
121+
122+
def test_minimal_spanning_tree_prim_on_graph(self):
123+
gr = testlib.new_graph(wt_range=(1,10))
124+
mst = minimal_spanning_tree_prim(gr, root=0)
105125
wt = tree_weight(gr, mst)
106126
len_dfs = len(depth_first_search(gr, root=0)[0])
107127
for each in mst:
@@ -116,6 +136,7 @@ def test_minimal_spanning_tree_on_graph(self):
116136
assert len(depth_first_search(gr2, root=0)[0]) < len_dfs
117137

118138

139+
119140
# shortest path tests
120141

121142
class test_shortest_path(unittest.TestCase):

0 commit comments

Comments
 (0)