D A -- C B -- H
| \ | \ |
| \ | \ |
E -- F G
nodes A B C D E F G H
degrees 3 2 2 0 2 3 2 2
edges 8
sum of degrees 16
G = (V,E)
graph, vertex/node/point, edge/line
adjacent
subgraph
connected
component: maximal connected subgraph
degree of a vertex
n or |V| number of nodes
m or |E| number of edges
adjacency matrix
A B C D E F G H
A 0 0 1 0 1 1 0 0
B 0 0 0 0 0 0 1 1
C 1 0 0 0 0 1 0 0
D 0 0 0 0 0 0 0 0
E 1 0 0 0 0 1 0 0
F 1 0 1 0 1 0 0 0
G 0 1 0 0 0 0 0 1
H 0 1 0 0 0 0 1 0
adjacency list
A [C E F]
B [H G]
C [A F]
D [ ]
E [A F]
F [A C E]
G [B H]
H [B G]
adjacency matrix uses n2 bits
adjacency list uses n + 2m node labels
worst case, m ∈ Θ( n2 )
if n large and average degree small, lists smaller than matrix
def dfs(G):
seen = {}
for v in G: seen[v] = False
for v in G:
if not seen[v]:
explore(G,v,seen)
def explore(G,v,seen):
print v, 'start explore'
seen[v] = True
for nbr in G[v]:
if not seen[nbr]:
explore(G,nbr,seen)
print v, 'finish explore'
def show(G):
for v in sorted(G):
print v,':',
for j in G[v]: print j,
print ''
print ''
G = {'A': ['C', 'E', 'F'],
'B': ['G', 'H'],
'C': ['A', 'F'],
'D': [],
'E': ['A', 'F'],
'F': ['A', 'C', 'E'],
'G': ['B', 'H'],
'H': ['B', 'G']}
show(G)
dfs(G)
A start explore C start explore F start explore E start explore E finish explore F finish explore C finish explore A finish explore B start explore G start explore H start explore H finish explore G finish explore B finish explore D start explore D finish explore
preorder: by dfs start (1st encounter)
A C F E B G H D
postorder: by dfs finish (last encounter)
E F C A H G B D
def dfs(G):
pre,post,seen,cmpt = {},{},{},{}
for v in G:
seen[v] = False
clock, cnum = [0], 0 #clock[0] passed by ref
for v in G:
if not seen[v]:
cnum += 1
explore(G,v,seen,pre,post,clock,cmpt,cnum)
showall(G,pre,post,cmpt)
def explore(G,v,seen,pre,post,clock,cmpt,c):
print 'explore from', v
seen[v] = True
cmpt[v] = c
timestamp(v, pre, clock)
for nbr in G[v]:
if not seen[nbr]:
explore(G,nbr,seen,pre,post,clock,cmpt,c)
timestamp(v, post, clock)
def timestamp(v, order, clock):
clock[0]+= 1; order[v] = clock[0]
def showall(G,pre,post,cnum):
print ' ',
for v in sorted(G): print '%2s' % v,
print '\npre ',
for v in sorted(G): print '%2s' % pre[v],
print '\npost ',
for v in sorted(G): print '%2s' % post[v],
print '\ncmpt ',
for v in sorted(G): print '%2s' % cnum[v],
def show(G):
for v in sorted(G):
print v,':',
for j in G[v]: print j,
print ''
print ''
G = {'A': ['C', 'E', 'F'],
'B': ['G', 'H'],
'C': ['A', 'F'],
'D': [],
'E': ['A', 'F'],
'F': ['A', 'C', 'E'],
'G': ['B', 'H'],
'H': ['B', 'G'] }
show(G)
dfs(G)
adjacency matrix: 
adjacency list: 
why? see text, then guess output from examples below
def traverse(G):
seen,cmpt,cnum,work = {},{},0,[0] #work reflects runtime
for v in G:
seen[v] = False
cmpt[v] = 0
for v in sorted(G):
work[0] += 1
if not seen[v]:
cnum += 1
dfs(G,v,seen,cmpt,cnum,work)
print ''
for v in sorted(G): print v,
print ''
for v in sorted(G): print cmpt[v],
print '\nwork', work[0]
def dfs(G,v,seen,cmpt,c,work):
seen[v],cmpt[v],work[0] = True,c,work[0]+1
for nbr in sorted(G[v]):
work[0] += 1
if not seen[nbr]:
dfs(G,nbr,seen,cmpt,c,work)
print v,
def show(G):
for v in sorted(G):
print v,':',
for j in G[v]: print j,
print ''
print ''
G = {'A': ['F'],
'B': ['D','C'],
'C': ['G','B','E','D'],
'D': ['G','B','C'],
'E': ['C','G'],
'F': ['A'],
'G': ['C', 'D']}
show(G)
traverse(G)
def n(G): return len(G[0])
def alpha(j): return chr(j+ord('A'))
def traverse(G):
seen,cmpt,cnum,work = {},{},0,[0]
for v in range(n(G)):
seen[v] = False
cmpt[v] = 0
for v in range(n(G)):
work[0] += 1
if not seen[v]:
cnum += 1
dfs(G,v,seen,cmpt,cnum,work)
print ''
for v in range(n(G)): print alpha(v),
print ''
for v in range(n(G)): print cmpt[v],
print '\nwork', work[0]
def dfs(G,v,seen,cmpt,c,work):
seen[v],cmpt[v],work[0] = True,c,work[0]+1
for j in range(n(G)): # possible nbr
work[0] += 1
if G[v][j]==1: # j is nbr
if not seen[j]:
dfs(G,j,seen,cmpt,c,work)
print alpha(v),
def show(G):
print ' ',
for v in range(n(G)): print alpha(v),
print ''
for v in range(n(G)):
print alpha(v),':',
for w in range(n(G)):
if G[v][w]==1: print '1',
else: print '0',
print ''
print ''
G = ([0, 0, 0, 0, 0, 1, 0],
[0, 0, 1, 1, 0, 0, 0],
[0, 1, 0, 1, 1, 0, 1],
[0, 1, 1, 0, 0, 0, 1],
[0, 0, 1, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 0, 0, 0])
show(G)
traverse(G)
B - L - G M A
\ / \ / \ /
I - E H - K
\ / \
C - J - F - D
def bfs(G): # depth in bfs forest
seen, depth = {}, {}
for v in G:
seen[v], depth[v] = False, 0
print 'bfs order ',
for v in sorted(G):
if not seen[v]:
explorebfs(G,v,seen,depth)
print '\nnodes ',
for v in sorted(G): print v,
print '\nbfs forest depth',
for v in sorted(G): print depth[v],
print ''
def addtolist(L,v,seen):
L.append(v); seen[v]=True
def explorebfs(G,v,seen,d):
Q = []
addtolist(Q,v,seen)
while len(Q)>0:
v = Q.pop(0); print v,
for nbr in G[v]:
if not seen[nbr]:
d[nbr] = 1 + d[v]
addtolist(Q,nbr,seen)
def show(G):
for v in sorted(G):
print v,':',
for j in G[v]: print j,
print ''
G = { 'A':['H'],
'B':['I','L'],
'C':['E','I','J'],
'D':['F'],
'E':['C','G','I','J','L'],
'F':['D','J'],
'G':['E','L'],
'H':['A','K','M'],
'I':['B','C','E','L'],
'J':['C','E','F'],
'K':['H'],
'L':['B','E','G','I'],
'M':['H']}
bfs(G)
D = { 'A':['B'],
'B':['C','E','F'],
'C':['D','G'],
'D':['C','H'],
'E':['A','F'],
'F':['G'],
'G':['F'],
'H':['D','G']
}
arc
strongly connected: if, for each ordered pair of vertices (a,b), there is a path from a to b
strongly connected component:
vertex subset
the subset is strongly connected
the subset is maximal (wrt being strongly connected)
transpose of a digraph: reverse direction of every arc
in a digraph
source node/scc: has no in-arcs
sink node/scc: has no out-arcs
to find sccs of digraph D:
find node s in sink scc S
find and remove S
repeat until done
how to find s ?
last in postorder of transpose T
how to find S?
start from s, use dfs/bfs on D
runtime
adjacency list
can find transpose in Θ(n+m) time
total Θ(n+m) time
adjacency matrix
can find transpose in time Θ(n2)
total time Θ(n2)
correctness
in acyclic digraph D postorder
each v appears after all w reachable from v, so
last vertex is D source, so
in transpose T postorder
last vertex is T source, so
last vertex is D sink
in postorder of transpose T of arbitrary digraph D,
last vertex is in D sink scc
def dfs(G,v,seen,cmpt,c,S,phase):
seen[v],cmpt[v] = True,c
for nbr in sorted(G[v]):
if not seen[nbr]: dfs(G,nbr,seen,cmpt,c,S,phase)
if (phase==0): S.append(v) # append in postorder
def transpose(G):
T = {}
for v in G: T[v] = []
for v in G:
for w in G[v]: T[w].append(v)
return T
def scc(G):
phase,seen,cmpt,c,S = 0,{},{},0,[]
for v in G: seen[v],cmpt[v] = False, 0
T = transpose(G)
for v in sorted(T):
if not seen[v]: dfs(T,v,seen,cmpt,c,S,phase)
showall(T,cmpt,S)
phase,c = 1,0
for v in G: seen[v],cmpt[v] = False, 0
while len(S)>0:
v = S.pop() # pop from end, so reverse order
if not seen[v]:
c += 1
dfs(G,v,seen,cmpt,c,S,phase)
showall(G,cmpt,S)
def showall(G,cmpt,S):
print ' ',
for v in sorted(G): print '%2s' % v,
print '\ncmpt ',
for v in sorted(G): print '%2s' % cmpt[v],
print '\nstack', S
D = { 'A':['B'],
'B':['C','E','F'],
'C':['D','G'],
'D':['C','H'],
'E':['A','F'],
'F':['G'],
'G':['F'],
'H':['D','G']
}
G = {'A':['F'],
'B':['H'],
'C':['B','F'],
'D':['I'],
'E':['B'],
'F':['C','I'],
'G':['E'],
'H':['G'],
'I':['A','G']}
scc(D)
a digraph D

its transpose T

D dfs traversal forest:
A C J
D G L
F K
H
B I
E
D-dfs postorder
D E B I H F G A K L C J
notice:
postorder-first node in sink scc (why?)
postorder-last node in source scc (why?)
T dfs traversal forest:
A B C D J
F E K
G I L
H
T-dfs postorder
G F A H I E B L K C D J
G dfs traversal forest, using reverse T postorder:
J D C B A
K E F
L I G
H
notice:
each tree in above forest is scc of T/D
how do the results of scc(D) compare with scc(transpose(D)) ?
try an example or two
explain