0% found this document useful (0 votes)
182 views

Algorithm Design Foundations Solutions

This document contains solutions to exercises from the book "Algorithm Design" by M. T. Goodrich and R. Tamassia, published by John Wiley & Sons. It includes over 20 solutions to analysis of algorithms exercises involving asymptotic analysis using big-O, big-Omega and other asymptotic notation. The solutions analyze the time complexity of algorithms and problems involving permutations, matrix traversal, binary search trees and other algorithm design topics.

Uploaded by

Aman Garg
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
182 views

Algorithm Design Foundations Solutions

This document contains solutions to exercises from the book "Algorithm Design" by M. T. Goodrich and R. Tamassia, published by John Wiley & Sons. It includes over 20 solutions to analysis of algorithms exercises involving asymptotic analysis using big-O, big-Omega and other asymptotic notation. The solutions analyze the time complexity of algorithms and problems involving permutations, matrix traversal, binary search trees and other algorithm design topics.

Uploaded by

Aman Garg
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 111

Algorithm Design

M. T. Goodrich and R. Tamassia


John Wiley & Sons

Solution of Exercise R-1.7


The numbers in the first row are quite large. The table below calculates it approximately in powers of 10. People might also choose to use powers of 2. Being close
to the answer is enough for the big numbers (within a few factors of 10 from the
answers shown).
1 Second
6

1 Hour
9

1 Month
9

log n

210 10300000

23.610 1010

106

n log n

105

3.6 109

n2

1012

1.3 1019
109

12

1 Century
12

22.610 100.810
6.8 1024

15

9.7 1030

2.6 1012

3.12 1015

101 1

15

23.110 1010

1014

1000

n3

6 104

1.6 106

5.6 107

100

2n

1500

14000

1500000

19

31

41

51

n!

12

15

17

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-1.10


The Loop1 method runs in O(n) time.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-1.11


The Loop2 method runs in O(n) time.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-1.12


The Loop3 method runs in O(n2 ) time.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-1.13


The Loop4 method runs in O(n2 ) time.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-1.19


By the definition of big-Oh, we need to find a real constant c > 0 and an integer
constant n0 1 such that (n + 1)5 c(n5 ) for every integer n n0 . Since (n +
1)5 = n5 + 5n4 + 10n3 + 10n2 + 5n + 1, (n + 1)5 c(n5 ) for c = 8 and n n0 = 2.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-1.23


By the definition of big-Omega, we need to find a real constant c > 0 and an
integer constant n0 1 such that n3 log n cn3 for n n0 . Choosing c = 1 and
n0 = 2, shows n3 log n cn3 for n n0 , since log n > 1 in this range.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-1.7


To say that Als algorithm is big-oh of Bills algorithm implies that Als algorithm will run faster than Bills for all input greater than somenon-zero positive
integer n0 . In this case, n0 = 100.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-1.8


Since r is represented with 100 bits, any candidate p that the eavedropper might
use to try to divide r uses also at most 100 bits. Thus, this very naive algorithm
requires 2100 divisions, which would take about 280 seconds, or at least 255 years.
Even if the eavesdropper uses the fact that a candidate p need not ever be more
than 50 bits, the problem is still difficult. For in this case, 250 divisions would take
about 230 seconds, or about 34 years.
Since each division takes time O(n) and there are 24n total divisions, the
asymptotic running time is O(n 24n).

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-1.9


One possible solution is f (n) = n2 + (1 + sin(n)).

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-1.17


The induction assumes that the set of n 1 sheep without a and the set of n 1
sheep without b have sheep in common. Clearly this is not true with the case of
2 sheep. If a base case of 2 sheep could be shown, then the induction would be
valid.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-1.22


n log2 i < n log2 n = n log2 n

i=1

i=1

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-1.23


For convenience assume that n is even. Then
n

log2 i/geq

i=1

which is (n logn).

i= 2 +1

log2

n n
n
= log2 ,
2 2
2

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-1.25


Let Bn1 , Bn2 ,..., B1 , B0 be the n bottles of wine. Consider any binary string
b = bn1 bn2 ...b1b0 of length n. A string b corresponds to a test as follows. If b
has k 1s, we give a taste tester to drink a sample that consists of exactly k drops:
a drop of bottle Bi is included to the sample only if bi = 1. The idea is to have that
sufficient collection of tests b1 , b2 ,..., bT running in parallel, so that, when (after
a month) their outcome is known, we can deterministically identify the poisoned
bottle.
There are several ways for someone to construct the tests. A naive solution
uses n tests, each of them having a drop from only one distinct bottle. We can do
much better. An efficient construction of the tests will be in such a way so that a
binary search is performed in the sequence of the bottles.
For simplicity assume that n is a power of 2, i.e., n = 2k for some integer k.
We define B(t) to be the binary string of length t that consists of 2t consecutive
0s and followed by 2t 1s. Let the first test be b1 = B(n). Given the output of
this test, our search space is reduced by half: if b1 is positive then we know that
the poisoned bottle is one of B n2 1 ,...,B1 ,B0 , otherwise one of Bn1 ,Bn2 ,...,B 2n .
Let now the second test be b2 = B( n2 )||B( 2n ), where || denotes string concatenation. Clearly tests b1 and b2 reduce the search space to one forth of the initial. We proceed in the same way: b3 = B( n4 )||B( 4n )||B( n4 )||B( 4n ) and, generally,
n
n
n
n
bi = B( i+1
)||B( i+1
)||B( i+1
)||B( i+1
), for 1 i k, where k = log n. Combining
all k tests b1 , b2 , ..., bk the search space is finally reduced to only one bottle. A
similar reasoning can be followed in the case that n is not an exact power of 2; in
that case, the number of tests are n.
We give an example for n = 13. The tests could be:
test 1: b1 =11111100 0 0000
test 2: b2 =11100011 1 0000
test 3: b3 =10010010 0 1100
test 4: b4 =11011011 0 1010,

2
that is, we use 4 taste testers. Suppose that only test 2 is positive. Given the
structure of the tests, we can infer that the 5th bottle B4 is the poisoned one.
When n is not a power of 2, there are generally more that one possible test
structures. They all can determine the poisoned bottle, but the king should choose
that collection of tests that have the least number of 1s (why?)...
Why is the king not smiling? No, its not because the rumour about the queen
- everybody believes she organized this!... The king is unhappy because all the
bottles of wine (some of them were really old) had to be opened.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-1.27


Start at the upper left of the matrix. Walk across the matrix until a 0 is found.
Then walk down the matrix until a 1 is found. This is repeated until the last row
or column is encountered. The row with the most 1s is the last row which was
walked across.
Clearly this is an O(n)-time algorithm since at most 2 n comparisons are
made.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-1.28


Using the two properties of the array, the method is described as follows.
Starting from element A[n 1, 0], we scan A moving only to the right and
upwards.
If the number at A[i, j] is 1, then we add the number of 1s in that column
(i + 1) to the current total of 1s
Otherwise we move up one position until we reach another 1.
An example of the traversal of the array is shown in the figure 0.1.

