0% found this document useful (0 votes)
585 views21 pages

Graph Theory Implementations in Python

This document discusses graphs and graph theory. It begins with the origins of graph theory in solving the Königsberg bridge problem in the 18th century. It then provides an introduction to graph theory concepts like vertices, edges, directed and undirected graphs. It discusses representing graphs in Python using dictionaries and provides an example graph class implementation with methods for vertices, edges, adding vertices and edges. Finally, it discusses finding paths in graphs and provides a method for finding a path between vertices.

Uploaded by

tuadongsong
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
585 views21 pages

Graph Theory Implementations in Python

This document discusses graphs and graph theory. It begins with the origins of graph theory in solving the Königsberg bridge problem in the 18th century. It then provides an introduction to graph theory concepts like vertices, edges, directed and undirected graphs. It discusses representing graphs in Python using dictionaries and provides an example graph class implementation with methods for vertices, edges, adding vertices and edges. Finally, it discusses finding paths in graphs and provides a method for finding a path between vertices.

Uploaded by

tuadongsong
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
  • Graphs in Python
  • Graphs as a Python Class
  • Paths in Graphs
  • Degree of a Vertex
  • Degree Sequence
  • Graph Density
  • Connected Graphs
  • Distance and Diameter of a Graph
  • Tree/Fores
  • Spanning Tree
  • Complete Python Graph Class

GRAPHSINPYTHON

ORIGINSOFGRAPHTHEORY
Beforewestartwiththeactualimplementationsof
graphsinPythonandbeforewestartwiththe
introductionofPythonmodulesdealingwithgraphs,we
wanttodevoteourselvestotheoriginsofgraphtheory.
TheoriginstakeusbackintimetotheKnigsbergofthe
18thcentury.KnigsbergwasacityinPrussiathattime.
TheriverPregelflowedthroughthetown,creatingtwo
islands.Thecityandtheislandswereconnectedby
sevenbridgesasshown.Theinhabitantsofthecitywere
movedbythequestion,ifitwaspossibletotakeawalk
throughthetownbyvisitingeachareaofthetownand
crossingeachbridgeonlyonce?Everybridgemusthave
beencrossedcompletely,i.e.itisnotallowedtowalk
halfwayontoabridgeandthenturnaroundandlater
crosstheotherhalffromtheotherside.Thewalkneed
notstartandendatthesamespot.LeonhardEuler
solvedtheproblemin1735byprovingthatitisnot
possible.Hefoundoutthatthechoiceofarouteinside
eachlandareaisirrelevantandthattheonlythingwhich
matteredistheorder(orthesequence)inwhichthe
bridgesarecrossed.Hehadformulatedanabstractionoftheproblem,eliminatingunnecessaryfactsandfocussingon
thelandareasandthebridgesconnectingthem.Thisway,hecreatedthefoundationsofgraphtheory.Ifweseea"land
area"asavertexandeachbridgeasanedge,wehave"reduced"theproblemtoagraph.

INTRODUCTIONINTOGRAPHTHEORYUSINGPYTHON
BeforewestartourtreatizeonpossiblePythonrepresentations
ofgraphs,wewanttopresentsomegeneraldefinitionsofgraphs
anditscomponents.
A"graph"1inmathematicsandcomputerscienceconsistsof
"nodes",alsoknownas"vertices".Nodesmayormaynotbe
connectedwithoneanother.Inourillustration,whichisa
pictorialrepresentationofagraph,thenode"a"isconnected
withthenode"c",but"a"isnotconnectedwith"b".The
connectinglinebetweentwonodesiscalledanedge.Ifthe
edgesbetweenthenodesareundirected,thegraphiscalledan
undirectedgraph.Ifanedgeisdirectedfromonevertex(node)
toanother,agraphiscalledadirectedgraph.Andirectededge
iscalledanarc.
Thoughgraphsmaylookverytheoretical,manypractical
problemscanberepresentedbygraphs.Theyareoftenusedto
modelproblemsorsituationsinphysics,biology,psychologyandaboveallincomputerscience.Incomputerscience,
graphsareusedtorepresentnetworksofcommunication,dataorganization,computationaldevices,theflowof
computation,
Inthelattercase,theareusedtorepresentthedataorganisation,likethefilesystemofanoperatingsystem,or
communicationnetworks.Thelinkstructureofwebsitescanbeseenasagraphaswell,i.e.adirectedgraph,becausea
linkisadirectededgeoranarc.
Pythonhasnobuiltindatatypeorclassforgraphs,butitiseasytoimplementtheminPython.Onedatatypeisideal
forrepresentinggraphsinPython,i.e.dictionaries.Thegraphinourillustrationcanbeimplementedinthefollowing
way:
graph={"a":["c"],
"b":["c","e"],
"c":["a","b","d","e"],
"d":["c"],
"e":["c","b"],
"f":[]
}

Thekeysofthedictionaryabovearethenodesofourgraph.Thecorrespondingvaluesarelistswiththenodes,which
areconnectingbyanedge.Thereisnosimplerandmoreelegantwaytorepresentagraph.
Anedgecanbeseenasa2tuplewithnodesaselements,i.e.("a","b")
Functiontogeneratethelistofalledges:
defgenerate_edges(graph):
edges=[]
fornodeingraph:
forneighbouringraph[node]:
edges.append((node,neighbour))
returnedges
print(generate_edges(graph))
Thiscodegeneratesthefollowingoutput,ifcombinedwiththepreviouslydefinedgraphdictionary:

