GRAPHS IMPLEMENTATION

Fred Agbo

2026-04-22

Announcements

  • Grading of MP3 is ongoing.
  • PS6 is published and due on Monday April 27th. Submission is as follows:
    • In-class show & tell by each student and then Upload to Github.
  • Last day in class is Wednesday April 29 NOT April 27th
    • Next week is dedicated to reviews of materials
  • Take note of your date for final exam:
    • Session: MW 10:20am – 11:50am: Tue., May 5th, from 8:00 a.m. - 11:00 a.m.
    • Session: MW 8:40am – 10:10am: Wed., May 6th, from 8:00 a.m. - 11:00 a.m.
    • Those with accommodation and writing the exam in testing center should communicate them and cc me to book a spot ASAP.
  • Few minutes will be dedicated to filling out the Course Evaluation Canvas next week.

Developing a Graph Collection

  • Users should be able to
    • Insert and remove vertices
    • Insert or remove an edge
    • Retrieve all the vertices and edges

Graph Implementation

  • Paths of least resistance in the graph implementation
    • You make a graph class a subclass of AbstractCollection
    • You make a graph’s size equal to its number of vertices
    • The add method adds a vertex with the given label to a graph
    • You allow a graph’s iterator to visit its vertices
  • Consequences
    • The len function returns the number of the graph’s vertices
    • The graph constructor’s source collection contains the labels of the new graph’s vertices
    • The for loop visits the graph’s vertices
    • The in operator returns True if the graph contains a given vertex
    • The == operator compares vertices in the two graph operands
    • The + operator creates a new graph that contains the vertices of its two operands

Graph Implementation: Classess & Methods

  • The graph implementation described here uses a linked structure.
    • Each vertex and edge is represented as an object (node) that contains references (links) to other vertices and edges.
    • This approach allows efficient traversal and modification of the graph, as each vertex maintains links to its adjacent edges and vertices.
  • Three Classes to model
    • Class LinkedDirectedGraph
    • Class LinkedVertex
    • Class LinkedEdge

Class LinkedDirectedGraph(1/2)

The LinkedDirectedGraph class typically provides the following methods:

Method What It Does
Graph Creation
g = LinkedDirectedGraph(sourceCollection=None) Creates a new directed graph using an adjacency list. Optionally adds vertices from a collection of labels.
Clearing, Size, and String Representation
g.clear() Removes all vertices from the graph.
g.clearEdgeMarks() Clears all edge marks.
g.clearVertexMarks() Clears all vertex marks.
g.isEmpty() Returns True if the graph contains no vertices.
g.sizeEdges() Returns the number of edges in the graph.
g.sizeVertices() Returns the number of vertices in the graph.
g.__str__() Returns a string representation of the graph.
Vertex-Related Methods
g.containsVertex(label) Returns True if the graph contains a vertex with the specified label.
g.addVertex(label) Adds a vertex with the specified label.
g.getVertex(label) Returns the vertex with the specified label, or None if not found.
g.removeVertex(label) Removes and returns the vertex with the specified label, or None if not found.

Class LinkedDirectedGraph (2/2)

Method What It Does
Edge-Related Methods
g.containsEdge(fromLabel, toLabel) Returns True if an edge exists from fromLabel to toLabel.
g.addEdge(fromLabel, toLabel, weight=None) Adds an edge with the specified weight between the given vertices.
g.getEdge(fromLabel, toLabel) Returns the edge between the specified vertices, or None if not found.
g.removeEdge(fromLabel, toLabel) Removes the edge and returns True if successful, else False.
Iterators
g.edges() Returns an iterator over the edges in the graph.
g.getVertices() Returns an iterator over the vertices in the graph.
g.incidentEdges(label) Returns an iterator over the incident edges of the vertex with the given label.
g.neighboringVertices(label) Returns an iterator over the neighboring vertices of the given vertex.
  • The implementation of LinkedDirectedGraph maintains a dictionary whose keys are labels and whose values are the corresponding vertices

The Class LinkedDirectedGraph ADT