array A
1
1
1
1
1
1
1
0

1
1
1
1
1
1
1
0

1
1
1
1
1
1
0
0

1
1
1
1
1
1
0
0

1
1
1
1
0
0
0
0

1
1
1
0
0
0
0
0

1
0
0
0
0
0
0
0

1
0
0
0
0
0
0
0

Figure 0.1: The traversal of the array.

The pseudo-code of the algorithm is shown below.


The running time is O(n). In the worst case, you will visit at most 2n 1
places in the array. In the case that the diagram has all 0s in rows 2 through n and
1s in the first row, then there will be n 1 iterations of the for loop at constant
time (since it will never enter the while loop) and 1 iteration of the while loop
which has n iterations of constant time.

Algorithm NumberOfOnes(A):
Input: An 2D array n n A with elements 1s and 0s as described.
Output: The total number of 1s.
N0
j0
for i n 1 to 0 do
while j n 1 A[i, j] = 1 do
N N +i+1
j j+1
return N

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-2.2


Name the two stacks as E and D, for we will enqueue into E and dequeue from
D. To implement enqueue(e), simply call E.push(e). To implement dequeue(),
simply call D.pop(), provided that D is not empty. If D is empty, iteratively pop
every element from E and push it onto D, until E is empty, and then call D.pop().
For the amortized analysis, charge $2 to each enqueue, using $1 to do the push
into E. Imagine that we store the extra cyber-dollar with the element just pushed.
We use the cyber-dollar associated with an element when we move it from E to
D. This is sufficient, since we never move elements back from D to E. Finally,
we charge $1 for each dequeue to pay for the push from D (to remove the element
returned). The total charges for n operations is O(n); hence, each operation runs
in O(1) amortized time.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-2.4


The number of permutations of n numbers is n! = n(n 1)(n 2)...3 2 1. The
idea is to compute this product only with increments of a counter. We use a stack
for that purpose. Starting with the call Enumerate(0, S), where S is a stack of n
elements. the pseudo code should be like that:
Algorithm Enumerate(t,S){
k = S.size();
while (! S.isEmpty() ) {
S.pop();
t++;
S : a new stack of size k 1
Enumerate(t,S );
}
}

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-2.5


We use the circular array approach (recall the implementation of a queue, Figure
2.4 of the textbook). For that purpose, we define the two indexes f and l to point
to the element of rank 0 and the element of rank n 1 respectively, where n is the
size of the vector (alternatively, l could point to the array position that is next to
element of rank n 1). In our algorithms, we use modular N arithmetic (N is the
size of the array A used to implement the vector).
We give the pseudo-code for the various methods:
Algorithm size():
return N + l f + 1 mod N
Algorithm isEmpty():
return (l = f 1)
Algorithm elemAtRank(r):
if r < 0 r size() then
throw InvalidRankException
return A[( f + r) mod N]

2
Algorithm replaceAtRank(r,e):
if r < 0 r size() then
throw InvalidRankException
o A[( f + r) mod N]
A[( f + r) mod N] e
return o
Algorithm insertAtRank(r,e):
s size()
if s = N 1 then
throw VectorFullException
if r < 0 r size() then
throw InvalidRankException
if r < 2s then
for i f , ( f + 1) mod N, ..., ( f + r 1) mod N do
A[(i 1) mod N] A[i]
A[( f + r 1) mod N] e
f ( f 1) mod N
else
for i l, (l 1) mod N, ..., (l s + r + 1) mod N do
A[(i + 1) mod N] A[i]
A[(l s + r + 1) mod N] e
l (l + 1) mod N
Algorithm removeAtRank(r):
s size()
if isEmpty() then
throw VectorEmptyException
if r < 0 r size() then
throw InvalidRankException
o elemAtRank(r)
if r < 2s then
for i ( f + r 1) mod N, ( f + r) mod N, ..., ( f + 1) mod N, f do
A[(i + 1) mod N] A[i]
f ( f + 1) mod N
else
for i (l s + 1) mod N, (l s) mod N, ..., (l 1) mod N, l do
A[(i 1) mod N] A[i]

3
l (l 1) mod N
return o

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-2.9


Algorithm preorderNext(Node v):
if visInternal() then
return vs left child
else
Node p = parent of v
if v is left child of p then
return right child of p
else
while v is not left child of p do
v=p
p = p.parent
return right child of p

Algorithm inorderNext(Node v):


if visInternal() then
return vs right child
else
Node p = parent of v
if v is left child of p then
return p
else
while v is not left child of p do
v=p
p = p.parent
return p
The worst case running times for these algorithms are all O(log n) where n is
the height of the tree T .

Algorithm postorderNext(Node v):


if visInternal() then
p = parent of v
if v = right child of p then
return p
else
v = right child of p
while v is not external do
v = le f tchildo f v
return v
else
p = parent of v
if v is left child of p then
return right child of p
else
return p

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-2.10


The idea is to perform a preorder (or postorder) traversal of the tree, where the
visit action is to report the depth of the node that is currently visited. This can
be easily done by using a counter that keeps track of the current depth. Method
PrintDepth(v,d) prints the depth d of the current node v and recursively traverses
the subtree defined by v. If T is the tree, then, initially, we call PrintDepth(T .root(),0).
Observe that we use only methods of the abstract tree ADT.
Algorithm PrintDepth(v,d):
print(d)
for each w T .children(v) do
PrintDepth(w,d + 1)

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-2.11


One way to do this is the following: in the external method, set height and balance
to be zero. Then, alter the right method as follows:
Algorithm right():
if visInternal(v) then
if v.le f tchild.height > v.rightchild.height then
v.height = v.le f tchild.height + 1;
else
v.height = v.rightchild.height + 1;
v.balance = absval(v.rightchild.height v.le f tchild.height);
printBalanceFactor(v)

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-2.13


Examining the Euler tree traversal, we have the following method for finding
tourNext(v,)
If v is a leaf, then:
if = left, then w is v and = below,
if = below, then w is v and = right,
if = right, then
if v is a left child, then w is vs parent and = below,
if v is a right child, then w is vs parent and = right.
If v is internal, then:
if = left, then = left and w is vs left child,
if = below, then = left and w is vs right child,
if = right, then
if v is a left child, then = below and w is vs parent,
if v is a right child, then = right and w is vs parent.
For every node v but the root, we can find whether v is a left or right child, by
asking v=T .leftChild(T .parent(v)). The complexity is always O(1).

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-2.16

2
Algorithm eulerTour(Tree T , Position v):
state start
while state 6= done do
if state = start then
if T.isExternal(v) then
left action
below action
right action
state = done
else
left action
state on the left
v v.le f tchild
if state = on the left then
if T.isExternal(v) then
left action
below action
right action
state = from the left
v v.parent
else
left action
v v.le f tchild
if state = from the left then
below action
state on the right
v v.right
if state = on the right then
if T.isExternal(v) then
state = from the right
left action
below action
right action
v v.parent
else
left action
state on the left
v v.le f t
if state = from the right then
right action
if T.isRoot(v) then
state done
else
if v is left child of parent then
state from the left
else

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-2.17