$python3graph_simple.py
[('a','c'),('c','a'),('c','b'),('c','d'),('c','e'),('b','c'),
('b','e'),('e','c'),('e','b'),('d','c')]
Aswecansee,thereisnoedgecontainingthenode"f"."f"isanisolatednodeofourgraph.
ThefollowingPythonfunctioncalculatestheisolatednodesofagivengraph:
deffind_isolated_nodes(graph):
"""returnsalistofisolatednodes."""
isolated=[]
fornodeingraph:
ifnotgraph[node]:
isolated+=node
returnisolated
Ifwecallthisfunctionwithourgraph,alistcontaining"f"willbereturned:["f"]

GRAPHSASAPYTHONCLASS
Beforewegoonwithwritingfunctionsforgraphs,wehavea
firstgoataPythongraphclassimplementation.Ifyoulookat
thefollowinglistingofourclass,youcanseeinthe__init__
methodthatweuseadictionary"self.__graph_dict"forstoring
theverticesandtheircorrespondingadjacentvertices.
"""APythonClass
AsimplePythongraphclass,
demonstratingtheessential
factsandfunctionalitiesofgraphs.
"""
classGraph(object):
def__init__(self,graph_dict=None):
"""initializesagraphobject
IfnodictionaryorNoneisgiven,
anemptydictionarywillbeused
"""
ifgraph_dict==None:
graph_dict={}
self.__graph_dict=graph_dict
defvertices(self):
"""returnstheverticesofagraph"""

returnlist(self.__graph_dict.keys())
defedges(self):
"""returnstheedgesofagraph"""
returnself.__generate_edges()
defadd_vertex(self,vertex):
"""Ifthevertex"vertex"isnotin
self.__graph_dict,akey"vertex"withanempty
listasavalueisaddedtothedictionary.
Otherwisenothinghastobedone.
"""
ifvertexnotinself.__graph_dict:
self.__graph_dict[vertex]=[]
defadd_edge(self,edge):
"""assumesthatedgeisoftypeset,tupleorlist
betweentwoverticescanbemultipleedges!
"""
edge=set(edge)
(vertex1,vertex2)=tuple(edge)
ifvertex1inself.__graph_dict:
self.__graph_dict[vertex1].append(vertex2)
else:
self.__graph_dict[vertex1]=[vertex2]
def__generate_edges(self):
"""Astaticmethodgeneratingtheedgesofthe
graph"graph".Edgesarerepresentedassets
withone(aloopbacktothevertex)ortwo
vertices
"""
edges=[]
forvertexinself.__graph_dict:
forneighbourinself.__graph_dict[vertex]:
if{neighbour,vertex}notinedges:
edges.append({vertex,neighbour})
returnedges
def__str__(self):
res="vertices:"
forkinself.__graph_dict:
res+=str(k)+""
res+="\nedges:"
foredgeinself.__generate_edges():
res+=str(edge)+""
returnres
if__name__=="__main__":
g={"a":["d"],
"b":["c"],
"c":["b","c","d","e"],
"d":["a","c"],
"e":["c"],
"f":[]
}
graph=Graph(g)
print("Verticesofgraph:")
print(graph.vertices())
print("Edgesofgraph:")
print(graph.edges())
print("Addvertex:")

graph.add_vertex("z")
print("Verticesofgraph:")
print(graph.vertices())

print("Addanedge:")
graph.add_edge({"a","z"})

print("Verticesofgraph:")
print(graph.vertices())
print("Edgesofgraph:")
print(graph.edges())
print('Addinganedge{"x","y"}withnewvertices:')
graph.add_edge({"x","y"})
print("Verticesofgraph:")
print(graph.vertices())
print("Edgesofgraph:")
print(graph.edges())

Ifyoustartthismodulestandalone,youwillgetthefollowingresult:
$python3graph.py
Verticesofgraph:
['a','c','b','e','d','f']
Edgesofgraph:
[{'a','d'},{'c','b'},{'c'},{'c','d'},{'c','e'}]
Addvertex:
Verticesofgraph:
['a','c','b','e','d','f','z']
Addanedge:
Verticesofgraph:
['a','c','b','e','d','f','z']
Edgesofgraph:
[{'a','d'},{'c','b'},{'c'},{'c','d'},{'c','e'},{'a','z'}]
Addinganedge{"x","y"}withnewvertices:
Verticesofgraph:
['a','c','b','e','d','f','y','z']
Edgesofgraph:
[{'a','d'},{'c','b'},{'c'},{'c','d'},{'c','e'},{'a','z'},{'y',
'x'}]

PATHSINGRAPHS
Wewanttofindnowtheshortestpathfromonenodetoanothernode.BeforewecometothePythoncodeforthis
problem,wewillhavetopresentsomeformaldefinitions.
Adjacentvertices:
Twoverticesareadjacentwhentheyarebothincidenttoacommonedge.
PathinanundirectedGraph:
ApathinanundirectedgraphisasequenceofverticesP=(v1,v2,...,vn)VxVx...xVsuchthatviisadjacentto
v{i+1}for1i<n.SuchapathPiscalledapathoflengthnfromv1tovn.
SimplePath:
Apathwithnorepeatedverticesiscalledasimplepath.
Example:
(a,c,e)isasimplepathinourgraph,aswellas(a,c,e,b).(a,c,e,b,c,d)isapathbutnotasimple
path,becausethenodecappearstwice.
Thefollowingmethodfindsapathfromastartvertextoanendvertex:

