Source code for pygorithm.data_structures.graph

"""
Author: OMKAR PATHAK
Created On: 12th August 2017
"""
from collections import defaultdict
import inspect
import math

[docs]class Graph(object): """Graph object Creates the graph """ def __init__(self): self.graph = defaultdict(list) self.count = 0
[docs] def print_graph(self): """ Prints the contents of the graph """ for i in self.graph: print(i, '->', ' -> '.join([str(j) for j in self.graph[i]]))
[docs] def add_edge(self, from_vertex, to_vertex): """ Adds an edge in the graph """ # check if vertex is already present self.graph[from_vertex].append(to_vertex) self.count += 1
[docs] def get_code(self): """ returns the code for the current class """ return inspect.getsource(Graph)
[docs]class WeightedGraph(object): """WeightedGraph object A graph with a numerical value (weight) on edges """ def __init__(self): self.vertexes = set() self.graph = {} self._forest = None
[docs] def get_weight(self, u, v): """ Returns the weight of an edge between vertexes u and v. If there isnt one: return None. """ return self.graph.get((u,v), self.graph.get((v,u), None))
[docs] def add_edge(self, u, v, weight): """ :param u: from vertex - type : integer :param v: to vertex - type : integer :param weight: weight of the edge - type : numeric """ if self.get_weight(u, v) != None: print("Such edge already exists!") else: self.vertexes.update((u, v)) self.graph[(u,v)] = weight
[docs] def print_graph(self): """ Print the graph :return: None """ for (u, v) in self.graph: print("%d -> %d weight: %d" % (u, v, self.graph[(u, v)]))
def _set_of(self, vertex): """ Helper method :param vertex: :return: """ for tree in self._forest: if vertex in tree: return tree return None def _union(self, u_set, v_set): """ Helper method :param u_set: :param v_set: :return: """ self._forest.remove(u_set) self._forest.remove(v_set) self._forest.append(v_set + u_set)
[docs] def kruskal_mst(self): """ Kruskal algorithm for finding the minimum spanning tree of a weighted graph. This version use a union-find data structure. More detailed info here: https://en.wikipedia.org/wiki/Kruskal%27s_algorithm Author: Michele De Vita <mik3dev@gmail.com> """ # sort by weight self.graph = {k: self.graph[k] for k in sorted(self.graph, key=self.graph.get, reverse=False)} edges_explored = [] self._forest = [[v] for v in self.vertexes] for (u, v) in self.graph: u_set, v_set = self._set_of(u), self._set_of(v) if u_set != v_set: self._union(u_set, v_set) edges_explored.append(((u, v), self.graph[u, v])) return edges_explored
# TODO: Is this necessary?
[docs] @staticmethod def kruskal_time_complexity(): """ Return time complexity of kruskal :return: string """ return "Worst case: O(E log(V)) where E in the number of edges and V the number of vertexes"
[docs] @classmethod def kruskal_code(cls): """ Returns the code for current class """ return inspect.getsource(cls.kruskal_mst)
[docs]class WeightedUndirectedGraph(object): """WeightedUndirectedGraph object A graph with a numerical value (weight) on edges, which is the same for both directions in an undirected graph. """ def __init__(self): self.graph = {} self.weights = {}
[docs] def add_edge(self, u, v, weight): """ Adds the specified edge to this graph. If the edge already exists, this will only modify the weight (not create duplicates). :param u: from vertex :param v: to vertex :param weight: weight of the edge - type : numeric """ changing_weight = (u, v) in self.weights.keys() self.weights[(u, v)] = weight self.weights[(v, u)] = weight if changing_weight: return if u in self.graph.keys(): self.graph[u].append(v) else: self.graph[u] = [v] if v in self.graph.keys(): self.graph[v].append(u) else: self.graph[v] = [u]
[docs] def get_edge_weight(self, u, v): """ Gets the weight between u and v if such an edge exists, or None if it does not. :param u: one edge :param v: the other edge :return: numeric or None """ return self.weights.get((u, v), None)
[docs] def remove_edge(self, edge, other_edge_or_none=None): """ Removes the specified edge from the grid entirely or, if specified, the connection with one other edge. Behavior is undefined if the connection does not exist. :param edge: the edge to remove :param other_edge_or_none: an edge connected to edge or none """ if other_edge_or_none is not None: del self.weights[(edge, other_edge_or_none)] del self.weights[(other_edge_or_none, edge)] edge_list = self.graph[edge] other_edge_list = self.graph[other_edge_or_none] if len(edge_list) == 1: del self.graph[edge] else: self.graph[edge].remove(other_edge_or_none) if len(other_edge_list) == 1: del self.graph[other_edge_or_none] else: self.graph[other_edge_or_none].remove(edge) else: edge_list = self.graph[edge] del self.graph[edge] for other_edge in edge_list: del self.weights[(edge, other_edge)] del self.weights[(other_edge, edge)] other_edge_list = self.graph[other_edge] if len(other_edge_list) == 1: del self.graph[other_edge] else: other_edge_list.remove(edge)
[docs] def gridify(self, size, weight): """ Constructs connections from a square grid starting at (0, 0) until (size-1, size-1) with connections between adjacent and diagonal nodes. Diagonal nodes have a weight of weight*sqrt(2) :param size: the size of the square grid to construct - type : integer :param weight: the weight between orthogonal nodes. - type: numeric :return: None """ rt2 = math.sqrt(2) acceptable_offsets = [ (-1, -1, rt2), (-1, 0, 1), (-1, 1, rt2), (0, -1, 1), (0, 1, 1), (1, -1, rt2), (1, 0, 1), (1, 1, rt2) ] for x in range(0, size): for y in range(0, size): for offset in acceptable_offsets: nx = x + offset[0] ny = y + offset[1] if nx >= 0 and ny >= 0 and nx < size and ny < size: self.add_edge((x, y), (nx, ny), weight * offset[2])
[docs]class TopologicalSort(Graph):
[docs] def topological_sort(self): """ function for sorting graph elements using topological sort """ # Marking all vertices as not visited visited = [False] * self.count # Stack for storing the vertex stack = [] for vertex in range(self.count): # Call the recursive function only if not visited if not visited[vertex]: self.__topological_sort_rec(vertex, visited, stack) return stack
def __topological_sort_rec(self, vertex, visited, stack): """ Recursive function for topological Sort """ # Mark the current node in visited visited[vertex] = True # mark all adjacent nodes of the current node try: for adjacent_node in self.graph[vertex]: if not visited[adjacent_node]: self.__topological_sort_rec(adjacent_node, visited, stack) except KeyError: return # Push current vertex to stack which stores the result stack.insert(0, vertex)
[docs] def get_code(self): """ returns the code for the current class """ return inspect.getsource(TopologicalSort)
[docs]class CheckCycleDirectedGraph(object): """CheckCycleDirectedGraph Class to check cycle in directed graph """ def __init__(self): self.graph = {} self.count = 0
[docs] def print_graph(self): """ for printing the contents of the graph """ for i in self.graph: print(i, '->', ' -> '.join([str(j) for j in self.graph[i]]))
[docs] def add_edge(self, from_vertex, to_vertex): """ function to add an edge in the graph """ # check if vertex is already present if from_vertex in self.graph.keys(): self.graph[from_vertex].append(to_vertex) self.count += 1 else: self.graph[from_vertex] = [to_vertex] self.count += 1
[docs] def check_cycle(self): """ This function will return True if graph is cyclic else return False """ visited = [False] * len(self.graph) stack = [False] * len(self.graph) for vertex in range(len(self.graph)): if not visited[vertex]: if self.__check_cycle_rec(visited, stack, vertex): return True return False
def __check_cycle_rec(self, visited, stack, vertex): """ Recursive function for finding the cycle """ # Mark the current node in visited and also add it to the stack visited[vertex] = True stack[vertex] = True # mark all adjacent nodes of the current node for adjacentNode in self.graph[vertex]: if not visited[adjacentNode]: if self.__check_cycle_rec(visited, stack, adjacentNode): return True elif stack[adjacentNode]: return True # The node needs to be popped from # recursion stack before function ends stack[vertex] = False return False
[docs] @staticmethod def get_code(): """ returns the code for the current class """ return inspect.getsource(CheckCycleDirectedGraph)
[docs]class CheckCycleUndirectedGraph(object): """CheckCycleUndirectedGraph Class to check cycle in undirected graph """ def __init__(self): self.graph = {} self.count = 0
[docs] def print_graph(self): """ for printing the contents of the graph """ for i in self.graph: print(i, '->', ' -> '.join([str(j) for j in self.graph[i]]))
[docs] def add_edge(self, from_vertex, to_vertex): """ for adding the edge between two vertices """ # check if vertex is already present, if from_vertex in self.graph.keys(): self.graph[from_vertex].append(to_vertex) else: # otherwise add it to the graph self.graph[from_vertex] = [to_vertex] if to_vertex in self.graph.keys(): self.graph[to_vertex].append(from_vertex) else: self.graph[to_vertex] = [from_vertex]
[docs] def check_cycle(self): """ This function will return True if graph is cyclic else return False """ # Marking all vertices as not visited visited = [False] * len(self.graph) for vertex in range(len(self.graph)): # Call the recursive function only if not visited if not visited[vertex]: if self.__check_cycle_rec(visited, -1, vertex): return True return False
def __check_cycle_rec(self, visited, parent, vertex): """ Recursive function for finding the cycle """ # Mark the current node in visited visited[vertex] = True # mark all adjacent nodes of the current node for adjacentNode in self.graph[vertex]: if not visited[adjacentNode]: if self.__check_cycle_rec(visited, vertex, adjacentNode): return True elif parent != adjacentNode: return True return False
[docs] @staticmethod def get_code(): """ returns the code for the current class """ return inspect.getsource(CheckCycleUndirectedGraph)