class LinkedDirectedGraph(AbstractCollection):
    """A graph has a count of vertices, a count of edges,
    and a dictionary of label/vertex pairs."""
    def __init__(self, sourceCollection = None):
        self.edgeCount = 0
        self. vertices = {}
        AbstractCollection.__init__(self, sourceCollection)
        
    # Method for clearing, marks, sizes, string rep
    
    def clear(self):
        """clears the graph"""
        self.size = 0
        self.edgeCount = 0
        self.vertices = {}
        
    def clearEdgeMarks(self):
        """Clear all the edge marks"""
        for edge in self.edges():
            edge.clearMark()
            
    def sizeEdges(self):
        """Returns the number of edges """
        return self.edgeCount
    
    def sizeVertices(self):
        """Returns the number of vertices """
        return len(self)
    
    def __str__(self):
        """Returns the string respresentation of the graph """
        result = str(len(self)) + " vertices: "
        for vertex in self.vertices:
            result += " " + str(vertex)
        result += "\n"
        result += str(self.sizeEdges()) + " Edges: "
        for edge in self.edges():
            result += " " + str(edge)
        return result
    
    def add(self, label):
        """For compatibility with other collections."""
        self.addVertex(label)
        
    # Vertex related methods
    
    def addVertex(self, label):
        """Precondition: a vertex with label must not
        already be in the graph.
        Raises: AttibuteError if a vertex with label
        is already in the graph."""
        if self.containsVertex(label):
            raise AttributeError("Label "+ str(label) + " already in graph")
        self.vertices[label] = LinkedVertex(label)
        self.size += 1
    
    def containsVertex(self, label):
        return label in self.vertices
    
    def getVertex(self, label):
        """ pre-condition: a vetext with label must already be in the graph"""
        if not self.containsVertex(label):
            raise AttributeError("Label "+ str(label) + " not in graph.")
        return self.vertices[label]
    
    def removeVertex(self, label):
        """Returns True if the vertex was removed, or False otherwise."""
        removedVertex = self.vertices.pop(label, None)
        if removedVertex is None:
            return False
        # Examine all other vertices to remove edges 
        for vertex in self.getVertices():
            if vertex.removeEdgesTo(removedVertex):
                self.edgeCount -= 1
                
        # Examine all edges from the removed vertex to others
        for edge in removedVertex.incidentEdges():
            self.edgeCount -= 1
        self.size -=1
        return True
    
    # Methods related to edges
    
    def addEdge(self, fromLabel, toLabel, weight):
        fromVertex = self.getVertex(fromLabel)
        toVertex = self.getVertex(toLabel)
        if self.getEdge(fromLabel, toLabel):
            raise AttributeError("An edge already connects " + str(fromLabel) + " and "+ str(toLabel))
        fromVertex.addEdgeTo(toVertex, weight)
        self.edgeCount += 1
        
    def containsEdge(self, fromeLabel, toLabel):
        return self.getEdge(fromeLabel, toLabel) != None
    
    def getEdge(self, fromLabel, toLabel):
        fromVertex = self.getVertex(fromLabel)
        toVertex = self.getVertex(toLabel)
        return fromVertex.getEdgeTo(toVertex)
    
    def removeEdge(self, fromLabel, toLabel):
        fromVertex = self.getVertex(fromLabel)
        toVertex = self.getVertex(toLabel)
        edgeRemovedFlg = fromVertex.removeEdgeTo(toVertex)
        if edgeRemovedFlg:
            self.edgeCount -= 1
        return edgeRemovedFlg
    
    # Iterators 
    
    def __iter__(self):
        """Supports iteration over a view of self (the vertices)."""
        return self.getVertices()
    
    def edges(self):
        """Supports iteration over the edges in the graph."""
        result = []
        for vertex in self.getVertices():
            result += list(vertex.incidentEdges())
        return iter(result)
    
    def getVertices(self):
        """Supports iteration over the vertices in the graph."""
        return iter(self.vertices.values()) 
    
    def incidentEdges(self, label):
        """Supports iteration over the incident edges of the
        given verrtex."""
        return self.getVertex(label).incidentEdges()
    
    def neighboringVertices(self, label):
        return self.getVertex(label).neighboringVertices()

The Class LinkedVertex

  • The methods in the class LinkedVertex
Method What It Does
LinkedVertex(label) Creates a vertex with the specified label. The vertex is initially unmarked.
clearMark() Unmarks the vertex.
setMark() Marks the vertex.
isMarked() Returns True if the vertex is marked, or False otherwise.
getLabel() Returns the label of the vertex.
setLabel(label, g) Changes the label of the vertex in graph g to label.
addEdgeTo(toVertex, weight) Adds an edge with the given weight from this vertex to toVertex.
getEdgeTo(toVertex) Returns the edge from this vertex to toVertex, or None if the edge does not exist.
incidentEdges() Returns an iterator over the incident edges of the vertex.
neighboringVertices() Returns an iterator over the neighboring vertices of the vertex.
__str__() Returns a string representation of the vertex.
__eq__(anyObject) Returns True if anyObject is a vertex and the two labels are the same.

The Class LinkedVertex: Structure

class LinkedVertex:
    # Constructor
    def __init__(self, label):
        self.label = label
        self.edgeList = []
        self.mark = False
        
          
    def clearMark(self):
        self.mark = False
        
    def isMark(self):
        """Returns True if the vertex is marked
        or False otherwise."""
        return self.mark

    def getLabel(self):
        """ Return the label for the vertex"""
        return self.label
        
    def setLabel(self, label, g):
        g.vertices.pop(self, label, None)
        g.vertices[label] = self
        self.label = label
    
    def setMark(self):
        self.mark = True
        
    def __str__(self):
        return str(self.label)
    
    def __eq__(self, other):
        if self is other: return True
        if type(self) != type(other): return False
        return self.getLabel() == other.getLabel()
    
    # Methods used by LinkedGraph
    
    def addEdgeTo(self, toVertex, weight):
        """Connects the vertices with an edge."""
        edge = LinkedEdge(self, toVertex, weight)
        self.edgeList.append(edge)
        
    def getEdgeTo(self, toVertex):
        """Return the connecting edge if it exists, or None otherwise"""
        edge = LinkedEdge(self, toVertex)
        try:
            return self.edgeList[self.edgeList.index(edge)]
        except:
            return None
        
    def incidentEdges(self):
        """Returns the incident edges of this vertex."""
        return iter(self.edgeList)
    
    def neighboringVertices(self):
        """Returns the neighboring vertices of this vertex."""
        vertices = []
        for edge in self.edgeList:
             vertices.append(edge.getOtherVertex(self))
        return iter(vertices)
    
    def removeEdgeto(self, toVertex):
        """Returns True if the edge exists and is removed,
        or False otherwise."""
        edge = LinkedEdge(self, toVertex)
        if edge in self.edgeList:
            self.edgeList.remove(edge)
            return True
        else:
            return False