deffind_path(self,start_vertex,end_vertex,path=None):
"""findapathfromstart_vertextoend_vertex
ingraph"""
ifpath==None:
path=[]
graph=self.__graph_dict
path.append(start_vertex)
ifstart_vertex==end_vertex:
returnpath
ifstart_vertexnotingraph:
returnNone
forvertexingraph[start_vertex]:
ifvertexnotinpath:
extended_path=self.find_path(vertex,
end_vertex,
path)
ifextended_path:
returnextended_path
returnNone
Ifwesaveourgraphclassincludingthefind_pathmethodas"graphs.py",wecancheckthewayofworkingofour
find_pathfunction:
fromgraphsimportGraph
g={"a":["d"],
"b":["c"],
"c":["b","c","d","e"],
"d":["a","c"],
"e":["c"],
"f":[]
}
graph=Graph(g)
print("Verticesofgraph:")
print(graph.vertices())
print("Edgesofgraph:")
print(graph.edges())
print('Thepathfromvertex"a"tovertex"b":')
path=graph.find_path("a","b")
print(path)
print('Thepathfromvertex"a"tovertex"f":')
path=graph.find_path("a","f")
print(path)
print('Thepathfromvertex"c"tovertex"c":')
path=graph.find_path("c","c")
print(path)
Theresultofthepreviousprogramlookslikethis:
Verticesofgraph:
['e','a','d','f','c','b']
Edgesofgraph:
[{'e','c'},{'a','d'},{'d','c'},{'b','c'},{'c'}]
Thepathfromvertex"a"tovertex"b":
['a','d','c','b']
Thepathfromvertex"a"tovertex"f":
None
Thepathfromvertex"c"tovertex"c":
['c']

Themethodfind_all_pathsfindsallthepathsbetweenastartvertextoanendvertex:
deffind_all_paths(self,start_vertex,end_vertex,path=[]):
"""findallpathsfromstart_vertexto
end_vertexingraph"""
graph=self.__graph_dict
path=path+[start_vertex]
ifstart_vertex==end_vertex:
return[path]
ifstart_vertexnotingraph:
return[]
paths=[]
forvertexingraph[start_vertex]:
ifvertexnotinpath:
extended_paths=self.find_all_paths(vertex,
end_vertex,
path)
forpinextended_paths:
paths.append(p)
returnpaths
Weslightlychangedourexamplegraphbyaddingedgesfrom"a"to"f"andfrom"f"to"d"totestthepreviously
definedmethod:
fromgraphsimportGraph
g={"a":["d","f"],
"b":["c"],
"c":["b","c","d","e"],
"d":["a","c"],
"e":["c"],
"f":["d"]
}
graph=Graph(g)
print("Verticesofgraph:")
print(graph.vertices())
print("Edgesofgraph:")
print(graph.edges())
print('Allpathsfromvertex"a"tovertex"b":')
path=graph.find_all_paths("a","b")
print(path)
print('Allpathsfromvertex"a"tovertex"f":')
path=graph.find_all_paths("a","f")
print(path)
print('Allpathsfromvertex"c"tovertex"c":')
path=graph.find_all_paths("c","c")
print(path)
Theresultlookslikethis:
Verticesofgraph:
['d','c','b','a','e','f']
Edgesofgraph:
[{'d','a'},{'d','c'},{'c','b'},{'c'},{'c','e'},{'f','a'},{'d',
'f'}]
Allpathsfromvertex"a"tovertex"b":
[['a','d','c','b'],['a','f','d','c','b']]
Allpathsfromvertex"a"tovertex"f":

[['a','f']]
Allpathsfromvertex"c"tovertex"c":
[['c']]

DEGREE
Thedegreeofavertexvinagraphisthenumberofedges
connectingit,withloopscountedtwice.Thedegreeofavertex
visdenoteddeg(v).ThemaximumdegreeofagraphG,denoted
by(G),andtheminimumdegreeofagraph,denotedby(G),
arethemaximumandminimumdegreeofitsvertices.
Inthegraphontherightside,themaximumdegreeis5atvertex
candtheminimumdegreeis0,i.etheisolatedvertexf.
Ifallthedegreesinagrapharethesame,thegraphisaregular
graph.Inaregulargraph,alldegreesarethesame,andsowe
canspeakofthedegreeofthegraph.
Thedegreesumformula(Handshakinglemma):
vVdeg(v)=2|E|
Thismeansthatthesumofdegreesofalltheverticesisequaltothenumberofedgesmultipliedby2.Wecan
concludethatthenumberofverticeswithodddegreehastobeeven.Thisstatementisknownasthehandshaking
lemma.Thename"handshakinglemma"stemsfromapopularmathematicalproblem:Inanygroupofpeoplethe
numberofpeoplewhohaveshakenhandswithanoddnumberofotherpeoplefromthegroupiseven.
Thefollowingmethodcalculatesthedegreeofavertex:
defvertex_degree(self,vertex):
"""Thedegreeofavertexisthenumberofedgesconnecting
it,i.e.thenumberofadjacentvertices.Loopsarecounted
double,i.e.everyoccurenceofvertexinthelist
ofadjacentvertices."""
adj_vertices=self.__graph_dict[vertex]
degree=len(adj_vertices)+adj_vertices.count(vertex)
returndegree
Thefollowingmethodcalculatesalistcontainingtheisolatedverticesofagraph:
deffind_isolated_vertices(self):
"""returnsalistofisolatedvertices."""
graph=self.__graph_dict
isolated=[]
forvertexingraph:
print(isolated,vertex)
ifnotgraph[vertex]:
isolated+=[vertex]
returnisolated
ThemethodsdeltaandDeltacanbeusedtocalculatetheminimumandmaximumdegreeofavertexrespectively:
defdelta(self):
"""theminimumdegreeofthevertices"""
min=100000000
forvertexinself.__graph_dict:
vertex_degree=self.vertex_degree(vertex)
ifvertex_degree<min:
min=vertex_degree
returnmin

