Skip to content

Commit e099f9a

Browse files
committed
source code for chapter 14
1 parent dbf291b commit e099f9a

File tree

10 files changed

+748
-0
lines changed

10 files changed

+748
-0
lines changed

ch14/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__all__ = ['bfs', 'dfs', 'graph', 'graph_examples', 'mst', 'partition', 'shortest_paths', 'topological_sort', 'transitive_closure']

ch14/bfs.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Copyright 2013, Michael H. Goldwasser
2+
#
3+
# Developed for use with the book:
4+
#
5+
# Data Structures and Algorithms in Python
6+
# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
7+
# John Wiley & Sons, 2013
8+
#
9+
# This program is free software: you can redistribute it and/or modify
10+
# it under the terms of the GNU General Public License as published by
11+
# the Free Software Foundation, either version 3 of the License, or
12+
# (at your option) any later version.
13+
#
14+
# This program is distributed in the hope that it will be useful,
15+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
# GNU General Public License for more details.
18+
#
19+
# You should have received a copy of the GNU General Public License
20+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
22+
def BFS(g, s, discovered):
23+
"""Perform BFS of the undiscovered portion of Graph g starting at Vertex s.
24+
25+
discovered is a dictionary mapping each vertex to the edge that was used to
26+
discover it during the BFS (s should be mapped to None prior to the call).
27+
Newly discovered vertices will be added to the dictionary as a result.
28+
"""
29+
level = [s] # first level includes only s
30+
while len(level) > 0:
31+
next_level = [] # prepare to gather newly found vertices
32+
for u in level:
33+
for e in g.incident_edges(u): # for every outgoing edge from u
34+
v = e.opposite(u)
35+
if v not in discovered: # v is an unvisited vertex
36+
discovered[v] = e # e is the tree edge that discovered v
37+
next_level.append(v) # v will be further considered in next pass
38+
level = next_level # relabel 'next' level to become current
39+
40+
def BFS_complete(g):
41+
"""Perform BFS for entire graph and return forest as a dictionary.
42+
43+
Result maps each vertex v to the edge that was used to discover it.
44+
(vertices that are roots of a BFS tree are mapped to None).
45+
"""
46+
forest = {}
47+
for u in g.vertices():
48+
if u not in forest:
49+
forest[u] = None # u will be a root of a tree
50+
BFS(g, u, forest)
51+
return forest

ch14/dfs.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Copyright 2013, Michael H. Goldwasser
2+
#
3+
# Developed for use with the book:
4+
#
5+
# Data Structures and Algorithms in Python
6+
# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
7+
# John Wiley & Sons, 2013
8+
#
9+
# This program is free software: you can redistribute it and/or modify
10+
# it under the terms of the GNU General Public License as published by
11+
# the Free Software Foundation, either version 3 of the License, or
12+
# (at your option) any later version.
13+
#
14+
# This program is distributed in the hope that it will be useful,
15+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
# GNU General Public License for more details.
18+
#
19+
# You should have received a copy of the GNU General Public License
20+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
22+
def DFS(g, u, discovered):
23+
"""Perform DFS of the undiscovered portion of Graph g starting at Vertex u.
24+
25+
discovered is a dictionary mapping each vertex to the edge that was used to
26+
discover it during the DFS. (u should be "discovered" prior to the call.)
27+
Newly discovered vertices will be added to the dictionary as a result.
28+
"""
29+
for e in g.incident_edges(u): # for every outgoing edge from u
30+
v = e.opposite(u)
31+
if v not in discovered: # v is an unvisited vertex
32+
discovered[v] = e # e is the tree edge that discovered v
33+
DFS(g, v, discovered) # recursively explore from v
34+
35+
def construct_path(u, v, discovered):
36+
"""
37+
Return a list of vertices comprising the directed path from u to v,
38+
or an empty list if v is not reachable from u.
39+
40+
discovered is a dictionary resulting from a previous call to DFS started at u.
41+
"""
42+
path = [] # empty path by default
43+
if v in discovered:
44+
# we build list from v to u and then reverse it at the end
45+
path.append(v)
46+
walk = v
47+
while walk is not u:
48+
e = discovered[walk] # find edge leading to walk
49+
parent = e.opposite(walk)
50+
path.append(parent)
51+
walk = parent
52+
path.reverse() # reorient path from u to v
53+
return path
54+
55+
def DFS_complete(g):
56+
"""Perform DFS for entire graph and return forest as a dictionary.
57+
58+
Result maps each vertex v to the edge that was used to discover it.
59+
(Vertices that are roots of a DFS tree are mapped to None.)
60+
"""
61+
forest = {}
62+
for u in g.vertices():
63+
if u not in forest:
64+
forest[u] = None # u will be the root of a tree
65+
DFS(g, u, forest)
66+
return forest

