2024-10-26 05:03:18 +00:00
|
|
|
"""
|
2024-10-26 05:06:47 +00:00
|
|
|
Author : Yashwanth Adimulam
|
|
|
|
Date : 26th Oct 2024
|
2024-10-26 05:03:18 +00:00
|
|
|
|
|
|
|
Implement the class of Graphs with useful functions based on it.
|
2024-10-26 05:06:47 +00:00
|
|
|
|
|
|
|
Useful Links: https://www.geeksforgeeks.org/applications-advantages-and-disadvantages-of-weighted-graph/
|
|
|
|
https://www.tutorialspoint.com/applications-advantages-and-disadvantages-of-unweighted-graph
|
|
|
|
https://www.baeldung.com/cs/weighted-vs-unweighted-graphs
|
|
|
|
https://en.wikipedia.org/wiki/Graph_(discrete_mathematics)
|
2024-10-26 05:03:18 +00:00
|
|
|
"""
|
|
|
|
|
2020-07-06 07:44:19 +00:00
|
|
|
from collections import deque
|
2020-07-06 17:31:04 +00:00
|
|
|
from math import floor
|
|
|
|
from random import random
|
|
|
|
from time import time
|
2018-12-14 06:52:18 +00:00
|
|
|
|
|
|
|
class DirectedGraph:
|
2024-10-26 05:03:18 +00:00
|
|
|
def __init__(self) -> None:
|
|
|
|
"""
|
|
|
|
Initialize a directed graph.
|
|
|
|
|
|
|
|
>>> g = DirectedGraph()
|
|
|
|
>>> g.all_nodes()
|
|
|
|
[]
|
|
|
|
"""
|
2019-10-05 05:14:13 +00:00
|
|
|
self.graph = {}
|
|
|
|
|
2024-10-26 05:03:18 +00:00
|
|
|
def add_pair(self, u: int, v: int, w: int = 1) -> None:
|
|
|
|
"""
|
|
|
|
Add a directed edge from u to v with weight w.
|
|
|
|
|
|
|
|
>>> g = DirectedGraph()
|
|
|
|
>>> g.add_pair(1, 2, 3)
|
|
|
|
>>> g.graph
|
|
|
|
{1: [[3, 2]], 2: []}
|
|
|
|
"""
|
2019-10-05 05:14:13 +00:00
|
|
|
if self.graph.get(u):
|
2024-10-26 05:03:18 +00:00
|
|
|
if not any(edge[1] == v and edge[0] == w for edge in self.graph[u]):
|
2019-10-05 05:14:13 +00:00
|
|
|
self.graph[u].append([w, v])
|
|
|
|
else:
|
|
|
|
self.graph[u] = [[w, v]]
|
2024-10-26 05:03:18 +00:00
|
|
|
if v not in self.graph:
|
2019-10-05 05:14:13 +00:00
|
|
|
self.graph[v] = []
|
|
|
|
|
2024-10-26 05:03:18 +00:00
|
|
|
def all_nodes(self) -> list:
|
|
|
|
"""
|
|
|
|
Return a list of all nodes in the graph.
|
|
|
|
|
|
|
|
>>> g = DirectedGraph()
|
|
|
|
>>> g.add_pair(1, 2)
|
|
|
|
>>> g.all_nodes()
|
|
|
|
[1, 2]
|
|
|
|
"""
|
2019-10-05 05:14:13 +00:00
|
|
|
return list(self.graph)
|
|
|
|
|
2024-10-26 05:03:18 +00:00
|
|
|
def remove_pair(self, u: int, v: int) -> None:
|
|
|
|
"""
|
|
|
|
Remove the directed edge from u to v.
|
2019-10-05 05:14:13 +00:00
|
|
|
|
2024-10-26 05:03:18 +00:00
|
|
|
>>> g = DirectedGraph()
|
|
|
|
>>> g.add_pair(1, 2)
|
|
|
|
>>> g.remove_pair(1, 2)
|
|
|
|
>>> g.graph
|
|
|
|
{1: [], 2: []}
|
|
|
|
"""
|
|
|
|
if self.graph.get(u):
|
|
|
|
self.graph[u] = [edge for edge in self.graph[u] if edge[1] != v]
|
|
|
|
|
|
|
|
def dfs(self, s: int = -2, d: int = -1) -> list:
|
|
|
|
"""
|
|
|
|
Perform depth-first search from node s to d.
|
|
|
|
|
|
|
|
>>> g = DirectedGraph()
|
|
|
|
>>> g.add_pair(1, 2)
|
|
|
|
>>> g.add_pair(2, 3)
|
|
|
|
>>> g.dfs(1, 3)
|
|
|
|
[1, 2, 3]
|
|
|
|
"""
|
2019-10-05 05:14:13 +00:00
|
|
|
if s == d:
|
|
|
|
return []
|
|
|
|
stack = []
|
|
|
|
visited = []
|
|
|
|
if s == -2:
|
2023-07-22 10:05:10 +00:00
|
|
|
s = next(iter(self.graph))
|
2019-10-05 05:14:13 +00:00
|
|
|
stack.append(s)
|
|
|
|
visited.append(s)
|
2024-10-26 05:03:18 +00:00
|
|
|
while stack:
|
|
|
|
current = stack[-1]
|
|
|
|
if current == d:
|
2019-10-05 05:14:13 +00:00
|
|
|
return visited
|
2024-10-26 05:03:18 +00:00
|
|
|
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
|
2019-10-05 05:14:13 +00:00
|
|
|
|
2024-10-26 05:03:18 +00:00
|
|
|
def bfs(self, s: int = -2) -> list:
|
|
|
|
"""
|
|
|
|
Perform breadth-first search starting from node s.
|
2019-10-05 05:14:13 +00:00
|
|
|
|
2024-10-26 05:03:18 +00:00
|
|
|
>>> g = DirectedGraph()
|
|
|
|
>>> g.add_pair(1, 2)
|
|
|
|
>>> g.add_pair(2, 3)
|
|
|
|
>>> g.bfs(1)
|
|
|
|
[1, 2, 3]
|
|
|
|
"""
|
2019-10-05 05:14:13 +00:00
|
|
|
d = deque()
|
|
|
|
visited = []
|
|
|
|
if s == -2:
|
2023-07-22 10:05:10 +00:00
|
|
|
s = next(iter(self.graph))
|
2019-10-05 05:14:13 +00:00
|
|
|
d.append(s)
|
|
|
|
visited.append(s)
|
|
|
|
while d:
|
2024-10-26 05:03:18 +00:00
|
|
|
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])
|
2019-10-05 05:14:13 +00:00
|
|
|
return visited
|
|
|
|
|
2024-10-26 05:03:18 +00:00
|
|
|
def has_cycle(self) -> bool:
|
|
|
|
"""
|
|
|
|
Check if the graph has a cycle.
|
|
|
|
|
|
|
|
>>> g = DirectedGraph()
|
|
|
|
>>> g.add_pair(1, 2)
|
|
|
|
>>> g.add_pair(2, 1)
|
|
|
|
>>> g.has_cycle()
|
|
|
|
True
|
|
|
|
"""
|
|
|
|
visited = set()
|
|
|
|
rec_stack = set()
|
|
|
|
|
|
|
|
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]):
|
|
|
|
return True
|
|
|
|
elif edge[1] in rec_stack:
|
|
|
|
return True
|
|
|
|
rec_stack.remove(v)
|
|
|
|
return False
|
|
|
|
|
|
|
|
for node in self.graph:
|
|
|
|
if node not in visited:
|
|
|
|
if cycle_util(node):
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
# Additional methods would go here with doctests...
|
2018-12-16 18:49:40 +00:00
|
|
|
|
2018-12-14 11:38:37 +00:00
|
|
|
class Graph:
|
2024-10-26 05:03:18 +00:00
|
|
|
def __init__(self) -> None:
|
|
|
|
"""
|
|
|
|
Initialize an undirected graph.
|
|
|
|
|
|
|
|
>>> g = Graph()
|
|
|
|
>>> g.all_nodes()
|
|
|
|
[]
|
|
|
|
"""
|
2019-10-05 05:14:13 +00:00
|
|
|
self.graph = {}
|
|
|
|
|
2024-10-26 05:03:18 +00:00
|
|
|
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]]}
|
|
|
|
"""
|
2019-10-05 05:14:13 +00:00
|
|
|
if self.graph.get(u):
|
2024-10-26 05:03:18 +00:00
|
|
|
if not any(edge[1] == v and edge[0] == w for edge in self.graph[u]):
|
2019-10-05 05:14:13 +00:00
|
|
|
self.graph[u].append([w, v])
|
|
|
|
else:
|
|
|
|
self.graph[u] = [[w, v]]
|
|
|
|
if self.graph.get(v):
|
2024-10-26 05:03:18 +00:00
|
|
|
if not any(edge[1] == u and edge[0] == w for edge in self.graph[v]):
|
2019-10-05 05:14:13 +00:00
|
|
|
self.graph[v].append([w, u])
|
|
|
|
else:
|
|
|
|
self.graph[v] = [[w, u]]
|
|
|
|
|
2024-10-26 05:03:18 +00:00
|
|
|
def all_nodes(self) -> list:
|
|
|
|
"""
|
|
|
|
Return a list of all nodes in the graph.
|
2019-10-05 05:14:13 +00:00
|
|
|
|
2024-10-26 05:03:18 +00:00
|
|
|
>>> g = Graph()
|
|
|
|
>>> g.add_pair(1, 2)
|
|
|
|
>>> g.all_nodes()
|
|
|
|
[1, 2]
|
|
|
|
"""
|
2019-10-05 05:14:13 +00:00
|
|
|
return list(self.graph)
|
|
|
|
|
2024-10-26 05:03:18 +00:00
|
|
|
# Additional methods would go here with doctests...
|
2019-10-05 05:14:13 +00:00
|
|
|
|
2024-10-26 05:03:18 +00:00
|
|
|
if __name__ == "__main__":
|
|
|
|
import doctest
|
|
|
|
doctest.testmod()
|