Skip to content

Commit ef33598

Browse files
committed
souce code for chapter 8
1 parent acabb51 commit ef33598

File tree

7 files changed

+767
-0
lines changed

7 files changed

+767
-0
lines changed

ch08/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__all__ = ['euler_tour', 'expression_tree', 'linked_binary_tree', 'traversal_examples']

ch08/binary_tree.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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+
from .tree import Tree
23+
24+
class BinaryTree(Tree):
25+
"""Abstract base class representing a binary tree structure."""
26+
27+
# --------------------- additional abstract methods ---------------------
28+
def left(self, p):
29+
"""Return a Position representing p's left child.
30+
31+
Return None if p does not have a left child.
32+
"""
33+
raise NotImplementedError('must be implemented by subclass')
34+
35+
def right(self, p):
36+
"""Return a Position representing p's right child.
37+
38+
Return None if p does not have a right child.
39+
"""
40+
raise NotImplementedError('must be implemented by subclass')
41+
42+
# ---------- concrete methods implemented in this class ----------
43+
def sibling(self, p):
44+
"""Return a Position representing p's sibling (or None if no sibling)."""
45+
parent = self.parent(p)
46+
if parent is None: # p must be the root
47+
return None # root has no sibling
48+
else:
49+
if p == self.left(parent):
50+
return self.right(parent) # possibly None
51+
else:
52+
return self.left(parent) # possibly None
53+
54+
def children(self, p):
55+
"""Generate an iteration of Positions representing p's children."""
56+
if self.left(p) is not None:
57+
yield self.left(p)
58+
if self.right(p) is not None:
59+
yield self.right(p)
60+
61+
def inorder(self):
62+
"""Generate an inorder iteration of positions in the tree."""
63+
if not self.is_empty():
64+
for p in self._subtree_inorder(self.root()):
65+
yield p
66+
67+
def _subtree_inorder(self, p):
68+
"""Generate an inorder iteration of positions in subtree rooted at p."""
69+
if self.left(p) is not None: # if left child exists, traverse its subtree
70+
for other in self._subtree_inorder(self.left(p)):
71+
yield other
72+
yield p # visit p between its subtrees
73+
if self.right(p) is not None: # if right child exists, traverse its subtree
74+
for other in self._subtree_inorder(self.right(p)):
75+
yield other
76+
77+
# override inherited version to make inorder the default
78+
def positions(self):
79+
"""Generate an iteration of the tree's positions."""
80+
return self.inorder() # make inorder the default