defDelta(self):
"""themaximumdegreeofthevertices"""
max=0
forvertexinself.__graph_dict:
vertex_degree=self.vertex_degree(vertex)
ifvertex_degree>max:
max=vertex_degree
returnmax

DEGREESEQUENCE
Thedegreesequenceofanundirectedgraphisdefinedasthesequenceofitsvertexdegreesinanonincreasingorder.
Thefollowingmethodreturnsatuplewiththedegreesequenceoftheinstancegraph:
defdegree_sequence(self):
"""calculatesthedegreesequence"""
seq=[]
forvertexinself.__graph_dict:
seq.append(self.vertex_degree(vertex))
seq.sort(reverse=True)
returntuple(seq)
Thedegreesequenceofourexamplegraphisthefollowingsequenceofintegers:(5,2,1,1,1,0).
Isomorphicgraphshavethesamedegreesequence.However,twographswiththesamedegreesequencearenot
necessarilyisomorphic.
Thereisthequestionwhetheragivendegreesequencecanberealizedbyasimplegraph.TheErdsGallaitheorem
statesthatanonincreasingsequenceofnnumbersdi(fori=1,...,n)isthedegreesequenceofasimplegraphifand
onlyifthesumofthesequenceisevenandthefollowingconditionisfulfilled:

IMPLEMENTATIONOFTHEERDSGALLAITHEOREM
Ourgraphclassseefurtherdownforacompletelistingcontainsamethod"erdoes_gallai"whichdecides,ifa
sequencefulfillstheErdsGallaitheorem.First,wecheck,ifthesumoftheelementsofthesequenceisodd.Ifsothe
functionreturnsFalse,becausetheErdsGallaitheoremcan'tbefulfilledanymore.Afterthiswecheckwiththestatic
methodis_degree_sequencewhethertheinputsequenceisadegreesequence,i.e.thattheelementsofthesequence
arenonincreasing.Thisiskindofsuperfluous,astheinputissupposedtobeadegreesequence,soyoumaydropthis
checkforefficiency.Now,wecheckinthebodyofthesecondifstatement,iftheinequationofthetheoremis
fulfilled:
@staticmethod
deferdoes_gallai(dsequence):
"""ChecksiftheconditionoftheErdoesGallaiinequality
isfullfilled
"""
ifsum(dsequence)%2:
#sumofsequenceisodd
returnFalse
ifGraph.is_degree_sequence(dsequence):
forkinrange(1,len(dsequence)+1):
left=sum(dsequence[:k])
right=k*(k1)+sum([min(x,k)forxindsequence[k:]])
ifleft>right:
returnFalse
else:
#sequenceisincreasing

returnFalse
returnTrue

Versionwithoutthesuperfluousdegreesequencetest:
@staticmethod
deferdoes_gallai(dsequence):
"""ChecksiftheconditionoftheErdoesGallaiinequality
isfullfilled
dsequencehastobeavaliddegreesequence
"""
ifsum(dsequence)%2:
#sumofsequenceisodd
returnFalse
forkinrange(1,len(dsequence)+1):
left=sum(dsequence[:k])
right=k*(k1)+sum([min(x,k)forxindsequence[k:]])
ifleft>right:
returnFalse
returnTrue

GRAPHDENSITY
Thegraphdensityisdefinedastheratioofthenumberofedgesofagivengraph,andthetotalnumberofedges,the
graphcouldhave.Inotherwords:Itmeasureshowcloseagivengraphistoacompletegraph.
Themaximaldensityis1,ifagraphiscomplete.Thisisclear,becausethemaximumnumberofedgesinagraph
dependsontheverticesandcanbecalculatedas:
max.numberofedges=*|V|*(|V|1).
Ontheotherhandtheminimaldensityis0,ifthegraphhasnoedges,i.e.itisanisolatedgraph.
Forundirectedsimplegraphs,thegraphdensityisdefinedas:

