Skip to content

Commit 3fadc0c

Browse files
author
Hamid Gasmi
committed
#188 Refactored to a linear time algorithm
1 parent 4c154a4 commit 3fadc0c

File tree

1 file changed

+79
-5
lines changed

1 file changed

+79
-5
lines changed

09-problems/graph-algorithms-in-genome-sequencing/eulerian_cycle.py

+79-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from collections import namedtuple
33

44
Node_Degree = namedtuple('Node_Degree', ['vertex_no', 'in_degree', 'out_degree'])
5+
Cycle = namedtuple('Cycle', ['cycle_no', 'cycle_vertex_pos'])
56

67
class Graph:
78
def __init__(self, edges):
@@ -100,7 +101,7 @@ def _is_eulerian_graph(self):
100101

101102
return True
102103

103-
def form_cycle(self, start, cycle, visited_edge_indexes, unvisited_edge_node_dict):
104+
def form_cycle_naive(self, start, cycle, visited_edge_indexes, unvisited_edge_node_dict):
104105

105106
node = start
106107
while visited_edge_indexes[node] + 1 < len(self.adjacency_list[node]):
@@ -121,7 +122,7 @@ def form_cycle(self, start, cycle, visited_edge_indexes, unvisited_edge_node_dic
121122

122123
return -1 if len(unvisited_edge_node_dict) == 0 else next(iter(unvisited_edge_node_dict.values()))
123124

124-
def eulerian_cycle(self):
125+
def eulerian_cycle_naive(self):
125126

126127
assert(self._is_eulerian_graph())
127128

@@ -131,7 +132,7 @@ def eulerian_cycle(self):
131132
# nodes with unvisited edges: {node, position in cycle}
132133
unvisited_edge_node_dict = dict()
133134

134-
new_start_pos_in_cycle = self.form_cycle(0, cycle, visited_edge_indexes, unvisited_edge_node_dict)
135+
new_start_pos_in_cycle = self.form_cycle_naive(0, cycle, visited_edge_indexes, unvisited_edge_node_dict)
135136

136137
while new_start_pos_in_cycle != -1:
137138

@@ -145,13 +146,86 @@ def eulerian_cycle(self):
145146
else:
146147
unvisited_edge_node_dict[node] += (len(cycle) - new_start_pos_in_cycle)
147148

148-
new_start_pos_in_cycle = self.form_cycle(new_start, cycle, visited_edge_indexes, unvisited_edge_node_dict)
149+
new_start_pos_in_cycle = self.form_cycle_naive(new_start, cycle, visited_edge_indexes, unvisited_edge_node_dict)
149150

150151
return cycle
151152

153+
def form_cycle(self, start, visited_edge_indexes, unvisited_edge_node_set):
154+
155+
node = start
156+
cycle = []
157+
while visited_edge_indexes[node] + 1 < len(self.adjacency_list[node]):
158+
159+
cycle.append(node)
160+
161+
# Save this node, if there are 2 or more unvisited edges
162+
if (visited_edge_indexes[node] + 2) < len(self.adjacency_list[node]):
163+
if not node in unvisited_edge_node_set:
164+
unvisited_edge_node_set.add(node)
165+
166+
elif node in unvisited_edge_node_set:
167+
unvisited_edge_node_set.remove(node)
168+
169+
visited_edge_indexes[node] += 1
170+
node = self.adjacency_list[node][ visited_edge_indexes[node] ]
171+
172+
cycle.append(node)
173+
174+
return cycle, -1 if len(unvisited_edge_node_set) == 0 else next(iter(unvisited_edge_node_set))
175+
176+
# Running Time: O(|E|)
177+
def eulerian_cycle(self):
178+
179+
assert(self._is_eulerian_graph())
180+
181+
visited_edge_indexes = [-1 for _ in range(len(self.nodes))]
182+
183+
# nodes with unvisited edges: {node, position in cycle}
184+
unvisited_edge_node_set = set()
185+
186+
# {node_no, cycle_no []}: will contain all cycle no starting from aach node
187+
node_cycles_dict = dict()
188+
189+
# 1. Find all cycles
190+
cycles = []
191+
start_node = 0
192+
while start_node != -1:
193+
194+
cycle, start_node = self.form_cycle(start_node, visited_edge_indexes, unvisited_edge_node_set)
195+
196+
cycles.append(cycle)
197+
198+
if cycle[0] in node_cycles_dict:
199+
node_cycles_dict[ cycle[0] ].append(len(cycles) - 1)
200+
else:
201+
node_cycles_dict[ cycle[0] ] = [ len(cycles) - 1 ]
202+
203+
# 2. Build Eulerian Cycle:
204+
cycle = []
205+
cycle_queue = [ Cycle(0, 0) ]
206+
while len(cycle_queue) != 0:
207+
208+
(cycle_no, cycle_vertex_pos) = cycle_queue.pop()
209+
vertex_no = cycles[cycle_no][cycle_vertex_pos]
210+
211+
cycle.append(vertex_no)
212+
cycle_vertex_pos += 1
213+
if cycle_vertex_pos < len(cycles[cycle_no]):
214+
cycle_queue.append((cycle_no, cycle_vertex_pos))
215+
216+
if vertex_no in node_cycles_dict:
217+
for new_cycle_no in node_cycles_dict[vertex_no]:
218+
if new_cycle_no != cycle_no:
219+
cycle_queue.append((new_cycle_no, 1))
220+
221+
node_cycles_dict.pop(vertex_no)
222+
223+
return cycle
224+
152225
if __name__ == "__main__":
153226
edges = sys.stdin.read().strip().splitlines()
154227

155228
eulerian_graph = Graph(edges)
156229

157-
print('->'.join( [ eulerian_graph.nodes[node] for node in eulerian_graph.eulerian_cycle() ] ))
230+
print("Naive : ", '->'.join( [ eulerian_graph.nodes[node] for node in eulerian_graph.eulerian_cycle_naive() ] ))
231+
print("Efficient: ", '->'.join( [ eulerian_graph.nodes[node] for node in eulerian_graph.eulerian_cycle() ] ))

0 commit comments

Comments
 (0)