Graph Implementation Using Adjacency Lists
for an undirected graph.
© Joe James, 2019.
Vertex Class
The Vertex class has a constructor that sets the name of the vertex (in our example, just a letter), and creates a new empty set to store neighbors.
The add_neighbor method adds the name of a neighboring vertex to the neighbors set. This set automatically eliminates duplicates.
class Vertex:
def __init__(self, n):
self.name = n
self.neighbors = set()
def add_neighbor(self, v):
self.neighbors.add(v)
Graph Class
The Graph class uses a dictionary to store vertices in the format, vertex_name:vertex_object.
Adding a new vertex to the graph, we first check if the object passed in is a vertex object, then we check if it already exists in the graph. If both checks pass, then we add the vertex to the graph’s vertices dictionary.
When adding an edge, we receive two vertex names, we first check if both vertex names are valid, then we add each to the other’s neighbors set.
To print the graph, we iterate through the vertices, and print each vertex name (the key) followed by its sorted neighbors list.
class Graph:
vertices = {}
def add_vertex(self, vertex):
if isinstance(vertex, Vertex) and vertex.name not in self.vertices:
self.vertices[vertex.name] = vertex
return True
else:
return False
def add_edge(self, u, v):
if u in self.vertices and v in self.vertices:
self.vertices[u].add_neighbor(v)
self.vertices[v].add_neighbor(u)
return True
else:
return False
def print_graph(self):
for key in sorted(list(self.vertices.keys())):
print(key, sorted(list(self.vertices[key].neighbors)))
Test Code
Here we create a new Graph object. We create a new vertex named A. We add A to the graph. Then we add new vertex B to the graph. Then we iterate from A to K and add a bunch of vertices to the graph. Since the add_vertex method checks for duplicates, A and B are not added twice.
g = Graph()
a = Vertex('A')
g.add_vertex(a)
g.add_vertex(Vertex('B'))
for i in range(ord('A'), ord('K')):
g.add_vertex(Vertex(chr(i)))
An edge consists of two vertex names. Here we iterate through a list of edges and add each to the graph.
This print_graph method doesn’t give a very good visualization of the graph, but it does show the neighbors for each vertex.
edges = ['AB', 'AE', 'BF', 'CG', 'DE', 'DH', 'EH', 'FG', 'FI', 'FJ', 'GJ', 'HI']
for edge in edges:
g.add_edge(edge[0], edge[1])
g.print_graph()
A ['B', 'E']
B ['A', 'F']
C ['G']
D ['E', 'H']
E ['A', 'D', 'H']
F ['B', 'G', 'I', 'J']
G ['C', 'F', 'J']
H ['D', 'E', 'I']
I ['F', 'H']
J ['F', 'G']
Graph Implementation Using Adjacency Matrix
for undirected graph, with weighted or unweighted edges.
© Joe James, 2019.
Vertex Class
A vertex object only needs to store its name.
class Vertex:
def __init__(self, n):
self.name = n
Graph Class
A graph object has three attributes:
vertices - a dictionary with vertex_name:vertex_object.
edges - a 2-dimensional list (ie. a matrix) of edges. for an unweighted graph it will contain 0 for no edge and 1 for edge.
edge_indices - a dictionary with vertex_name:list_index (eg. A:0) to access edges.
add_vertex updates all three of these attributes.
add_edge only needs to update the edges matrix.
class Graph:
vertices = {}
edges = []
edge_indices = {}
def add_vertex(self, vertex):
if isinstance(vertex, Vertex) and vertex.name not in self.vertices:
self.vertices[vertex.name] = vertex
# for loop appends a column of zeros to the edges matrix
for row in self.edges:
row.append(0)
# append a row of zeros to the bottom of the edges matrix
self.edges.append([0] * (len(self.edges)+1))
self.edge_indices[vertex.name] = len(self.edge_indices)
# Exp: Vertices 'A', 'C' and 'B' are added one after another, then self.edge_indices['C'] = 1
return True
else:
return False
def add_edge(self, u, v, weight=1):
if u in self.vertices and v in self.vertices:
self.edges[self.edge_indices[u]][self.edge_indices[v]] = weight
self.edges[self.edge_indices[v]][self.edge_indices[u]] = weight
return True
else:
return False
def print_graph(self):
print(self.edge_indices)
for v, i in sorted(self.edge_indices.items()):
print(v + ' ', end='')
for j in range(len(self.edges)):
print(self.edges[i][j], end=' ')
print(' ')
Test Code
Here we create a new Graph object. We create a new vertex named A. We add A to the graph. Then we add new vertex B to the graph. Then we iterate from A to K and add a bunch of vertices to the graph. Since the add_vertex method checks for duplicates, A and B are not added twice.
This is exactly the same test code we used for the graph with adjacency lists.
g = Graph()
a = Vertex('A')
g.add_vertex(a)
g.add_vertex(Vertex('B'))
for i in range(ord('A'), ord('K')):
g.add_vertex(Vertex(chr(i)))
An edge consists of two vertex names. Here we iterate through a list of edges and add each to the graph.
This print_graph method doesn’t give a very good visualization of the graph, but it does show the adjacency matrix so we can see each vertex’s neighbors.
edges = ['AB', 'AE', 'BF', 'CG', 'DE', 'DH', 'EH', 'FG', 'FI', 'FJ', 'GJ', 'HI']
for edge in edges:
g.add_edge(edge[0], edge[1])
g.print_graph()
{'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7, 'I': 8, 'J': 9}
A 0 1 0 0 1 0 0 0 0 0
B 1 0 0 0 0 1 0 0 0 0
C 0 0 0 0 0 0 1 0 0 0
D 0 0 0 0 1 0 0 1 0 0
E 1 0 0 1 0 0 0 1 0 0
F 0 1 0 0 0 0 1 0 1 1
G 0 0 1 0 0 1 0 0 0 1
H 0 0 0 1 1 0 0 0 1 0
I 0 0 0 0 0 1 0 1 0 0
J 0 0 0 0 0 1 1 0 0 0