Adensegraphisagraphinwhichthenumberofedgesisclosetothemaximalnumberofedges.Agraphwithonlya
fewedges,iscalledasparsegraph.Thedefinitionforthosetwotermsisnotverysharp,i.e.thereisnoleastupper
bound(supremum)forasparsedensityandnogreatestlowerbound(infimum)fordefiningadensegraph.
TheprecisestmathematicalnotationusesthebigOnotation:
SparseGraph:DenseGraph:
AdensegraphisagraphG=(V,E)inwhich|E|=(|V|2).
"density"isamethodofourclasstocalculatethedensityofagraph:
defdensity(self):
"""methodtocalculatethedensityofagraph"""
g=self.__graph_dict
V=len(g.keys())
E=len(self.edges())
return2.0*E/(V*(V1))
Wecantestthismethodwiththefollowingscript.
fromgraph2importGraph
g={"a":["d","f"],
"b":["c","b"],
"c":["b","c","d","e"],
"d":["a","c"],
"e":["c"],
"f":["a"]
}

complete_graph={
"a":["b","c"],
"b":["a","c"],
"c":["a","b"]
}
isolated_graph={
"a":[],
"b":[],
"c":[]
}
graph=Graph(g)
print(graph.density())
graph=Graph(complete_graph)
print(graph.density())
graph=Graph(isolated_graph)
print(graph.density())
Acompletegraphhasadensityof1andisolatedgraphhasadensityof0,aswecanseefromtheresultsofthe
previoustestscript:
$pythontest_density.py
0.466666666667
1.0
0.0

CONNECTEDGRAPHS
Agraphissaidtobeconnectedifeverypairofverticesinthe
graphisconnected.Theexamplegraphontherightsideisa
connectedgraph.
Itpossibletodeterminewithasimplealgorithmwhetheragraph
isconnected:
1.ChooseanarbitrarynodexofthegraphGasthestarting
point
2.DeterminethesetAofallthenodeswhichcanbe
reachedfromx.
3.IfAisequaltothesetofnodesofG,thegraphis
connectedotherwiseitisdisconnected.
Weimplementamethodis_connectedtocheckifagraphisa
connectedgraph.Wedon'tputemphasisonefficiencybutonreadability.
defis_connected(self,
vertices_encountered=None,
start_vertex=None):
"""determinesifthegraphisconnected"""
ifvertices_encounteredisNone:
vertices_encountered=set()
gdict=self.__graph_dict
vertices=list(gdict.keys())#"list"necessaryinPython3
ifnotstart_vertex:
#chosseavertexfromgraphasastartingpoint
start_vertex=vertices[0]
vertices_encountered.add(start_vertex)
iflen(vertices_encountered)!=len(vertices):

forvertexingdict[start_vertex]:
ifvertexnotinvertices_encountered:
ifself.is_connected(vertices_encountered,vertex):
returnTrue
else:
returnTrue
returnFalse

Ifyouaddthismethodtoourgraphclass,wecantestitwiththefollowingscript.Assumingthatyousavethegraph
classasgraph2.py:
fromgraph2importGraph
g={"a":["d"],
"b":["c"],
"c":["b","c","d","e"],
"d":["a","c"],
"e":["c"],
"f":[]
}
g2={"a":["d","f"],
"b":["c"],
"c":["b","c","d","e"],
"d":["a","c"],
"e":["c"],
"f":["a"]
}
g3={"a":["d","f"],
"b":["c","b"],
"c":["b","c","d","e"],
"d":["a","c"],
"e":["c"],
"f":["a"]
}
graph=Graph(g)
print(graph)
print(graph.is_connected())
graph=Graph(g2)
print(graph)
print(graph.is_connected())
graph=Graph(g3)
print(graph)
print(graph.is_connected())

AconnectedcomponentisamaximalconnectedsubgraphofG.Eachvertexbelongstoexactlyoneconnected
component,asdoeseachedge.

DISTANCEANDDIAMETEROFAGRAPH
Thedistance"dist"betweentwoverticesinagraphisthelengthoftheshortestpathbetweenthesevertices.No
backtracks,detours,orloopsareallowedforthecalculationofadistance.
Inourexamplegraphontheright,thedistancebetweenthevertexaandthevertexfis3,i.e.dist(a,f)=3,becausethe
shortestwayisviatheverticescande(orcandbalternatively).
Theeccentricityofavertexsofagraphgisthemaximaldistancetoeveryothervertexofthegraph:

e(s)=max({dist(s,v)|vV})
(Visthesetofallverticesofg)
Thediameterdofagraphisdefinedasthemaximum
eccentricityofanyvertexinthegraph.Thismeansthatthe
diameteristhelengthoftheshortestpathbetweenthemost
distancednodes.Todeterminethediameterofagraph,firstfind
theshortestpathbetweeneachpairofvertices.Thegreatest
lengthofanyofthesepathsisthediameterofthegraph.
Wecandirectlyseeinourexamplegraphthatthediameteris3,
becausetheminimallengthbetweenaandfis3andthereisno
otherpairofverticeswithalongerpath.
Thefollowingmethodimplementsanalgorithmtocalculatethe
diameter.
defdiameter(self):
"""calculatesthediameterofthegraph"""