Algorithm inorder(Tree T ):
Stack S new Stack()
Node v T .root()
push v
while S is not empty do
while v is internal do
v v.le f t
push v
while S is not empty do
pop v
visit v
if v is internal then
v v.right
push v
while v is internal do
v v.le f t
push v

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-2.18


Algorithm levelOrderTraversal(BinaryTree T ):
Queue Q = new Queue()
Q.enqueue(T .root())
while Q is not empty do
Node v Q.dequeue()
if T.isInternal(v) then
Q.enqueue(v.le f tchild)
Q.enqueue(v.rightchild)

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-2.21


Algorithm LCA(Node v, Node w):
int vd pth v.depth
int wd pth w.depth
while vd pth > wd pth do
v v.parent
while wd pth > vd pth do
w w.parent
while v 6= w do
v v.parent
w w.parent
return v

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-2.22


Let duv be the diameter of T . First observe that both u and v are tree leaves; if not,
we can find a path of higher length, only by considering one of us or vs children.
Moreover, one of u, v has to be a leaf of highest depth. This can be proved by
contradiction. Consider a tree and some leaf-to-leaf path P that does not include a
leaf of highest depth. Then consider a leaf of greatest depth v; it is always possible
to find a new path P that starts at v and is longer than P. This yields the desired
contradiction.
The main idea of the algorithm is to find a leaf v of highest depth and starting
from this leaf to keep moving towards the trees root. At each visited node u
(including v, but excluding the trees root), the height of us sibling is computed
and the current length Lmax of the longest path in which v belongs is updated
accordingly. When we are at T s root, Lmax contains the diameter of T .
We give the pseudo-code for the above algorithm. We use methods depth(T ,v)
and height(T ,v), which are described in section 2.3.2, pages 80-81, of the textbook, and compute the depth and correspondingly the height of the subtree rooted
at v. Method DeepestLeaf(T , v, H, c) returns the deepest node of T or NULL if
it is not found in the subtree rooted at v. This is done by essentially performing a
pre-order traversal that stops as soon as the deepest leaf is found. H is T s height
and c is a counter.
Algorithm DeepestLeaf(T ,v,H,d):
Input : a tree T , a node v, the height H, and the current depth d
Out put : The deepest node, or NULL if it is not found
if T .isExternal(v) then
if d = H then { Base case: check for deepest node }
return v
return NULL
u NULL
{See if there is a deepest leaf in the left child}
u DeepestLeaf(T ,T .leftChild(v),H,d + 1)

2
if u 6= NULL then
return u
{See if there is a deepest leaf in the right child}
u DeepestLeaf(T ,T .rightChild(v),H,d + 1)
return u

Algorithm findDiameter(T ):
Input : a tree T
Out put : The diameter of T
H depth(T ,T .root())
v DeepestLeaf(T ,v,H,0)
if v = T .root() then
return 0 { if deeps leaf is just the root node, diameter is 0}
Lmax 0
x 1 {x: number of nodes visited}
while v 6= T .root() do
{Assign y to be the length of the path going to the sibling}
y height(T ,T .sibling(v))
{See if we have found a new longest path}
if x + 1 + y > Lmax then
Lmax x + 1 + y
{Climb up the tree one step}
x x+1
v T .parent(v)
return Lmax
The running time of the algorithm is linear. Both depth(T ,v) and height(T ,v)
have complexity O(Sv), where Sv is the size of the subtree rooted at v. depth(T ,v)
is called from the trees root once, so its time complexity is O(n). height(T ,v)
is called for each sibling on our way up to the root, so, in total, it adds an O(n)
time complexity. DeepestLeaf also has linear worst case time complexity, for it is
essentially a pre-order tree traversal. Finally, the leaf-to-root path traversal adds a
linear time complexity.
We can improve on this algorithm, however. Note that the above algorithm
can visit all the nodes in the tree twice, once to find the deepest node, and once to

3
find the height subtrees. We can combine these two operations into one algorithm
with a little bit of ingenuity and recursion. We observe that given a binary tree Tv
rooted at a node v, if we know the diameters and heights of the two subtrees, we
know the diameter of Tv . Imagine we took a path from the deepest node of the
left subtree to the deepest node of the other subtree, passing through v. This path
would have length = 2 + height(Tv, Tv .leftChild(v)) + height(Tv , Tv .rightChild(v)).
Further, note that this is a longest path that runs through the root of Tv , since the
height of the left and right subtrees is simply the longest path from their respective
roots to any of their leafs. Therefore, the longest path in Tv is simply the maximum
of longest path in the found in Tv s left and right subtrees, and the longest path
running through v. These observations give rise to the following algorithm. For
convenience, we denote the return type of this algorithm to be in the form (h, d)
where h and d represent height and diameter respectively, and are accessed using
the h() and d() methods.
Algorithm Diameter(T ,v)
Input : a tree T and a node in that tree v
Out put : a (h, d) pair
if T .isExternal(v) then {Base case}
return (0, 0)
{Get the return pairs of the left and right subtrees}
L Diameter(T , T .leftChild(v))
R Diameter(T , T .rightChild(v))
{Find the height of this subtree}
H max(L.h(),R.h()) + 1
{Find the longest path length}
P L.h() + R.h() + 2
return (H, max(L.d(), R.d(), P))

Since this algorithm visits each node in the tree exactly once, and performs a
constant amount of operations at each node, it follows that this algorithm runs in
O(n)

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-2.24

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-2.29


The path to the last node in the heap is given by the path represented by the binary
expansion of n with the highest-order bit removed.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-2.31


Construct a heap, which takes O(n) time. Then call removeMinElement k times,
which takes O(k log n) time.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-2.34


When we remove an item from a hash table that uses linear probing without using
deleted element markers, we need to rearrange the hash table contents so that
it seems the removed item never existed. This action can be done by simple
incrementally stepping forward through the table (much as we do when there
are collisions) and moving back one spot each item k for which f (k) equals
f (removeditem). We continue walking through the table until we encounter the
first item for which the f (k) value differs.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-2.2


The tree expresses the formula 6/(1 5/7).

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-2.7


We assume that a vector S is used for the representation of the tree T . Also, we
assume that S contains as elements positions. We give the pseudo-code for the
various methods:
Algorithm root():
return S.elemAtRank(1)
Algorithm parent(v):
return S.elemAtRank(p(v)/2)
Algorithm leftchild(v):
return S.elemAtRank(2p(v))
Algorithm rightchild(v):
return S.elemAtRank(2p(v) + 1)
Algorithm sibling(v):
if T .isRoot(v) then
throw InvalidNodeException
if p(v) is even then
return S.elemAtRank(p(v) + 1)
else
return S.elemAtRank(p(v) 1)
Algorithm isInternal(v):
return (2p(v) + 1 < S.size()1) (S.elemAtRank(2p(v))6= NULL)
Algorithm isExternal(v):
return T .isInternal(v)
Algorithm isRoot(v):
return (v=S.elemAtRank(1))