ch08/euler_tour.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
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 EulerTour:
23+
"""Abstract base class for performing Euler tour of a tree.
24+
25+
_hook_previsit and _hook_postvisit may be overridden by subclasses.
26+
"""
27+
def __init__(self, tree):
28+
"""Prepare an Euler tour template for given tree."""
29+
self._tree = tree
30+
31+
def tree(self):
32+
"""Return reference to the tree being traversed."""
33+
return self._tree
34+
35+
def execute(self):
36+
"""Perform the tour and return any result from post visit of root."""
37+
if len(self._tree) > 0:
38+
return self._tour(self._tree.root(), 0, []) # start the recursion
39+
40+
def _tour(self, p, d, path):
41+
"""Perform tour of subtree rooted at Position p.
42+
43+
p Position of current node being visited
44+
d depth of p in the tree
45+
path list of indices of children on path from root to p
46+
"""
47+
self._hook_previsit(p, d, path) # "pre visit" p
48+
results = []
49+
path.append(0) # add new index to end of path before recursion
50+
for c in self._tree.children(p):
51+
results.append(self._tour(c, d+1, path)) # recur on child's subtree
52+
path[-1] += 1 # increment index
53+
path.pop() # remove extraneous index from end of path
54+
answer = self._hook_postvisit(p, d, path, results) # "post visit" p
55+
return answer
56+
57+
def _hook_previsit(self, p, d, path):
58+
"""Visit Position p, before the tour of its children.
59+
60+
p Position of current position being visited
61+
d depth of p in the tree
62+
path list of indices of children on path from root to p
63+
"""
64+
pass
65+
66+
def _hook_postvisit(self, p, d, path, results):
67+
"""Visit Position p, after the tour of its children.
68+
69+
p Position of current position being visited
70+
d depth of p in the tree
71+
path list of indices of children on path from root to p
72+
results is a list of values returned by _hook_postvisit(c)
73+
for each child c of p.
74+
"""
75+
pass
76+
77+
class PreorderPrintIndentedTour(EulerTour):
78+
def _hook_previsit(self, p, d, path):
79+
print(2*d*' ' + str(p.element()))
80+
81+
class PreorderPrintIndentedLabeledTour(EulerTour):
82+
def _hook_previsit(self, p, d, path):
83+
label = '.'.join(str(j+1) for j in path) # labels are one-indexed
84+
print(2*d*' ' + label, p.element())
85+
86+
class ParenthesizeTour(EulerTour):
87+
def _hook_previsit(self, p, d, path):
88+
if path and path[-1] > 0: # p follows a sibling
89+
print(', ', end='') # so preface with comma
90+
print(p.element(), end='') # then print element
91+
if not self.tree().is_leaf(p): # if p has children
92+
print(' (', end='') # print opening parenthesis
93+
94+
def _hook_postvisit(self, p, d, path, results):
95+
if not self.tree().is_leaf(p): # if p has children
96+
print(')', end='') # print closing parenthesis
97+
98+
class DiskSpaceTour(EulerTour):
99+
def _hook_postvisit(self, p, d, path, results):
100+
# we simply add space associated with p to that of its subtrees
101+
return p.element().space() + sum(results)
102+
103+
class BinaryEulerTour(EulerTour):
104+
"""Abstract base class for performing Euler tour of a binary tree.
105+
106+
This version includes an additional _hook_invisit that is called after the tour
107+
of the left subtree (if any), yet before the tour of the right subtree (if any).
108+
109+
Note: Right child is always assigned index 1 in path, even if no left sibling.
110+
"""
111+
def _tour(self, p, d, path):
112+
results = [None, None] # will update with results of recursions
113+
self._hook_previsit(p, d, path) # "pre visit" for p
114+
if self._tree.left(p) is not None: # consider left child
115+
path.append(0)
116+
results[0] = self._tour(self._tree.left(p), d+1, path)
117+
path.pop()
118+
self._hook_invisit(p, d, path) # "in visit" for p
119+
if self._tree.right(p) is not None: # consider right child
120+
path.append(1)
121+
results[1] = self._tour(self._tree.right(p), d+1, path)
122+
path.pop()
123+
answer = self._hook_postvisit(p, d, path, results) # "post visit" p
124+
return answer
125+
126+
def _hook_invisit(self, p, d, path):
127+
"""Visit Position p, between tour of its left and right subtrees."""
128+
pass
129+
130+
class BinaryLayout(BinaryEulerTour):
131+
"""Class for computing (x,y) coordinates for each node of a binary tree."""
132+
def __init__(self, tree):
133+
super().__init__(tree) # must call the parent constructor
134+
self._count = 0 # initialize count of processed nodes
135+
136+
def _hook_invisit(self, p, d, path):
137+
p.element().setX(self._count) # x-coordinate serialized by count
138+
p.element().setY(d) # y-coordinate is depth
139+
self._count += 1 # advance count of processed nodes