v=self.vertices()
pairs=[(v[i],v[j])foriinrange(len(v)1)forjinrange(i+1,
len(v))]
smallest_paths=[]
for(s,e)inpairs:
paths=self.find_all_paths(s,e)
smallest=sorted(paths,key=len)[0]
smallest_paths.append(smallest)
smallest_paths.sort(key=len)
#longestpathisattheendoflist,
#i.e.diametercorrespondstothelengthofthispath
diameter=len(smallest_paths[1])
returndiameter
Wecancalculatethediameterofourexamplegraphwiththefollowingscript,assumingagain,thatourcomplete
graphclassissavedasgraph2.py:
fromgraph2importGraph
g={"a":["c"],
"b":["c","e","f"],
"c":["a","b","d","e"],
"d":["c"],
"e":["b","c","f"],
"f":["b","e"]
}
graph=Graph(g)
diameter=graph.diameter()
print(diameter)
Itwillprintoutthevalue3.

THECOMPLETEPYTHONGRAPHCLASS
InthefollowingPythoncode,youfindthecompletePythonClassModulewithallthediscussedmethodes:graph2.py

TREE/FOREST

Atreeisanundirectedgraphwhichcontainsnocycles.Thismeansthatanytwoverticesofthegraphareconnected
byexactlyonesimplepath.
Aforestisadisjointunionoftrees.Contrarytoforestsinnature,aforestingraphtheorycanconsistofasingletree!
Agraphwithonevertexandnoedgeisatree(andaforest).
Anexampleofatree:

Whilethepreviousexampledepictsagraphwhichisatreeandforest,thefollowingpictureshowsagraphwhich
consistsoftwotrees,i.e.thegraphisaforestbutnotatree:

OVERVIEWOFFORESTS:
Withonevertex:

Forestgraphswithtwovertices:

Forestgraphswiththreevertices:

SPANNINGTREE
AspanningtreeTofaconnected,undirectedgraphGisasubgraphG'ofG,whichisatree,andG'containsallthe
verticesandasubsetoftheedgesofG.G'containsalltheedgesofG,ifGisatreegraph.Informally,aspanningtree
ofGisaselectionofedgesofGthatformatreespanningeveryvertex.Thatis,everyvertexliesinthetree,butno
cycles(orloops)arecontained.
Example:
Afullyconnectedgraph:

Twospanningtreesfromthepreviousfullyconnectedgraph:

HAMILTONIANGAME

AnHamiltonianpathisapathinanundirectedordirectedgraphthatvisitseachvertexexactlyonce.AHamiltonian
cycle(orcircuit)isaHamiltonianpaththatisacycle.
Noteforcomputerscientists:Generally,itisnotnotpossibletodetermine,whethersuchpathsorcyclesexistin
arbitrarygraphs,becausetheHamiltonianpathproblemhasbeenproventobeNPcomplete.
ItisnamedafterWilliamRowanHamiltonwhoinventedthesocalled"icosiangame",orHamilton'spuzzle,which
involvesfindingaHamiltoniancycleintheedgegraphofthedodecahedron.Hamiltonsolvedthisproblemusingthe
icosiancalculus,analgebraicstructurebasedonrootsofunitywithmanysimilaritiestothequaternions,whichhealso
invented.

COMPLETELISTINGOFTHEGRAPHCLASS
"""APythonClass
AsimplePythongraphclass,demonstratingthe
essential
factsandfunctionalitiesofgraphs.
"""
classGraph(object):
def__init__(self,graph_dict=None):
"""initializesagraphobject
IfnodictionaryorNoneisgiven,anemptydictionarywillbe
used
"""
ifgraph_dict==None:
graph_dict={}
self.__graph_dict=graph_dict
defvertices(self):
"""returnstheverticesofagraph"""
returnlist(self.__graph_dict.keys())
defedges(self):
"""returnstheedgesofagraph"""
returnself.__generate_edges()
defadd_vertex(self,vertex):
"""Ifthevertex"vertex"isnotin
self.__graph_dict,akey"vertex"withanempty
listasavalueisaddedtothedictionary.
Otherwisenothinghastobedone.
"""
ifvertexnotinself.__graph_dict:
self.__graph_dict[vertex]=[]
defadd_edge(self,edge):
"""assumesthatedgeisoftypeset,tupleorlist
betweentwoverticescanbemultipleedges!
"""
edge=set(edge)
vertex1=edge.pop()
ifedge:
#notaloop
vertex2=edge.pop()
else:
#aloop
vertex2=vertex1
ifvertex1inself.__graph_dict:
self.__graph_dict[vertex1].append(vertex2)
else:
self.__graph_dict[vertex1]=[vertex2]
def__generate_edges(self):
"""Astaticmethodgeneratingtheedgesofthe
graph"graph".Edgesarerepresentedassets
withone(aloopbacktothevertex)ortwo
vertices
"""
edges=[]
forvertexinself.__graph_dict:
forneighbourinself.__graph_dict[vertex]:
if{neighbour,vertex}notinedges:

edges.append({vertex,neighbour})
returnedges
def__str__(self):
res="vertices:"
forkinself.__graph_dict:
res+=str(k)+""
res+="\nedges:"
foredgeinself.__generate_edges():
res+=str(edge)+""
returnres
deffind_isolated_vertices(self):
"""returnsalistofisolatedvertices."""
graph=self.__graph_dict
isolated=[]
forvertexingraph:
print(isolated,vertex)
ifnotgraph[vertex]:
isolated+=[vertex]
returnisolated
deffind_path(self,start_vertex,end_vertex,path=[]):
"""findapathfromstart_vertextoend_vertex
ingraph"""
graph=self.__graph_dict
path=path+[start_vertex]
ifstart_vertex==end_vertex:
returnpath
ifstart_vertexnotingraph:
returnNone
forvertexingraph[start_vertex]:
ifvertexnotinpath:
extended_path=self.find_path(vertex,
end_vertex,
path)
ifextended_path:
returnextended_path
returnNone