2
Algorithm size():
c0
for i 1, 2, ..., S.size()1 do
if S.elemAtRank(i)6= NULL then
c c+1
return c

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-2.8


221536441039132925
315364410229132925
393644102215132925
391044362215132925
391013362215442925
391013152236442925
391013152236442925
391013152225442936
391013152225294436
391013152225293644

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-2.9


221536441039132925
152236441039132925
152236441039132925
101522364439132925
310152236449132925
391015223644132925
391013152236442925
391013152229364425
391013152225293644

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-2.10


A worst-case sequence for insertion sort would be one that is in descending order
of keys, e.g., 443629252215131093. With this sequence, each element will first
be moved to the front and then moved back in the sequence incrementally, as
every remaining is processed. Thus, each element will be moved n times. For n
elements, this means at a total of n2 times, which implies (n2) time overall.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-2.11


The largest key in a heap may be stored at any external node.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-2.13


Yes, the tree T is a min-heap.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-2.14


With a preorder traversal, a heap that produces its elements in sorted order is that
which is represented by the vector (1, 2, 5, 3, 4, 6, 7). There does not exist a heap
for which an inorder traversal produces the keys in sorted order. This is because
in a heap the parent is always less than all of its children or greater than all of its
children. The heap represented by (7, 3, 6, 1, 2, 4, 5) is an example of one which
produces its keys in sorted order during a postorder traversal.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-2.20


11

39

20

16

44

88

12

23

13

94

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-2.21


20

16

11

39

44

88

12

23

13

94

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-2.22


11

23

20

16

39

44

94

12

88

13

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-3.2


There are several solutions. One is to draw the binary search tree created by the
input sequence: 9, 5, 12, 7, 13. Now draw the tree created when you switch the 5
and the 7 in the input sequence: 9, 7, 12, 5, 13.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-3.3


There are several solutions. One is to draw the AVL tree created by the input
sequence: 9, 5, 12, 7, 13. Now draw the tree created when you switch the 5 and the
7 in the input sequence: 9, 7, 12, 5, 13.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-3.5




62

"
Z 
"

Z
"

52


JJ
 

44

50

 
BB

C
C

17



C

C

54

78


@@



C

C

58


JJ


Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-3.6




78

"
Z 
"

Z
"

44




JJ




17

88


J
J
J

50



S


C

S

C

48

54

 
C

BB

C


Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-3.8


No. One property of a (2, 4) tree is that all external nodes are at the same depth.
The multi-way search tree of the example does not adhere to this property.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-3.9


The key k2 would be stored at vs parent in this case. This is done in order to
maintain the ordering of the keys within the tree. By storing k2 at vs parent, all of
the children would still be ordered such that the children to the left of the key are
less than the key and those to the right of the key are greater than the key.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-3.10


insertion order: 4, 6, 12, 15, 3, 5


34


"
5 12
"
"
" 
6



Z
Z
Z

Z

 15

insertion order: 12, 3, 6, 4, 5, 15




 6




S
S


345 
12 15


Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-3.3


Algorithm findAllElem ents(k, v, c):
Input: The search key k, a node of the binary search tree v and a container c
Output: An iterator containing the found elements
if v is an external node then
return c.elem ents()
if k = key(v) then
c.addElem ent(v)
return findAllElem ents(k, T.rightChild(v), c)
else if k < key(v) then
return findAllElem ents(k, T.leftChild(v))
else
{we know k > key(v)}
return findAllElem ents(k, T.rightChild(v))
Note that after finding k, if it occurs again, it will be in the left most internal
node of the right subtree.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-3.5





@

@

@ 


@





@

@

@ 


@




@

@

@ 


@



C

E
C

E
C



E
E
 E


 E
C

 E
C


C
E




E

E



E

E

E

E
E


Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-3.11


For each node of the tree, maintain the size of the corresponding subtree, defined
as the number of internal nodes in that subtree. While performing the search operation in both the insertion and deletion, the subtree sizes can be either incremented
or decremented. During the rebalancing, care must be taken to update the subtree
sizes of the three nodes involved (labeled a, b, and c by the restructure algorithm).
To calculate the number of nodes in a range (k1 , k2 ), search for both k1 and
k2 , and let P1 and P2 be the associated search paths. Call v the last node common
to the two paths. Traverse path P1 from v to k1 . For each internal node w 6= v
encountered, if the right child of w is in not in P1 , add one plus the size of the
subtree of the child to the current sum. Similarly, traverse path P2 from v to k2 .
For each internal node w 6= v encountered, if the left child of w is in not in P2 , add
one plus the size of the subtree of the left to the current sum. Finally, add one to
the current sum (for the key stored at node v).

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-3.14


Assume, without loss of generality, that n m.
Clearly, we can not use the general insertion/deletion tree operations to perform the joining; such a solution would take O(n log n) time.
Let T and U have heights ht and hu respectively. Our task is to manually
join the two trees into one (2-4) tree V in logarithmic time. V must have all
the properties of a (2-4) tree (that is, the size property, the depth property and
the property of V being a multi-way search tree). The idea here is very simple.
Remove either the largest element of T or the minimum element of U (we name
this element e). Without loss of generality, assume that after this removal ht hu .
If ht = hu , create a new node v that stores the element e that was initially removed
and make T s and U s root vs left and right respectively children. If ht > hu ,
insert the element e into the rightmost node of tree T at height ht hu 1 and link
this node to the root of tree U . If ht < hu , the approach is symmetric to the one
described above.
Readily, the time complexity is O(log n). Only one element is removed and
re-inserted; it can be located in time proportional to trees height and the insertion
and deletion operations take each O(log n) time. The height of a tree - if it is not
stored separately - can be computed in O(log n) time.
Below, we give pseudo-code for method join(T ,U ). We assume that trees
heights are known. M ostLeftRight(T ,T .root(),h,f lag)returns the rightmost (leftmost) node of tree T at height h, depending on if flag is set (not set).
Algorithm join(T ,U ):
{rem ove the largestelem entfrom T }
hT heightofT
MaxNodeT M ostLeftRight(T ,T .root(),hT ,true)
MaxKeyT T .Key(MaxNodeT ,Type(MaxNodeT ))
MaxElemT T .Elem (MaxNodeT ,Type(MaxNodeT ))
T.Rem ove.(MaxNodeT ,MaxKey,MaxElemT )
T .Restructure(MaxNodeT )

2
{we considerthree cases}
hT heightofT
hU heightofU
if hT = hU then {case 1}
V new tree
V .Insert(V .root(),MaxKeyT ,MaxElemT )
V .Child(V .root(),1) T .root()
V .Child(V .root(),2) U .root()
return V
if hT > hU then {case 2}
v M ostLeftRight(T ,T .root(),hT hU 1,f alse)
T .Insert(v,MaxKeyT ,MaxElemT )
T .Child(v,Type(v)+ 1) U .root()
T .Restructure(v)
return T
if hT < hU then {case 2}
v M ostLeftRight(U ,U .root(),hU hT 1,true)
U .Insert(v,MaxKeyT ,MaxElemT )
U .Child(v,1) T .root()
U .Restructure(v)
return U