ch08/expression_tree.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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+
from .linked_binary_tree import LinkedBinaryTree
23+
24+
class ExpressionTree(LinkedBinaryTree):
25+
"""An arithmetic expression tree."""
26+
27+
def __init__(self, token, left=None, right=None):
28+
"""Create an expression tree.
29+
30+
In a single parameter form, token should be a leaf value (e.g., '42'),
31+
and the expression tree will have that value at an isolated node.
32+
33+
In a three-parameter version, token should be an operator,
34+
and left and right should be existing ExpressionTree instances
35+
that become the operands for the binary operator.
36+
"""
37+
super().__init__() # LinkedBinaryTree initialization
38+
if not isinstance(token, str):
39+
raise TypeError('Token must be a string')
40+
self._add_root(token) # use inherited, nonpublic method
41+
if left is not None: # presumably three-parameter form
42+
if token not in '+-*x/':
43+
raise ValueError('token must be valid operator')
44+
self._attach(self.root(), left, right) # use inherited, nonpublic method
45+
46+
def __str__(self):
47+
"""Return string representation of the expression."""
48+
pieces = [] # sequence of piecewise strings to compose
49+
self._parenthesize_recur(self.root(), pieces)
50+
return ''.join(pieces)
51+
52+
def _parenthesize_recur(self, p, result):
53+
"""Append piecewise representation of p's subtree to resulting list."""
54+
if self.is_leaf(p):
55+
result.append(str(p.element())) # leaf value as a string
56+
else:
57+
result.append('(') # opening parenthesis
58+
self._parenthesize_recur(self.left(p), result) # left subtree
59+
result.append(p.element()) # operator
60+
self._parenthesize_recur(self.right(p), result) # right subtree
61+
result.append(')') # closing parenthesis
62+
63+
def evaluate(self):
64+
"""Return the numeric result of the expression."""
65+
return self._evaluate_recur(self.root())
66+
67+
def _evaluate_recur(self, p):
68+
"""Return the numeric result of subtree rooted at p."""
69+
if self.is_leaf(p):
70+
return float(p.element()) # we assume element is numeric
71+
else:
72+
op = p.element()
73+
left_val = self._evaluate_recur(self.left(p))
74+
right_val = self._evaluate_recur(self.right(p))
75+
if op == '+':
76+
return left_val + right_val
77+
elif op == '-':
78+
return left_val - right_val
79+
elif op == '/':
80+
return left_val / right_val
81+
else: # treat 'x' or '*' as multiplication
82+
return left_val * right_val
83+
84+
85+
def tokenize(raw):
86+
"""Produces list of tokens indicated by a raw expression string.
87+
88+
For example the string '(43-(3*10))' results in the list
89+
['(', '43', '-', '(', '3', '*', '10', ')', ')']
90+
"""
91+
SYMBOLS = set('+-x*/() ') # allow for '*' or 'x' for multiplication
92+
93+
mark = 0
94+
tokens = []
95+
n = len(raw)
96+
for j in range(n):
97+
if raw[j] in SYMBOLS:
98+
if mark != j:
99+
tokens.append(raw[mark:j]) # complete preceding token
100+
if raw[j] != ' ':
101+
tokens.append(raw[j]) # include this token
102+
mark = j+1 # update mark to being at next index
103+
if mark != n:
104+
tokens.append(raw[mark:n]) # complete preceding token
105+
return tokens
106+
107+
def build_expression_tree(tokens):
108+
"""Returns an ExpressionTree based upon by a tokenized expression.
109+
110+
tokens must be an iterable of strings representing a fully parenthesized
111+
binary expression, such as ['(', '43', '-', '(', '3', '*', '10', ')', ')']
112+
"""
113+
S = [] # we use Python list as stack
114+
for t in tokens:
115+
if t in '+-x*/': # t is an operator symbol
116+
S.append(t) # push the operator symbol
117+
elif t not in '()': # consider t to be a literal
118+
S.append(ExpressionTree(t)) # push trivial tree storing value
119+
elif t == ')': # compose a new tree from three constituent parts
120+
right = S.pop() # right subtree as per LIFO
121+
op = S.pop() # operator symbol
122+
left = S.pop() # left subtree
123+
S.append(ExpressionTree(op, left, right)) # repush tree
124+
# we ignore a left parenthesis
125+
return S.pop()
126+
127+
if __name__ == '__main__':
128+
big = build_expression_tree(tokenize('((((3+1)x3)/((9-5)+2))-((3x(7-4))+6))'))
129+
print(big, '=', big.evaluate())

0 commit comments

Comments
 (0)