deffind_all_paths(self,start_vertex,end_vertex,path=[]):
"""findallpathsfromstart_vertexto
end_vertexingraph"""
graph=self.__graph_dict
path=path+[start_vertex]
ifstart_vertex==end_vertex:
return[path]
ifstart_vertexnotingraph:
return[]
paths=[]
forvertexingraph[start_vertex]:
ifvertexnotinpath:
extended_paths=self.find_all_paths(vertex,
end_vertex,
path)
forpinextended_paths:
paths.append(p)
returnpaths
defis_connected(self,
vertices_encountered=None,
start_vertex=None):
"""determinesifthegraphisconnected"""
ifvertices_encounteredisNone:
vertices_encountered=set()
gdict=self.__graph_dict
vertices=list(gdict.keys())#"list"necessaryinPython3
ifnotstart_vertex:
#chosseavertexfromgraphasastartingpoint

#chosseavertexfromgraphasastartingpoint
start_vertex=vertices[0]
vertices_encountered.add(start_vertex)
iflen(vertices_encountered)!=len(vertices):
forvertexingdifromgraph2importGraph
g={"a":["d"],
"b":["c"],
"c":["b","c","d","e"],
"d":["a","c"],
"e":["c"],
"f":[]
}
graph=Graph(g)
print(graph)
fornodeingraph.vertices():
print(graph.vertex_degree(node))
print("Listofisolatedvertices:")
print(graph.find_isolated_vertices())
print("""Apathfrom"a"to"e":""")
print(graph.find_path("a","e"))
print("""Allpathesfrom"a"to"e":""")
print(graph.find_all_paths("a","e"))
print("Themaximumdegreeofthegraphis:")
print(graph.Delta())
print("Theminimumdegreeofthegraphis:")
print(graph.delta())
print("Edges:")
print(graph.edges())
print("DegreeSequence:")
ds=graph.degree_sequence()
print(ds)
fullfilling=[[2,2,2,2,1,1],
[3,3,3,3,3,3],
[3,3,2,1,1]
]
non_fullfilling=[[4,3,2,2,2,1,1],
[6,6,5,4,4,2,1],
[3,3,3,1]]
forsequenceinfullfilling+non_fullfilling:
print(sequence,Graph.erdoes_gallai(sequence))
print("Addvertex'z':")
graph.add_vertex("z")
print(graph)
print("Addedge('x','y'):")
graph.add_edge(('x','y'))
print(graph)
print("Addedge('a','d'):")
graph.add_edge(('a','d'))
print(graph)
ct[start_vertex]:
ifvertexnotinvertices_encountered:
ifself.is_connected(vertices_encountered,vertex):
returnTrue
else:
returnTrue

returnTrue
returnFalse
defvertex_degree(self,vertex):
"""Thedegreeofavertexisthenumberofedgesconnecting
it,i.e.thenumberofadjacentvertices.Loopsarecounted
double,i.e.everyoccurenceofvertexinthelist
ofadjacentvertices."""
adj_vertices=self.__graph_dict[vertex]
degree=len(adj_vertices)+adj_vertices.count(vertex)
returndegree
defdegree_sequence(self):
"""calculatesthedegreesequence"""
seq=[]
forvertexinself.__graph_dict:
seq.append(self.vertex_degree(vertex))
seq.sort(reverse=True)
returntuple(seq)
@staticmethod
defis_degree_sequence(sequence):
"""MethodreturnsTrue,ifthesequence"sequence"isa
degreesequence,i.e.anonincreasingsequence.
OtherwiseFalseisreturned.
"""
#checkifthesequencesequenceisnonincreasing:
returnall(x>=yforx,yinzip(sequence,sequence[1:]))

defdelta(self):
"""theminimumdegreeofthevertices"""
min=100000000
forvertexinself.__graph_dict:
vertex_degree=self.vertex_degree(vertex)
ifvertex_degree<min:
min=vertex_degree
returnmin

defDelta(self):
"""themaximumdegreeofthevertices"""
max=0
forvertexinself.__graph_dict:
vertex_degree=self.vertex_degree(vertex)
ifvertex_degree>max:
max=vertex_degree
returnmax
defdensity(self):
"""methodtocalculatethedensityofagraph"""
g=self.__graph_dict
V=len(g.keys())
E=len(self.edges())
return2.0*E/(V*(V1))
defdiameter(self):
"""calculatesthediameterofthegraph"""

v=self.vertices()
pairs=[(v[i],v[j])foriinrange(len(v))forjinrange(i+1,
len(v)1)]
smallest_paths=[]
for(s,e)inpairs:
paths=self.find_all_paths(s,e)
smallest=sorted(paths,key=len)[0]
smallest_paths.append(smallest)
smallest_paths.sort(key=len)
#longestpathisattheendoflist,