Algorithm M ostLeftRight(T ,v,h,f lag):


if h = 0 then
return v
if f lag then
return M ostLeftRight(T ,T .Child(v,T .Type(v)),h 1,f lag)
else
return M ostLeftRight(T ,T .Child(v,1),h 1,f lag)

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-3.17


We give two possible solutions. Let v be a red node of a red-black tree T .
This solution assumes that keys in T are integers. We change keys stored in
T using the following simple rule: if v is red, set new key(v)= 2key(v)+1,
or if v is black, set new key(v)= 2key(v). Observe that this mapping function is 1-1 and strictly increasing; this property preserves the property of T
of being a binary search tree and helps in uniquely identify a nodes color.
Namely, any node can be identified of being red or black, by simply checking whether new key(v) is respectively odd or even.
This solution assumes that keys in T are unique. Because of the fact that v is
red, it can not be a leaf. Let u and w be vs left and right child respectively.
If both u and w are not place-holder nodes, we can represent the fact that v
is red, implicitly, by exchanging the children of v, i.e., by making u vs right
child and w vs left child. This allows us to correctly identify if a node is
red or black, only by checking the key values for nodes u, v and w. Node
v is red, if and only if the property of T of being binary search is locally
violated, i.e., if and only if key(u)>key(v)>key(w). If one of vs children is
a place holder node, then again a similar violation of T s property of being
a binary tree can be used to identify vs color. Finally, if both children of v
are place-holder nodes, then we can either store some fictitious element in
one of them or remove vs right (or left) child.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-3.26


First, observe that methods SkipSearch(k) uses only methods below(p) and after(p).
It is a top-down, scan-forward traversal technique. We examine how an insertion
and a removal of an item can be performed in a similar traversing manner.
Insertion: The main idea here is that we can do all the coin flips before we do
any skip-list traversals or insertions. The insertion algorithm consists of the
following steps:
Flip the coin several times until heads come up to compute the height
of the new tower.
Compute the height of the skip-list if necessary (unless you store this
information and update it during insertion/removal stage). We can always compute the height by going down from the topmost left element
till we hit the rock bottom.
If the number of coin tosses is more than the height of the skip-list,
add new level(s), otherwise find the topmost level in which to start
insertion (i.e., go down height-of-the-list - number-coin-tosses levels).
Starting from the position found in the previous step, do something
very similar to SkipSearch except that you also have to insert the new
element at each level before you skip down.
Removal: For the removal algorithm, we perform a SkipSearch(k)-like traversal
down the skip-list for the topmost leftmost item such that the item to the
right has key k (if such a key exists). We go on by deleting the whole tower
in a top-down fashion.
In both cases, we need to restructure the references between elements (namely
references below(p) and after(p)). We give the pseudo-code that performs these
operations. We assume topleft stores the topmost-left position of the skip-list.
Algorithm insertItem(k, e)

2
{compute the height of the tower}
ctosses 0
while (random() < 1/2) do
ctosses ctosses + 1
height 1
current tople f t
{find height of skip list}
while (below(current) 6= null) do
current below(current)
height height + 1
{ Insert new levels if necessary and find the first level to insert at}
if (height < ctosses) then
for i 1 to ctosses height do
oldtople f t tople f t
tople f t new Item(,null)
after(tople f t) new Item(,null)
below(tople f t) oldtople f t
below(after(tople f t)) after(oldtople f t)
insertat below(tople f t)
else
insertat tople f t
for i 0 to (height ctosses 1) do
insertat below(insertat)
{ Now do SkipSearch, inserting before going down }
oldnewnode null
while (insertat 6= null) do
while(key(after(insertat)) k) do
insertat after(insertat)
newnode new Item(k,e)
after(newnode) after(insertat)
if (oldnewnode 6= null) then
below(oldnewnode) newnode
after(insertat) newnode
oldnewnode newnode
insertat below(insertat)

Algorithm removeElement(k)
current tople f t
while (below(current) 6= null) do
current below(current)
while key(after(current)) < k do
current after(current)
if after(current) = k then
tmp after(current)
after(current) after(after(current))
delete tmp

Note that the insertion has O(log(n)) expected time (for the same reasons that
SkipSearch has O(log(n))) expected time.
An alternative realization of insertItem could involve the use of a stack. As
we perform SkipSearch, we push into a stack every element that we access. Once
we are at the bottom of the structure, we start tossing the coin and as long as
the flip is coming up heads we insert a copy of the inserted element. We track
any after(p) reference that needs be updated by performing a pop operation of
the stack. Any below(p) reference is handled by storing the last copy added to the
tower. Depending on when the flip is coming up tails, the stack may not become
empty or we may need add new levels. The details are left as an exercise.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-3.30


To implement an efficient ordered dictionary, we can use a hash table with separate chaining. By using this data structure, we will be able to group together all
elements with the same keythey will all be chained together. The chains will
also be sorted, so as to help with the ordered structure. In this system, all ordered
dictionary operations can be handled in O(log k + s) expected time - a quick hash
to the right spot in the table (constant time) plus a quick search through the chain
to find the correct element (which takes O(log k) time).

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-4.1


For each element in a sequence of size n there is exactly one exterior node in the
merge-sort tree associated with it. Since the merge-sort tree is binary with exactly
n exterior nodes, we know it has height log n.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-4.5


Merge sequences A and B into a new sequence C (i.e., call merge(A, B,C)). Do
a linear scan through the sequence C removing all duplicate elements (i.e., if the
next element is equal to the current element, remove it).

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-4.9


O(n log n) time.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-4.14


The bubble-sort and merge-sort algorithms are stable.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-4.16


No. Bucket-sort does not use a constant amount of additional storage. It uses
O(n + N) space.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-4.1


This can be done by extending the Merger class to be an equalsMerger class. In
the equalsMerger class, the methods firstIsLess and firstIsGreater would be null
methods. The method bothAreEqual would add a to the Sequence C. Then, after
this is done, C could be checked against A and B to see that it is the same size.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-4.4


First we sort the objects of A. Then we can walk through the sorted sequence and
remove all duplicates. This takes O(n log n) time to sort and n time to remove the
duplicates. Overall, therefore, this is an O(n logn)-time method.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-4.9


For the red and blue elements, we can order them by doing the following. Start
with a marker at the begining of the array and one at the end of the array. While the
first marker is at a blue element, continue incrementing its index. Likewise, when
the second marker is at a red element, continue decrementing its index. When the
first marker has reached a red element and the second a blue element, swap the
elements. Continue moving the markers and swapping until they meet. At this
point, the sequence is ordered. With three colors in the sequence, we can order it
by doing the above algorithm twice. In the first run, we will move one color to the
front, swapping back elements of the other two colors. Then we can start at the
end of the first run and swap the elements of the other two colors in exactly the
same way as before. Only this time the first marker will begin where it stopped at
the end of the first run.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-4.10


First sort the sequence S by the candidates ID. Then walk through the sorted
sequence, storing the current max count and the count of the current candidate ID
as you go. When you move on to a new ID, check it against the current max and
replace the max if necessary.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-4.11


