diff --git a/graphs/directed_and_undirected_weighted_graph.py b/graphs/directed_and_undirected_weighted_graph.py index 91e83731b..640da1bad 100644 --- a/graphs/directed_and_undirected_weighted_graph.py +++ b/graphs/directed_and_undirected_weighted_graph.py @@ -10,23 +10,17 @@ Useful Links: https://www.geeksforgeeks.org/applications-advantages-and-disadvan https://en.wikipedia.org/wiki/Graph_(discrete_mathematics) """ + from collections import deque from math import floor from random import random from time import time class DirectedGraph: - def __init__(self) -> None: - """ - Initialize a directed graph. - - >>> g = DirectedGraph() - >>> g.all_nodes() - [] - """ + def __init__(self): self.graph = {} - def add_pair(self, u: int, v: int, w: int = 1) -> None: + def add_pair(self, u, v, w=1): """ Add a directed edge from u to v with weight w. @@ -36,14 +30,14 @@ class DirectedGraph: {1: [[3, 2]], 2: []} """ if self.graph.get(u): - if not any(edge[1] == v and edge[0] == w for edge in self.graph[u]): + if self.graph[u].count([w, v]) == 0: self.graph[u].append([w, v]) else: self.graph[u] = [[w, v]] - if v not in self.graph: + if not self.graph.get(v): self.graph[v] = [] - def all_nodes(self) -> list: + def all_nodes(self): """ Return a list of all nodes in the graph. @@ -54,7 +48,7 @@ class DirectedGraph: """ return list(self.graph) - def remove_pair(self, u: int, v: int) -> None: + def remove_pair(self, u, v): """ Remove the directed edge from u to v. @@ -65,9 +59,11 @@ class DirectedGraph: {1: [], 2: []} """ if self.graph.get(u): - self.graph[u] = [edge for edge in self.graph[u] if edge[1] != v] + for _ in self.graph[u]: + if _[1] == v: + self.graph[u].remove(_) - def dfs(self, s: int = -2, d: int = -1) -> list: + def dfs(self, s=-2, d=-1): """ Perform depth-first search from node s to d. @@ -76,6 +72,8 @@ class DirectedGraph: >>> g.add_pair(2, 3) >>> g.dfs(1, 3) [1, 2, 3] + >>> g.dfs(1, 2) + [1, 2] """ if s == d: return [] @@ -85,23 +83,48 @@ class DirectedGraph: s = next(iter(self.graph)) stack.append(s) visited.append(s) - while stack: - current = stack[-1] - if current == d: - return visited - if current in self.graph: - for edge in self.graph[current]: - if edge[1] not in visited: - stack.append(edge[1]) - visited.append(edge[1]) - break - else: - stack.pop() - else: - stack.pop() - return visited + while True: + if len(self.graph[s]) != 0: + ss = s + for node in self.graph[s]: + if visited.count(node[1]) < 1: + if node[1] == d: + visited.append(d) + return visited + else: + stack.append(node[1]) + visited.append(node[1]) + ss = node[1] + break - def bfs(self, s: int = -2) -> list: + if s == ss: + stack.pop() + if len(stack) != 0: + s = stack[len(stack) - 1] + else: + s = ss + + if len(stack) == 0: + return visited + + def fill_graph_randomly(self, c=-1): + """ + Fill the graph with random edges. + + >>> g = DirectedGraph() + >>> g.fill_graph_randomly(5) + >>> len(g.all_nodes()) > 0 + True + """ + if c == -1: + c = floor(random() * 10000) + 10 + for i in range(c): + for _ in range(floor(random() * 102) + 1): + n = floor(random() * c) + 1 + if n != i: + self.add_pair(i, n, 1) + + def bfs(self, s=-2): """ Perform breadth-first search starting from node s. @@ -118,15 +141,134 @@ class DirectedGraph: d.append(s) visited.append(s) while d: - current = d.popleft() - if current in self.graph: - for edge in self.graph[current]: - if edge[1] not in visited: - d.append(edge[1]) - visited.append(edge[1]) + s = d.popleft() + if len(self.graph[s]) != 0: + for node in self.graph[s]: + if visited.count(node[1]) < 1: + d.append(node[1]) + visited.append(node[1]) return visited - def has_cycle(self) -> bool: + def in_degree(self, u): + """ + Calculate in-degree of node u. + + >>> g = DirectedGraph() + >>> g.add_pair(1, 2) + >>> g.in_degree(2) + 1 + """ + count = 0 + for x in self.graph: + for y in self.graph[x]: + if y[1] == u: + count += 1 + return count + + def out_degree(self, u): + """ + Calculate out-degree of node u. + + >>> g = DirectedGraph() + >>> g.add_pair(1, 2) + >>> g.out_degree(1) + 1 + """ + return len(self.graph[u]) + + def topological_sort(self, s=-2): + """ + Perform topological sort of the graph. + + >>> g = DirectedGraph() + >>> g.add_pair(1, 2) + >>> g.add_pair(2, 3) + >>> g.topological_sort() + [1, 2, 3] + """ + stack = [] + visited = [] + if s == -2: + s = next(iter(self.graph)) + stack.append(s) + visited.append(s) + sorted_nodes = [] + + while True: + if len(self.graph[s]) != 0: + ss = s + for node in self.graph[s]: + if visited.count(node[1]) < 1: + stack.append(node[1]) + visited.append(node[1]) + ss = node[1] + break + + if s == ss: + sorted_nodes.append(stack.pop()) + if len(stack) != 0: + s = stack[len(stack) - 1] + else: + s = ss + + if len(stack) == 0: + return sorted_nodes + + def cycle_nodes(self): + """ + Get nodes that are part of a cycle. + + >>> g = DirectedGraph() + >>> g.add_pair(1, 2) + >>> g.add_pair(2, 1) + >>> g.cycle_nodes() + [1, 2] + """ + stack = [] + visited = [] + s = next(iter(self.graph)) + stack.append(s) + visited.append(s) + parent = -2 + indirect_parents = [] + ss = s + anticipating_nodes = set() + + while True: + if len(self.graph[s]) != 0: + ss = s + for node in self.graph[s]: + if ( + visited.count(node[1]) > 0 + and node[1] != parent + and indirect_parents.count(node[1]) > 0 + ): + len_stack = len(stack) - 1 + while len_stack >= 0: + if stack[len_stack] == node[1]: + anticipating_nodes.add(node[1]) + break + else: + anticipating_nodes.add(stack[len_stack]) + len_stack -= 1 + if visited.count(node[1]) < 1: + stack.append(node[1]) + visited.append(node[1]) + ss = node[1] + break + + if s == ss: + stack.pop() + if len(stack) != 0: + s = stack[len(stack) - 1] + else: + parent = s + s = ss + + if len(stack) == 0: + return list(anticipating_nodes) + + def has_cycle(self): """ Check if the graph has a cycle. @@ -136,73 +278,65 @@ class DirectedGraph: >>> g.has_cycle() True """ - visited = set() - rec_stack = set() + stack = [] + visited = [] + s = next(iter(self.graph)) + stack.append(s) + visited.append(s) + parent = -2 + indirect_parents = [] + ss = s - def cycle_util(v): - visited.add(v) - rec_stack.add(v) - for edge in self.graph.get(v, []): - if edge[1] not in visited: - if cycle_util(edge[1]): + while True: + if len(self.graph[s]) != 0: + ss = s + for node in self.graph[s]: + if ( + visited.count(node[1]) > 0 + and node[1] != parent + and indirect_parents.count(node[1]) > 0 + ): return True - elif edge[1] in rec_stack: - return True - rec_stack.remove(v) - return False + if visited.count(node[1]) < 1: + stack.append(node[1]) + visited.append(node[1]) + ss = node[1] + break - for node in self.graph: - if node not in visited: - if cycle_util(node): - return True - return False + if s == ss: + stack.pop() + if len(stack) != 0: + s = stack[len(stack) - 1] + else: + s = ss - # Additional methods would go here with doctests... + if len(stack) == 0: + return False -class Graph: - def __init__(self) -> None: + def dfs_time(self, s=-2, e=-1): """ - Initialize an undirected graph. + Measure the time taken for DFS. - >>> g = Graph() - >>> g.all_nodes() - [] - """ - self.graph = {} - - def add_pair(self, u: int, v: int, w: int = 1) -> None: - """ - Add an undirected edge between u and v with weight w. - - >>> g = Graph() - >>> g.add_pair(1, 2, 3) - >>> g.graph - {1: [[3, 2]], 2: [[3, 1]]} - """ - if self.graph.get(u): - if not any(edge[1] == v and edge[0] == w for edge in self.graph[u]): - self.graph[u].append([w, v]) - else: - self.graph[u] = [[w, v]] - if self.graph.get(v): - if not any(edge[1] == u and edge[0] == w for edge in self.graph[v]): - self.graph[v].append([w, u]) - else: - self.graph[v] = [[w, u]] - - def all_nodes(self) -> list: - """ - Return a list of all nodes in the graph. - - >>> g = Graph() + >>> g = DirectedGraph() >>> g.add_pair(1, 2) - >>> g.all_nodes() - [1, 2] + >>> g.dfs_time(1, 2) >= 0 + True """ - return list(self.graph) + begin = time() + self.dfs(s, e) + end = time() + return end - begin - # Additional methods would go here with doctests... + def bfs_time(self, s=-2): + """ + Measure the time taken for BFS. -if __name__ == "__main__": - import doctest - doctest.testmod() + >>> g = DirectedGraph() + >>> g.add_pair(1, 2) + >>> g.bfs_time(1) >= 0 + True + """ + begin = time() + self.bfs(s) + end = time() + return end - begin