#longestpathisattheendoflist,
#i.e.diametercorrespondstothelengthofthispath
diameter=len(smallest_paths[1])
returndiameter
@staticmethod
deferdoes_gallai(dsequence):
"""ChecksiftheconditionoftheErdoesGallaiinequality
isfullfilled
"""
ifsum(dsequence)%2:
#sumofsequenceisodd
returnFalse
ifGraph.is_degree_sequence(dsequence):
forkinrange(1,len(dsequence)+1):
left=sum(dsequence[:k])
right=k*(k1)+sum([min(x,k)forxindsequence[k:]])
ifleft>right:
returnFalse
else:
#sequenceisincreasing
returnFalse
returnTrue
WecantestthisGraphclasswiththefollowingprogram:
fromgraphsimportGraph
g={"a":["d"],
"b":["c"],
"c":["b","c","d","e"],
"d":["a","c"],
"e":["c"],
"f":[]
}
graph=Graph(g)
print(graph)
fornodeingraph.vertices():
print(graph.vertex_degree(node))
print("Listofisolatedvertices:")
print(graph.find_isolated_vertices())
print("""Apathfrom"a"to"e":""")
print(graph.find_path("a","e"))
print("""Allpathesfrom"a"to"e":""")
print(graph.find_all_paths("a","e"))
print("Themaximumdegreeofthegraphis:")
print(graph.Delta())
print("Theminimumdegreeofthegraphis:")
print(graph.delta())
print("Edges:")
print(graph.edges())
print("DegreeSequence:")
ds=graph.degree_sequence()
print(ds)
fullfilling=[[2,2,2,2,1,1],
[3,3,3,3,3,3],
[3,3,2,1,1]
]
non_fullfilling=[[4,3,2,2,2,1,1],

[6,6,5,4,4,2,1],
[3,3,3,1]]
forsequenceinfullfilling+non_fullfilling:
print(sequence,Graph.erdoes_gallai(sequence))
print("Addvertex'z':")
graph.add_vertex("z")
print(graph)
print("Addedge('x','y'):")
graph.add_edge(('x','y'))
print(graph)
print("Addedge('a','d'):")
graph.add_edge(('a','d'))
print(graph)
Ifwestartthisprogram,wegetthefollowingoutput:
vertices:cefadb
edges:{'c','b'}{'c'}{'c','d'}{'c','e'}{'d','a'}
5
1
0
1
2
1
Listofisolatedvertices:
[]c
[]e
[]f
['f']a
['f']d
['f']b
['f']
Apathfrom"a"to"e":
['a','d','c','e']
Allpathesfrom"a"to"e":
[['a','d','c','e']]
Themaximumdegreeofthegraphis:
5
Theminimumdegreeofthegraphis:
0
Edges:
[{'c','b'},{'c'},{'c','d'},{'c','e'},{'d','a'}]
DegreeSequence:
(5,2,1,1,1,0)
[2,2,2,2,1,1]True
[3,3,3,3,3,3]True
[3,3,2,1,1]True
[4,3,2,2,2,1,1]False
[6,6,5,4,4,2,1]False
[3,3,3,1]False
Addvertex'z':
vertices:cefzadb
edges:{'c','b'}{'c'}{'c','d'}{'c','e'}{'d','a'}
Addedge('x','y'):
vertices:cefzaydb
edges:{'c','b'}{'c'}{'c','d'}{'c','e'}{'d','a'}{'y','x'}
Addedge('a','d'):
vertices:cefzaydb
edges:{'c','b'}{'c'}{'c','d'}{'c','e'}{'d','a'}{'y','x'}

Footnotes:
1Thegraphsstudiedingraphtheory(andinthischapterofourPythontutorial)shouldnotbeconfusedwiththe

graphsoffunctions
2Asingletonisasetthatcontainsexactlyoneelement.
Credits:
NarayanaChikkam,nchikkam(at)gmail(dot)com,pointedoutanindexerrorinthe"erdoes_gallai"method.Thankyou
verymuch,Narayana!

20112016,BerndKlein,BodenseoDesignbyDeniseMitchinsonadaptedforpythoncourse.eubyBerndKlein

G R A P H S   I N   P Y T H O N
ORIGINS OF GRAPH THEORY
Before we start with the actual implementations of
graphs in Python a
The keys of the dictionary above are the nodes of our graph. The corresponding values are lists with the nodes, which
are con
        return list(self.__graph_dict.keys())
    def edges(self):
        """ returns the edges of a graph """
        retur
    graph.add_vertex("z")
    print("Vertices of graph:")
    print(graph.vertices())
 
    print("Add an edge:")
    graph.a
    def find_path(self, start_vertex, end_vertex, path=None):
        """ find a path from start_vertex to end_vertex 
      
The method find_all_paths finds all the paths between a start vertex to an end vertex:
    def find_all_paths(self, start_ver
[['a', 'f']]
All paths from vertex "c" to vertex "c":
[['c']]
DEGREE
The degree of a vertex v in a graph is the number of edg
    def Delta(self):
        """ the maximum degree of the vertices """
        max = 0
        for vertex in self.__graph_di
            return False
        return True
Version without the superfluous degree sequence test:
    @staticmethod
    def 
complete_graph = { 
    "a" : ["b","c"],
    "b" : ["a","c"],
    "c" : ["a","b"]
}
isolated_graph = { 
    "a" : [],
    "b"

You might also like