In this case we can store candidate IDs in a balanced search tree, such as an AVL
tree or red-black tree, where in addition to each ID we store in this tree the number
of votes that ID has received. Initially, all such counts are 0. Then, we traverse
the sequence of votes, incrementing the count for the appropriate ID with each
vote. Since this data structure stored k elements, each such search and update
takes O(log k) time. Thus, the total time is O(n log k).

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-4.14


To sort S, do a radix sort on the bits of each of the n elements, viewing them as
pairs (i, j) such that i and j are integers in the range [0, n 1].

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-4.16


Sort the elements of S, which takes O(n log n) time. Then, step through the sequence looking for two consecutive elements that are equal, which takes an additional O(n) time. Overall, this method takes O(n log n) time.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-4.21


Any reverse-ordered sequence (in descending order) will fit this solution.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-5.8


Employ the trick used in Java, C, and C++, where we terminate an OR early if one
its terms is 1. The probability that one of the terms in each row-column product is
1/k2 ; hence, the expected time for any row-column product is constant. Therefore,
the expected running time of the entire product of A and B is O(n2 ). If k is n, on
the other hand, then the probability than an entry in a row-column product is 1 is
1/n2 ; hence, the expected running time for a row-column product is O(n) in this
case. That is, in this case, the expected time to multiply A and B is O(n3 ).

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-5.12


This is a knapsack problem, where the weight of the sack is n, and each bid i
corresponds to an item of weight ki and value di . If each bidder i is unwilling to
accept fewer than ki widgets, then this is a 0/1 problem. If bidders are willing
to accept partial lots, on the other hand, then this is a fractional version of the
knapsack problem.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-5.3


First give as many quarters as possible, then dimes, then nickles and finally pennies.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-5.4


If the demoninations are $0.25, $0.24, $0.01, then a greedy algorithm for making
change for 48 cents would give 1 quarter and 23 pennies.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-5.5


We can use a greedy algorithm, which seeks to cover all the designated points on
L with the fewest number of length-2 intervals (for such an interval is the distance
one guard can protect). This greedy algorithm starts with x0 and covers all the
points that are within distance 2 of x0 . If xi is the next uncovered point, then we
repeat this same covering step starting from xi . We then repeat this process until
we have covered all the points in X .

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-5.6


Divide the set of numbers into n/2 groups of two elements each. With n/2 comparisons we will determine a minimum and a maximum in each group. Separate
the maximums and minimums, and find the minimum of the minimums and the
maximum of the maximums using the standard algorithm. These additional scans
use n/2 comparisons each.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-6.1




@

@
.

.




.

.


.
@

@



If G has 12 vertices and 3 connected components, then the maximum number


of edges it can have is 45. This would be a fully connected component with 10
vertices (thus having 45 edges) and two connected components each with a single
vertex.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-6.2


We know that m n (n 1)/2 is O(n2 ). It follows, therefore, that O(log(n2 )) =
O(2 log n) = O(log n).

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-6.3







? 



? 



?








? 



? 



? 



A
-

6



6


 
6


 
6

C
D



 
 
6


 
6




The Euler tour is A B C D E F G H A H G


F E DC BA

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-6.5


Inserting a vertex runs in O(1) time since it is simply inserting an element into
a doubly linked list. To remove a vertex, on the other hand, we must inspect all
edges. Clearly this will take O(m) time.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-6.7


1. The adjacency list structure is preferable. Indeed, the adjacency matrix
structure wastes a lot of space. It allocates entries for 100,000,000 edges
while the graph has only 20,000 edges.
2. In general, both structures work well in this case. Regarding the space requirement, there is no clear winner. Note that the exact space usage of the
two structures depends on the implementation details. The adjacency matrix structure is much better for operation areAdjacent, while the adjacency
list structure is much better for operations insertVertex and removeVertex.
3. The adjacency matrix structure is preferable. Indeed, it supports operation
areAdjacent in O(1) time, irrespectively of the number of vertices or edges.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise R-6.10


(BOS, JFK, MIA, ORD, DFW, SFO, LAX).

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-6.12


We can compute the diameter of the tree T by slightly modifying the algorithm
we used in previous question.
1. Remove all leaves of T . Let the remaining tree be T1 .
2. Remove all leaves of T1 . Let the remaining tree be T2 .
3. Repeat the remove operation as follows: Remove all leaves of Ti . Let
remaining tree be Ti+1 .
4. When the remaining tree has only one node or two nodes, stop! Suppose
now the remaining tree is Tk .
5. If Tk has only one node, that is the center of T . The diameter of T is 2k.
6. If Tk has two nodes, either can be the center of T . The diameter of T is
2k + 1.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-6.13


For each vertex v, perform a modified BFS traversal starting a v that stops as
soon as level 4 is computed.
Alternatively, for each vertex v, call method DFS(v, 4) given below, which is
a modified DFS traversal starting at v:
Algorithm DFS(v, i):
if i > 0 and v is not marked then
Mark v.
Print v.
for all vertices w adjacent to v do
DFS(w, i 1).

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-6.17


Essentially, we are performing a DFS-like traversal of the graph. The idea is to
keep visiting nodes using edges that have never been traversed (in this direction)
in such a way so that we are able to finish the traversal exactly at the node where
we started from. To accomplish this, we keep track of the edge that we used to
reach a node for the very first time. We use this edge to leave the node when we
have traversed all its other edges.
The algorithm can be described as follows:
Start from any vertex, say s, of the graph. Traverse any incident edge
of s and visit a new node. On a node that you have just reached, if it
has not ever been visited before, label it as VISITED. Mark the entrance edge of any node that is labeled as VISITED (in fact, remember the entrance neighbor). While being on a node u, traverse any
incident edge other than the entrance one, that you have not ever
traversed from node u, to visit a neighboring node. If, while being at
a node u, you can not traverse any edge (except the entrance edge)
that you have not traversed before, return to the entry neighbor; if
u = s, there is no entry neighbor, so terminate.
First, we show that the algorithm is correct. Each edge that is not an entrance
edge is traversed twice: the first time is when a node is named as VISITED and the
second when we leave the node and never return back. Each other node is clearly
traversed twice. Note that we finish the traversal at the node that we started it.
Any node keeps a label (boolean variable) that denotes if it has ever been
visited or not. Also, a node keeps a reference to its entrance node. Finally, a
node u needs to keep track of the incident edges that it has traversed (from this
node to a neighboring node). This can be done by storing the next edge that need
be traversed, assuming that an ordering has been defined over the neighboring
nodes.
We give the pseudo-code. Each node stores three extra variables: a boolean
flag VISITED(v), a node reference ENTRANCE(v) and an iterator EDGE IT(v)