ch14/graph.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# Copyright 2013, Michael H. Goldwasser
2+
#
3+
# Developed for use with the book:
4+
#
5+
# Data Structures and Algorithms in Python
6+
# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
7+
# John Wiley & Sons, 2013
8+
#
9+
# This program is free software: you can redistribute it and/or modify
10+
# it under the terms of the GNU General Public License as published by
11+
# the Free Software Foundation, either version 3 of the License, or
12+
# (at your option) any later version.
13+
#
14+
# This program is distributed in the hope that it will be useful,
15+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
# GNU General Public License for more details.
18+
#
19+
# You should have received a copy of the GNU General Public License
20+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
22+
class Graph:
23+
"""Representation of a simple graph using an adjacency map."""
24+
25+
#------------------------- nested Vertex class -------------------------
26+
class Vertex:
27+
"""Lightweight vertex structure for a graph."""
28+
__slots__ = '_element'
29+
30+
def __init__(self, x):
31+
"""Do not call constructor directly. Use Graph's insert_vertex(x)."""
32+
self._element = x
33+
34+
def element(self):
35+
"""Return element associated with this vertex."""
36+
return self._element
37+
38+
def __hash__(self): # will allow vertex to be a map/set key
39+
return hash(id(self))
40+
41+
def __str__(self):
42+
return str(self._element)
43+
44+
#------------------------- nested Edge class -------------------------
45+
class Edge:
46+
"""Lightweight edge structure for a graph."""
47+
__slots__ = '_origin', '_destination', '_element'
48+
49+
def __init__(self, u, v, x):
50+
"""Do not call constructor directly. Use Graph's insert_edge(u,v,x)."""
51+
self._origin = u
52+
self._destination = v
53+
self._element = x
54+
55+
def endpoints(self):
56+
"""Return (u,v) tuple for vertices u and v."""
57+
return (self._origin, self._destination)
58+
59+
def opposite(self, v):
60+
"""Return the vertex that is opposite v on this edge."""
61+
if not isinstance(v, Graph.Vertex):
62+
raise TypeError('v must be a Vertex')
63+
return self._destination if v is self._origin else self._origin
64+
raise ValueError('v not incident to edge')
65+
66+
def element(self):
67+
"""Return element associated with this edge."""
68+
return self._element
69+
70+
def __hash__(self): # will allow edge to be a map/set key
71+
return hash( (self._origin, self._destination) )
72+
73+
def __str__(self):
74+
return '({0},{1},{2})'.format(self._origin,self._destination,self._element)
75+
76+
#------------------------- Graph methods -------------------------
77+
def __init__(self, directed=False):
78+
"""Create an empty graph (undirected, by default).
79+
80+
Graph is directed if optional paramter is set to True.
81+
"""
82+
self._outgoing = {}
83+
# only create second map for directed graph; use alias for undirected
84+
self._incoming = {} if directed else self._outgoing
85+
86+
def _validate_vertex(self, v):
87+
"""Verify that v is a Vertex of this graph."""
88+
if not isinstance(v, self.Vertex):
89+
raise TypeError('Vertex expected')
90+
if v not in self._outgoing:
91+
raise ValueError('Vertex does not belong to this graph.')
92+
93+
def is_directed(self):
94+
"""Return True if this is a directed graph; False if undirected.
95+
96+
Property is based on the original declaration of the graph, not its contents.
97+
"""
98+
return self._incoming is not self._outgoing # directed if maps are distinct
99+
100+
def vertex_count(self):
101+
"""Return the number of vertices in the graph."""
102+
return len(self._outgoing)
103+
104+
def vertices(self):
105+
"""Return an iteration of all vertices of the graph."""
106+
return self._outgoing.keys()
107+
108+
def edge_count(self):
109+
"""Return the number of edges in the graph."""
110+
total = sum(len(self._outgoing[v]) for v in self._outgoing)
111+
# for undirected graphs, make sure not to double-count edges
112+
return total if self.is_directed() else total // 2
113+
114+
def edges(self):
115+
"""Return a set of all edges of the graph."""
116+
result = set() # avoid double-reporting edges of undirected graph
117+
for secondary_map in self._outgoing.values():
118+
result.update(secondary_map.values()) # add edges to resulting set
119+
return result
120+
121+
def get_edge(self, u, v):
122+
"""Return the edge from u to v, or None if not adjacent."""
123+
self._validate_vertex(u)
124+
self._validate_vertex(v)
125+
return self._outgoing[u].get(v) # returns None if v not adjacent
126+
127+
def degree(self, v, outgoing=True):
128+
"""Return number of (outgoing) edges incident to vertex v in the graph.
129+
130+
If graph is directed, optional parameter used to count incoming edges.
131+
"""
132+
self._validate_vertex(v)
133+
adj = self._outgoing if outgoing else self._incoming
134+
return len(adj[v])
135+
136+
def incident_edges(self, v, outgoing=True):
137+
"""Return all (outgoing) edges incident to vertex v in the graph.
138+
139+
If graph is directed, optional parameter used to request incoming edges.
140+
"""
141+
self._validate_vertex(v)
142+
adj = self._outgoing if outgoing else self._incoming
143+
for edge in adj[v].values():
144+
yield edge
145+
146+
def insert_vertex(self, x=None):
147+
"""Insert and return a new Vertex with element x."""
148+
v = self.Vertex(x)
149+
self._outgoing[v] = {}
150+
if self.is_directed():
151+
self._incoming[v] = {} # need distinct map for incoming edges
152+
return v
153+
154+
def insert_edge(self, u, v, x=None):
155+
"""Insert and return a new Edge from u to v with auxiliary element x.
156+
157+
Raise a ValueError if u and v are not vertices of the graph.
158+
Raise a ValueError if u and v are already adjacent.
159+
"""
160+
if self.get_edge(u, v) is not None: # includes error checking
161+
raise ValueError('u and v are already adjacent')
162+
e = self.Edge(u, v, x)
163+
self._outgoing[u][v] = e
164+
self._incoming[v][u] = e

0 commit comments

Comments
 (0)