This is a work in progress, there’s a lot of complex questions you can ask about graphs, but I though it was neat that you could produce an actual graphy looking thing in so few lines of code. This is a directed graph, and you use the graph object to first create nodes, and then define (unweighted) edges between them or to themselves. The __repr__() method just lists the node data, whatever that is, with an ASCII arrow pointing to another node’s data.
Bare Bones
This is was my first attempt at implementing a graph. Next, I’ll look at depth first and breadth first searching.
class DirectedGraph:
linkCount = 0
class Node:
def __init__( self, data ):
self.data = data
self.links = []
def pointsTo( self, node ):
self.links.append( node )
DirectedGraph.linkCount += 1
def __init__( self ):
self.nodes = []
self.nodeCount = 0
def newNode( self, data ):
node = self.Node( data )
self.nodes.append( node )
self.nodeCount += 1
return node
def __repr__( self ):
result = ""
for node in self.nodes:
for link in node.links:
result += "{0} --> {1}\n".format( node.data, link.data )
return result
dg = DirectedGraph()
n0 = dg.newNode("A")
n1 = dg.newNode("B")
n2 = dg.newNode("C")
n0.pointsTo( n1 )
n1.pointsTo( n2 )
n2.pointsTo( n0 )
n2.pointsTo( n2 )
assert dg.linkCount == 4
assert dg.nodeCount == 3
Searching
What’s neat about this code is that it shows that the depth first and breadth first algorithms are essentially the same except for your choice of internal data structure. If you add child nodes to a queue, then you get breadth first behavior, but if you add child nodes to a stack, then you get depth first behavior. I mean, you could define depth first searches recursively, but then you wouldn’t see the beauty of that symmetry between depth first and breadth first searches.
class DirectedGraph:
linkCount = 0
class Node:
def __init__( self, data ):
self.data = data
self.links = []
self.visited = False
def pointsTo( self, node ):
self.links.append( node )
DirectedGraph.linkCount += 1
def __init__( self ):
self.nodes = []
self.nodeCount = 0
def unvisit( self ):
for node in self.nodes:
node.visited = False
def newNode( self, data ):
node = self.Node( data )
self.nodes.append( node )
self.nodeCount += 1
return node
def search( self, node, method ):
self.unvisit()
# `data` is short for `data structure`
data = [ node ]
while True:
if len( data ) == 0:
break
# treat `data` as a stack
if method == "depth first":
node = data.pop()
# treat `data` as a queue
elif method == "breadth first":
node = data.pop(0)
if node.visited == True:
continue
node.visited = True
print "visiting {}".format( node.data )
for link in node.links:
data.append( link )
def depthFirstSearch( self, node ):
self.search( node, "depth first" )
def breadthFirstSearch( self, node ):
self.search( node, "breadth first" )
def __repr__( self ):
result = ""
for node in self.nodes:
for link in node.links:
result += "{0} --> {1}\n".format( node.data, link.data )
return result