2
over incident edges. For all these extra variables we assume that there is a mechanism for setting and getting the corresponding values (in constant time). Method
NextNeighbor(G,v) returns that neighbor u of v that will be next visited (that is, the
corresponding edge has not been traversed from v to u and u is not the entrance
node of v) or NULL if no such node can be visited.
Algorithm Traverse(G):
v G.aVertex()
VISITED(v) true
ENTRANCE(v) NULL
u NextNeighbor(G,v)
DONE
e v false
while DONE do
if VISITED(u) then
VISITED(u) true
ENTRANCE(u) e
w NextNeighbor(G,u)
if w = NULL then
if u = v then DONE true
else
Report(u,ENTRANCE(u))
u ENTRANCE(u)
else
Report(u,w)
uw
Algorithm NextNeighbor(G,v):
if EDGE IT(v).hasNext() then
o EDGE IT(v).nextObject()
u G.opposite(v, o)
if u = ENTRANCE(v) then
return NextNeighbor(v)
else return u
else return NULL
The running time of the algorithm is O(n + m). Each edge is clearly traversed (reported) twice and at each node, the decision about what is the next
node to be visited, is made in constant time.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-6.19


First, even if it is not asked, we show that a connected directed graph has an Euler
tour, if and only if, each vertex has the same in-degree and out-degree (degree
property from now on).
If the graph has an Euler tour, then by following that tour, it is always able
to leave any vertex that we visit. Thus, the in-degree of each vertex equals its
out-degree.
Assume now that each vertex has the same in-degree and out-degree. we can
construct an Euler tour using the following precedure. Start at any node and keep
traversing edges until you reach the same vertex (the graph is connected and by
the degree property we are always able to return at this node). If this cycle (not
necessary simple) is a tour (contains all edges) we are done. Otherwise, delete
the traversed edges from the graph and starting by any vertex of the cycle that has
non-zero degree, find (by traversing edges) another cycle (observe that the degree
property holds even after the edges removal). Remove the traversed edges and
continue until no edges are left in the graph. All the cycles that where discovered
can be conbined to give us an Euler tour: just keep track of the starting vertex of
each cycle and insert this cycle into the previous cycle.
The last procedure corresponds to a well defined linear time algorithm for
finding an Euler tour. However, we give another algorithm that, using a DFS
traversal over the graph, constructs an Euler tour in a systematic way.
The algorithm is very similar to the one that traverses a connected undirected
graph such that each edge is traversed exactly twice (once for each direction) (see
problem 2 of homework 7a - Collaborative). there by marking the entrance edge
we had been able to succesfully (that is, so that no edges were left untouched)
leave a vertex and never visit it again.
This, however, can not be done here because an edge has only one direction.
We, instead, preprocess the graph, by performing a DFS traversal in the graph.
however, traversing edges in the opposite direction (that is, we have a DFS traversal where each edge is trvaersed in its opposite direction (backwards)). We only
label the discovering edges in this DFS. Thus, when the DFS is over, each vertex will have a unique outgoing labeled edge (the one connecting this vertex with

2
its perent in the discovering tree of DFS). We then simulate the algorithm for
homework 7a, problem 2: starting from any node, we perform a blind traversal,
where we do not cross any label edge unless we are forced (there are no other way
to leave the current vertex).
We give the pseudo-code that describe the algorithm. Instead of walking
blindly, we assume that each vertex keep an iterator OUT EDGE IT of the outgoing incident edges and in this way edges are traversed in a systematic way (and
thus, we do not have to label them). Also, each vertex stores an edge reference
LAST EDGE; it is a reference to an edge (the one labeled by the DFS traversal)
that must be traversed only if no other option is available. Finally, instead of per

forming a DFS on graph G traversing edges backwards, we can perform a DFS at

graph G , which is graph G having each edge with a reversed direction. We then

reverse all edges (to switch back to the graph G ) but keep the labeling.
Algorithm EulerTour(G):
for all vertices v do
OUT EDGE IT(v) G.outIncidentEdges(v)
LAST EDGE(v) NULL

let G be the graph resulting by reversing

the direction of each edge of G

perform DFS to graph G and for each vertex v store the


corresponding unique outgoing discovering edge as LAST EDGE(v)
s G.aVertex()
NextVertex(G,s)
Algorithm NextEdge(G,v):
if OUT EDGE IT(v).hasNext() then
e OUT EDGE IT(v).nextObject()
if e =LAST EDGE(v) then
return NextEdge(G,v)
else return e
else return LAST EDGE(v)
Algorithm NextVertex(G,v):
e NextEdge(G,v)
if e 6= NULL then
u = G.opposite(v,e)
Report(e)

3
NextVertex(G,u)
The time complexity is clearly O(n + m): the edge labeling takes linear time

(using DFS in graph G ) and each edge is traversed exactly once and the decision
about which edge to traverse next is made in constant time.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-7.3


The greedy algorithm presented in this problem is not guaranteed to find the shortest path between vertices in graph. This can be seen by counterexample. Consider
the following weighted graph:



```

``2`

``
` C



```

```
` D
3 ``


Suppose we wish to find the shortest path from start = A to goal = D and we
make use of the proposed greedy strategy. Starting at A and with path initialized
to A, the algorithm will next place B in path (because (A, B) is the minimum
cost edge incident on A) and set start equal to B. The lightest edge incident
on start = B which has not yet been traversed is (B, D). As a result, D will be
appended to path and start will be assigned D. At this point the algorithm will
terminate (since start = goal = D). In the end we have that path = A, B, D, which
clearly is not the shortest path from A to D. (Note: in fact the algorithm is not
guaranteed to find any path between two given nodes since it makes no provisions
for backing up. In an appropriate situation, it will fail to halt.)

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-7.7


We can model this problem using a graph. We associate a vertex of the graph
with each switching center and an edge of the graph with each line between two
switching centers. We assign the weight of each edge to be its bandwidth. Vertices
that represent switching centers that are not connected by a line do not have an
edge between them.
We use the same basic idea as in Dijkstras algorithm. We keep a variable
d[v] associated with each vertex v that is the bandwidth on any path from a to this
vertex. We initialize the d values of all vertices to 0, except for the value of the
source (the vertex corresponding to a) that is initialized to infinity. We also keep
a value associated with each vertex (that contains the predecessor vertex).
The basic subroutine will be very similar to the subroutine Relax in Dijkstra.
Assume that we have an edge (u, v). If min{d[u], w(u, v)} > d[v] then we should
update d[v] to min{d[u], w(u, v)} (because the path from a to u and then to v has
bandwidth min{d[u], w(u, v)}, which is more than the one we have currently).
Algorithm M ax-Bandwidth(G,a,b):
for allvertices v in V do
d[v] 0
d[a]
Known
U nknown V
while U nknown 6= do
u ExtractM ax(U nknown)
Known Known {u}
for allvertices v in Ad j[u] do
if (min(d[u], w(u, v)) > d[v])
d[v] min(d[u], w(u, v))
return d[b]
Here, the function ExtractM ax(U nknown)finds the node in U nknown with the
maximum value of d.

2
Complexity (this has not been asked for): If we maintain U nknown as a heap,
then there are n = |V | ExtractM ax operations on it and upto m = |E| changes to
the values of elements in the heap. We can implement a data structure which
takes O(log n) time for each of these operations. Hence, the total running time is
O((n + m) logn).

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-7.8