The Class LinkedEdge

  • The methods in the class LinkedEdge
Method What It Does
LinkedEdge(fromVertex, toVertex, weight=None) Creates an edge with the specified vertices and weight. It is initially unmarked.
clearMark() Unmarks the edge.
setMark() Marks the edge.
isMarked() Returns True if the edge is marked, or False otherwise.
getWeight() Returns the weight of the edge.
setWeight(weight) Sets the edge’s weight to the specified weight.
getOtherVertex(vertex) Returns the edge’s other vertex.
getToVertex() Returns the edge’s destination vertex.
__str__() Returns the string representation of the edge.
__eq__(anyObject) Returns True if anyObject is an edge and the two edges are connected to the same vertices and have the same weight.

The Class LinkedEdge

  • An edge maintains references to its two vertices, its weight, and a mark
  • Although the weight can be any object labeling the edge,
    • The weight is often a number or some other comparable value
  • Two edges are considered equal if they have the same vertices and weight

The Class LinkedEdge: Structure

cclass LinkedEdge:
    # Constructor
    def __init__(self, fromVertex, toVertex, weight =None):
        self.vertex1 = fromVertex
        self.vertex2 = toVertex
        self.weight = weight
        self.mark = False
        
    def clearMark(self):
        pass
    
    def __eq__(self, other):
        """Two edges are equal if they connect
        the same vertices."""
        if self is other: return True
        if type(self) != type(other):
            return False
        return self.vertex1 == other.vertex1 and self.vertex2 == other.vertex2 and self.weight == other.weight  
    
    def getOtherVertex(self, thisVertex): 
        if thisVertex == None or thisVertex == self.vertex2:
            return self.vertex1
        else:
            return self.vertex2
    
    def getToVertex(self):
        return self.vertex2
    
    def getWeight(self):
        return self.weight
    
    def isMark(self):
        """Returns True if the edge is marked
        or False otherwise."""
        return self.mark

    def setMark(self):
        self.mark = True
    
    def setWeight(self, weight):
        self.weight = weight
        
    def __str__(self):
        return str(self.vertex1) + ">" + str(self.vertex2) + ":" + str(self.weight)

Test LinkedDirectedGraph

"""
Tests preconditions on methods.
"""

from graph import LinkedDirectedGraph

# Create a directed graph using an adjacency list
g = LinkedDirectedGraph()

# Add vertices labeled A, B, and C to the graph and print it      
g.addVertex("A")
g.addVertex("B")
g.addVertex("C")
print("Expect vertices ABC and no edges: \n" + str(g))

# Insert edges with weight 2.5 and print the graph
g.addEdge("A", "B", 2.5)
g.addEdge("B", "C", 2.5)
g.addEdge("C", "B", 2.5)
print("Expect same vertices and edges AB BC CB all with weight 2.5: \n" + str(g))

#g.addVertex("A") should return an error 
#g.addEdge("A", "D", 2.5)
#g.addEdge("A", "B", 2.5)

print("Expect A: ", g.getVertex("A"))

print("Edge from A to B:", g.getEdge("A", "B"))
print("Edge from B to A:", g.getEdge("B", "A"))

print("Incident edges of A:", list(map(str, g.incidentEdges("A"))))

Review of PS6: Homework

  • Case Study: Testing Graph Algorithms
    • In Foundamentals of Python Data Structures (2nd Ed.) by Lambert
      • See Chapter 12, pages 391 to 397

Instructions

  • This case study develops a data model and user interface that allow the programmer to create graphs and use them to test graph algorithms.
  • Use the ADT of graph implementation discussed in the class today plus other modules provided in the case study to:
    • Write a program that allows the user to test some graph-processing algorithms (see the case study for detailed implementation strategies including starter codes)

Tasks

  1. Complete the classes in the case study and test the operations to input a graph and display it.

  2. Complete the function spanTree in the case study and test it thoroughly.

  3. Complete the function shortestPaths in the case study and test it thoroughly

Homework Submission

  • This home work will be discussed in the class on Monday April 27
    • How?
      • Each student will take turn to show-&-tell by demonstrating their solution
      • Then upload the solution to Github