Consider the weighted graph G = (V, E), where V is the set of stations and E is
the set of channels between the stations. Define the weight w(e) of an edge e E
as the bandwidth of the corresponding channel.
Given below are two ways of solving the problem:
1. At every step of the greedy algorithm for constructing the minimum spanning tree, instead of picking the edge having the least weight, pick the edge
having the greatest weight.
2. Negate all the edge-weights. Run the usual minimum spanning tree algorithm on this graph. The algorithm gives us the desired solution.

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-7.9


We will describe two solutions for the flight scheduling problem.
1. We will reduce the flight scheduling problem to the shortest paths problem.
That means that we will construct an instance for the shortest paths problem, that when solved, gives us a solution for the flight scheduling problem.
Given the set of airports A and the set of flights F , we consider a weighted

directed graph G that is constructed as follows.


For each airport ai A, draw a circle circlei to represent the time (24
hours).
For each flight fi F , find the origin airport ao , the destination airport ad , the departure time td , and the arrival time ta . Draw a vertex v1 on circleo marked the time td and also draw a vertex v2 on
circled marked the time (ta +c(ad )). Draw an directed edge from v1
to v2 with weight ta +c(ad )-td (of course we must compute the correct
weight (flight time) in case like the departure time is 10:00PM and the
arrival time is 1:00AM next day). The direction of edges on a circle
should be clockwise.
Now we have a new graph like the Figure ?? below:
Note that we have included the minimum connecting time in the total flight
time, so we can only consider the modified new flights without worrying
about the connecting times.

Given graph G , in order to solve the flight scheduling problem, we can


just find a shortest path from the first vertex on circle(a) (origin airport a)
representing time t or after t to one vertex on circle(b) (destination airport
b) in the graph. The flights sequence can be obtained from the shortest path
from origin to destination.
We can use Dijkstras algorithm to find the desired shortest path (observe

that G has only positive weights), but we need to slightly modify the algorithm (as it is presented in the textbook), so that it is applicable to directed
graphs. The modifications should be straightforward: in the relaxation step,

2
2am f1 9pm

12am

f3

f2

10pm

4am

a1

a2
8am

6pm
10am

2pm

f6

a3

a4

12pm

12pm

f5

f4

f7

10pm

a5

9pm
6am

6pm

f8

a6
2pm

Figure 0.1: Graph used to reduce a flight scheduling problem to a shortest paths

problem.

we only consider edges with orientation from the previously selected vertex
to some other vertex. Finally, we need keep track (mark) the shortest path,
so we include a parent-child relationship to the visited nodes (value p).
Algorithm F lightS chedu ling(a, b, t):

constru ct graph G
v fi rst vertex on circle(a) representing time t or after t
D[v] 0

for each vertex u 6= v of G do


D[u]

let a priority q u eu e Q contain all the vertices of G w ith D valu es as key s


while Q is not empty do
u Q.removeM in()

for each vertex z su ch that (u, z) E


z Q do
G

if D[u] + w((u, z)) < D[z] then

D[z] D[u] + w((u, z))


change to D[z] the key of vertex z in Q
p[z] u
w vertex on circle(b) w ith minimu m valu e D
return reversed path from w to v (u sing valu es p)

The time complexity of the algorithm essentially the time complexity of


Dijkstras algorithm (since the construction of the graph can be done in
linear (O(n + m)) time). Using a heap as the priority queue, we achieve a
total O((N + M) log N) time complexity, where N the number of vertices of

G and M the number of edges of G . Note that, generally, M = O(m) and


N = O(m), so the running time of the algorithm is O(m log m).
2. The following algorithm finds the minimum travel time path from airport
a A to airport b A. Recall, we must depart from a at or after time
t. Note, and  are operations which we must implement. We define
these operations as follows: If xy = z, then z is the point in time (with date
taken into consideration) which follows x by y time units. Also, if a  b,
then a is a point in time which preceeds or equals b. These operations can
be implemented in constant time.
Algorithm F lightS chedu ling(a, b, t):
initialize a set P to a
initialize incoming fl ight(x) to nil for each x A
earliest arrival time(a) t
for all x A su ch that x 6= a do
earliest arrival time(x)
time can depart
repeat
remove from P airport x su ch that earliest arrival time(x) is soonest
if x 6= b then
if x = a then
time can depart t
else
time can depart earliest arrival time(x) c(x)
for each fl ight f F su ch that a1 ( f ) = x do
if time can depart  t1 ( f )
t2 ( f )  earliest arrival time(a2( f )) then
earliest arrival time(a2 ( f )) t2 ( f )
incoming fl ight(a2 ( f )) f
add a2 ( f ) to P if it is not y et there
until x = b
initialize city to b

4
initialize f light sequence to nil
while (city 6= a) do
append incoming f light(city) to f light sequence
assign to city the departu re city of incoming f light(city)
reverse f light sequence and ou tpu t
The algorithm essentially performs Dijkstras Shortest Path Algorithm (the
cities are the vertices and the flights are the edges). The only small alterations are that we restrict edge traversals (an edge can only be traversed at a
certain time), we stop at a certain goal, and we output the path (a sequence
of flights) to this goal. The only change of possible consequence to the
time complexity is the flight sequence computation (the last portion of the
algorithm). A minimum time path will certainly never contain more that m
flights (since there are only m total flights). Thus, we may discover the path
(tracing from b back to a) in O(m) time. Reversal of this path will require
O(m) time as well. Thus, since the time complexity of Dijkstras algorithm
is O(m log n), the time complexity of our algorithm is O(m log n).
Note: Prior to execution of this algorithm, we may (if not given to us) divide
F into subsets F1 , F2 , . . . , FN , where Fi contains the flights which depart from
airport i. Then when we say for each flight f F such that a1 ( f ) = x,
we will not have to waste time looking through flights which do not depart
from x (we will actually compute for each flight f Fx ). This division
of F will require O(m) time (since there are m total flights), and thus will
not slow down the computation of the minimum time path (which requires
O(m log n) time).

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-7.10


Simply run the shortest path algorithm with path(a) < path(b) if there is more
gold on path(a) than path(b).

Algorithm Design
M. T. Goodrich and R. Tamassia
John Wiley & Sons

Solution of Exercise C-7.11


1. If M 2 (i, j) = 1, then there is a path of length 2 (a path traversing exactly 2
edges) from vertex i to vertex j in the graph G. Alternatively, if M 2 (i, j) = 0,
then there is no such path.
2. Similarly, if M 4 (i, j) = 1, then there exists a path of length 4 from vi to v j ,
otherwise no such path exists. The situation with M 5 is analogous to that
of M 4 and M 2 . In general, M p gives us all the vertex pairs of G which are
connected by paths of length p.
3. if M 2 (i, j) = k, then we can conclude that the shortest path connecting vertices i and j of length 2 has weight k. That is, if k = , then i and j are
not connected with a path of length at most 2, and, if k 6= , then they are
connected with a path of length at most 2 that has weight k and is a shortest
path of length 2.

You might also like