Data Structures and Algorithms in Java 6th Edition 151 200
Data Structures and Algorithms in Java 6th Edition 151 200
Although we could implement a doubly linked list without sentinel nodes (as we
did with our singly linked list in Section 3.2), the slight extra memory devoted to the
sentinels greatly simplifies the logic of our operations. Most notably, the header and
trailer nodes never change—only the nodes between them change. Furthermore,
we can treat all insertions in a unified manner, because a new node will always be
placed between a pair of existing nodes. In similar fashion, every element that is to
be deleted is guaranteed to be stored in a node that has neighbors on each side.
For contrast, we look at our SinglyLinkedList implementation from Section 3.2.
Its addLast method required a conditional (lines 39–42 of Code Fragment 3.15) to
manage the special case of inserting into an empty list. In the general case, the new
node was linked after the existing tail. But when adding to an empty list, there is
no existing tail; instead it is necessary to reassign head to reference the new node.
The use of a sentinel node in that implementation would eliminate the special case,
as there would always be an existing node (possibly the header) before a new node.
Every insertion into our doubly linked list representation will take place between
a pair of existing nodes, as diagrammed in Figure 3.20. For example, when a new
element is inserted at the front of the sequence, we will simply add the new node
between the header and the node that is currently after the header. (See Figure 3.21.)
header trailer
BWI JFK SFO
(a)
header trailer
BWI JFK PVD SFO
(b)
header trailer
BWI JFK PVD SFO
(c)
Figure 3.20: Adding an element to a doubly linked list with header and trailer sen-
tinels: (a) before the operation; (b) after creating the new node; (c) after linking the
neighbors to the new node.
www.it-ebooks.info
134 Chapter 3. Fundamental Data Structures
header trailer
BWI JFK SFO
(a)
header trailer
PVD BWI JFK SFO
(b)
header trailer
PVD BWI JFK SFO
(c)
Figure 3.21: Adding an element to the front of a sequence represented by a dou-
bly linked list with header and trailer sentinels: (a) before the operation; (b) after
creating the new node; (c) after linking the neighbors to the new node.
The deletion of a node, portrayed in Figure 3.22, proceeds in the opposite fash-
ion of an insertion. The two neighbors of the node to be deleted are linked directly
to each other, thereby bypassing the original node. As a result, that node will no
longer be considered part of the list and it can be reclaimed by the system. Because
of our use of sentinels, the same implementation can be used when deleting the first
or the last element of a sequence, because even such an element will be stored at a
node that lies between two others.
header trailer
BWI JFK PVD SFO
(a)
header trailer
BWI JFK PVD SFO
(b)
header trailer
BWI JFK SFO
(c)
Figure 3.22: Removing the element PVD from a doubly linked list: (a) before
the removal; (b) after linking out the old node; (c) after the removal (and garbage
collection).
www.it-ebooks.info
3.4. Doubly Linked Lists 135
www.it-ebooks.info
136 Chapter 3. Fundamental Data Structures
www.it-ebooks.info
3.4. Doubly Linked Lists 137
www.it-ebooks.info
138 Chapter 3. Fundamental Data Structures
www.it-ebooks.info
3.5. Equivalence Testing 139
For most applications, the Arrays.equals behavior captures the appropriate no-
tion of equivalence. However, there is an additional complication when using
multidimensional arrays. The fact that two-dimensional arrays in Java are really
one-dimensional arrays nested inside a common one-dimensional array raises an
interesting issue with respect to how we think about compound objects, which are
objects—like a two-dimensional array—that are made up of other objects. In par-
ticular, it brings up the question of where a compound object begins and ends.
Thus, if we have a two-dimensional array, a, and another two-dimensional ar-
ray, b, that has the same entries as a, we probably want to think that a is equal
to b. But the one-dimensional arrays that make up the rows of a and b (such as
a[0] and b[0]) are stored in different memory locations, even though they have the
same internal content. Therefore, a call to the method java.util.Arrays.equals(a,b)
will return false in this case, because it tests a[k].equals(b[k]), which invokes the
Object class’s definition of equals.
To support the more natural notion of multidimensional arrays being equal if
they have equal contents, the class provides an additional method:
www.it-ebooks.info
140 Chapter 3. Fundamental Data Structures
www.it-ebooks.info
3.6. Cloning Data Structures 141
www.it-ebooks.info
142 Chapter 3. Fundamental Data Structures
data
2 3 5 7 11 13 17 19
backup 0 1 2 3 4 5 6 7
Figure 3.23: The result of the command backup = data for int arrays.
Instead, if we want to make a copy of the array, data, and assign a reference to
the new array to variable, backup, we should write:
backup = data.clone( );
The clone method, when executed on an array, initializes each cell of the new array
to the value that is stored in the corresponding cell of the original array. This results
in an independent array, as shown in Figure 3.24.
data 2 3 5 7 11 13 17 19
0 1 2 3 4 5 6 7
backup 2 3 5 7 11 13 17 19
0 1 2 3 4 5 6 7
Figure 3.24: The result of the command backup = data.clone( ) for int arrays.
www.it-ebooks.info
3.6. Cloning Data Structures 143
For example, if the variable contacts refers to an array of hypothetical Person
instances, the result of the command guests = contacts.clone( ) produces a shal-
low copy, as portrayed in Figure 3.25.
0 1 2 3 4 5 6 7
contacts
guests
0 1 2 3 4 5 6 7
Figure 3.25: A shallow copy of an array of objects, resulting from the command
guests = contacts.clone( ).
A deep copy of the contact list can be created by iteratively cloning the indi-
vidual elements, as follows, but only if the Person class is declared as Cloneable.
Person[ ] guests = new Person[contacts.length];
for (int k=0; k < contacts.length; k++)
guests[k] = (Person) contacts[k].clone( ); // returns Object type
www.it-ebooks.info
144 Chapter 3. Fundamental Data Structures
www.it-ebooks.info
3.7. Exercises 145
3.7 Exercises
Reinforcement
R-3.1 Give the next five pseudorandom numbers generated by the process described on
page 113, with a = 12, b = 5, and n = 100, and 92 as the seed for cur.
R-3.2 Write a Java method that repeatedly selects and removes a random entry from an
array until the array holds no more entries.
R-3.3 Explain the changes that would have to be made to the program of Code Frag-
ment 3.8 so that it could perform the Caesar cipher for messages that are written
in an alphabet-based language other than English, such as Greek, Russian, or
Hebrew.
R-3.4 The TicTacToe class of Code Fragments 3.9 and 3.10 has a flaw, in that it allows
a player to place a mark even after the game has already been won by someone.
Modify the class so that the putMark method throws an IllegalStateException in
that case.
R-3.5 The removeFirst method of the SinglyLinkedList class includes a special case to
reset the tail field to null when deleting the last node of a list (see lines 51 and 52
of Code Fragment 3.15). What are the consequences if we were to remove those
two lines from the code? Explain why the class would or would not work with
such a modification.
R-3.6 Give an algorithm for finding the second-to-last node in a singly linked list in
which the last node is indicated by a null next reference.
R-3.7 Consider the implementation of CircularlyLinkedList.addFirst, in Code Frag-
ment 3.16. The else body at lines 39 and 40 of that method relies on a locally
declared variable, newest. Redesign that clause to avoid use of any local vari-
able.
R-3.8 Describe a method for finding the middle node of a doubly linked list with header
and trailer sentinels by “link hopping,” and without relying on explicit knowledge
of the size of the list. In the case of an even number of nodes, report the node
slightly left of center as the “middle.” What is the running time of this method?
R-3.9 Give an implementation of the size( ) method for the SingularlyLinkedList class,
assuming that we did not maintain size as an instance variable.
R-3.10 Give an implementation of the size( ) method for the CircularlyLinkedList class,
assuming that we did not maintain size as an instance variable.
R-3.11 Give an implementation of the size( ) method for the DoublyLinkedList class,
assuming that we did not maintain size as an instance variable.
R-3.12 Implement a rotate( ) method in the SinglyLinkedList class, which has semantics
equal to addLast(removeFirst( )), yet without creating any new node.
www.it-ebooks.info
146 Chapter 3. Fundamental Data Structures
R-3.13 What is the difference between a shallow equality test and a deep equality test
between two Java arrays, A and B, if they are one-dimensional arrays of type int?
What if the arrays are two-dimensional arrays of type int?
R-3.14 Give three different examples of a single Java statement that assigns variable,
backup, to a new array with copies of all int entries of an existing array, original.
R-3.15 Implement the equals( ) method for the CircularlyLinkedList class, assuming that
two lists are equal if they have the same sequence of elements, with correspond-
ing elements currently at the front of the list.
R-3.16 Implement the equals( ) method for the DoublyLinkedList class.
Creativity
C-3.17 Let A be an array of size n ≥ 2 containing integers from 1 to n − 1 inclusive, one
of which is repeated. Describe an algorithm for finding the integer in A that is
repeated.
C-3.18 Let B be an array of size n ≥ 6 containing integers from 1 to n − 5 inclusive, five
of which are repeated. Describe an algorithm for finding the five integers in B
that are repeated.
C-3.19 Give Java code for performing add(e) and remove(i) methods for the Scoreboard
class, as in Code Fragments 3.3 and 3.4, except this time, don’t maintain the game
entries in order. Assume that we still need to keep n entries stored in indices 0 to
n − 1. You should be able to implement the methods without using any loops, so
that the number of steps they perform does not depend on n.
C-3.20 Give examples of values for a and b in the pseudorandom generator given on
page 113 of this chapter such that the result is not very random looking, for
n = 1000.
C-3.21 Suppose you are given an array, A, containing 100 integers that were generated
using the method r.nextInt(10), where r is an object of type java.util.Random.
Let x denote the product of the integers in A. There is a single number that x will
equal with probability at least 0.99. What is that number and what is a formula
describing the probability that x is equal to that number?
C-3.22 Write a method, shuffle(A), that rearranges the elements of array A so that every
possible ordering is equally likely. You may rely on the nextInt(n) method of
the java.util.Random class, which returns a random number between 0 and n − 1
inclusive.
C-3.23 Suppose you are designing a multiplayer game that has n ≥ 1 000 players, num-
bered 1 to n, interacting in an enchanted forest. The winner of this game is the
first player who can meet all the other players at least once (ties are allowed).
Assuming that there is a method meet(i, j), which is called each time a player i
meets a player j (with i 6= j), describe a way to keep track of the pairs of meeting
players and who is the winner.
www.it-ebooks.info
3.7. Exercises 147
C-3.24 Write a Java method that takes two three-dimensional integer arrays and adds
them componentwise.
C-3.25 Describe an algorithm for concatenating two singly linked lists L and M, into a
single list L′ that contains all the nodes of L followed by all the nodes of M.
C-3.26 Give an algorithm for concatenating two doubly linked lists L and M, with header
and trailer sentinel nodes, into a single list L′ .
C-3.27 Describe in detail how to swap two nodes x and y (and not just their contents) in
a singly linked list L given references only to x and y. Repeat this exercise for the
case when L is a doubly linked list. Which algorithm takes more time?
C-3.28 Describe in detail an algorithm for reversing a singly linked list L using only a
constant amount of additional space.
C-3.29 Suppose you are given two circularly linked lists, L and M. Describe an algorithm
for telling if L and M store the same sequence of elements (but perhaps with
different starting points).
C-3.30 Given a circularly linked list L containing an even number of nodes, describe
how to split L into two circularly linked lists of half the size.
C-3.31 Our implementation of a doubly linked list relies on two sentinel nodes, header
and trailer, but a single sentinel node that guards both ends of the list should
suffice. Reimplement the DoublyLinkedList class using only one sentinel node.
C-3.32 Implement a circular version of a doubly linked list, without any sentinels, that
supports all the public behaviors of the original as well as two new update meth-
ods, rotate( ) and rotateBackward( ).
C-3.33 Solve the previous problem using inheritance, such that a DoublyLinkedList class
inherits from the existing CircularlyLinkedList, and the DoublyLinkedList.Node
nested class inherits from CircularlyLinkedList.Node.
C-3.34 Implement the clone( ) method for the CircularlyLinkedList class.
C-3.35 Implement the clone( ) method for the DoublyLinkedList class.
Projects
P-3.36 Write a Java program for a matrix class that can add and multiply arbitrary two-
dimensional arrays of integers.
P-3.37 Write a class that maintains the top ten scores for a game application, implement-
ing the add and remove methods of Section 3.1.1, but using a singly linked list
instead of an array.
P-3.38 Perform the previous project, but use a doubly linked list. Moreover, your imple-
mentation of remove(i) should make the fewest number of pointer hops to get to
the game entry at index i.
P-3.39 Write a program that can perform the Caesar cipher for English messages that
include both upper- and lowercase characters.
www.it-ebooks.info
148 Chapter 3. Fundamental Data Structures
P-3.40 Implement a class, SubstitutionCipher, with a constructor that takes a string with
the 26 uppercase letters in an arbitrary order and uses that as the encoder for a
cipher (that is, A is mapped to the first character of the parameter, B is mapped
to the second, and so on.) You should derive the decoding map from the forward
version.
P-3.41 Redesign the CaesarCipher class as a subclass of the SubstitutionCipher from
the previous problem.
P-3.42 Design a RandomCipher class as a subclass of the SubstitutionCipher from Ex-
ercise P-3.40, so that each instance of the class relies on a random permutation
of letters for its mapping.
P-3.43 In the children’s game, Duck, Duck, Goose, a group of children sit in a circle.
One of them is elected “it” and that person walks around the outside of the circle.
The person who is “it” pats each child on the head, saying “Duck” each time,
until randomly reaching a child that the “it” person identifies as “Goose.” At this
point there is a mad scramble, as the “Goose” and the “it” person race around the
circle. Whoever returns to the Goose’s former place first gets to remain in the
circle. The loser of this race is the “it” person for the next round of play. The
game continues like this until the children get bored or an adult tells them it’s
snack time. Write software that simulates a game of Duck, Duck, Goose.
Chapter Notes
The fundamental data structures of arrays and linked lists discussed in this chapter belong
to the folklore of computer science. They were first chronicled in the computer science
literature by Knuth in his seminal book on Fundamental Algorithms [60].
www.it-ebooks.info
Chapter
4 Algorithm Analysis
Contents
www.it-ebooks.info
150 Chapter 4. Algorithm Analysis
In a classic story, the famous mathematician Archimedes was asked to deter-
mine if a golden crown commissioned by the king was indeed pure gold, and not
part silver, as an informant had claimed. Archimedes discovered a way to perform
this analysis while stepping into a bath. He noted that water spilled out of the bath
in proportion to the amount of him that went in. Realizing the implications of this
fact, he immediately got out of the bath and ran naked through the city shouting,
“Eureka, eureka!” for he had discovered an analysis tool (displacement), which,
when combined with a simple scale, could determine if the king’s new crown was
good or not. That is, Archimedes could dip the crown and an equal-weight amount
of gold into a bowl of water to see if they both displaced the same amount. This
discovery was unfortunate for the goldsmith, however, for when Archimedes did
his analysis, the crown displaced more water than an equal-weight lump of pure
gold, indicating that the crown was not, in fact, pure gold.
In this book, we are interested in the design of “good” data structures and algo-
rithms. Simply put, a data structure is a systematic way of organizing and access-
ing data, and an algorithm is a step-by-step procedure for performing some task in
a finite amount of time. These concepts are central to computing, but to be able to
classify some data structures and algorithms as “good,” we must have precise ways
of analyzing them.
The primary analysis tool we will use in this book involves characterizing the
running times of algorithms and data structure operations, with space usage also
being of interest. Running time is a natural measure of “goodness,” since time is a
precious resource—computer solutions should run as fast as possible. In general,
the running time of an algorithm or data structure operation increases with the input
size, although it may also vary for different inputs of the same size. Also, the run-
ning time is affected by the hardware environment (e.g., the processor, clock rate,
memory, disk) and software environment (e.g., the operating system, programming
language) in which the algorithm is implemented and executed. All other factors
being equal, the running time of the same algorithm on the same input data will be
smaller if the computer has, say, a much faster processor or if the implementation
is done in a program compiled into native machine code instead of an interpreted
implementation run on a virtual machine. We begin this chapter by discussing tools
for performing experimental studies, yet also limitations to the use of experiments
as a primary means for evaluating algorithm efficiency.
Focusing on running time as a primary measure of goodness requires that we be
able to use a few mathematical tools. In spite of the possible variations that come
from different environmental factors, we would like to focus on the relationship
between the running time of an algorithm and the size of its input. We are interested
in characterizing an algorithm’s running time as a function of the input size. But
what is the proper way of measuring it? In this chapter, we “roll up our sleeves”
and develop a mathematical way of analyzing algorithms.
www.it-ebooks.info
4.1. Experimental Studies 151
www.it-ebooks.info
152 Chapter 4. Algorithm Analysis
As a tangible example of experimental analysis, we consider two algorithms
for constructing long strings in Java. Our goal will be to have a method, with a
calling signature such as repeat('*', 40), that produces a string composed of 40
asterisks: "****************************************".
The first algorithm we consider performs repeated string concatenation, based
on the + operator. It is implemented as method repeat1 in Code Fragment 4.2.
The second algorithm relies on Java’s StringBuilder class (see Section 1.3), and is
implemented as method repeat2 in Code Fragment 4.2.
Table 4.1: Results of timing experiment on the methods from Code Fragment 4.2.
www.it-ebooks.info
4.1. Experimental Studies 153
109
108
Figure 4.1: Chart of the results of the timing experiment from Code Fragment 4.2,
displayed on a log-log scale. The divergent slopes demonstrate an order of magni-
tude difference in the growth of the running times.
The most striking outcome of these experiments is how much faster the repeat2
algorithm is relative to repeat1. While repeat1 is already taking more than 3 days
to compose a string of 12.8 million characters, repeat2 is able to do the same in a
fraction of a second. We also see some interesting trends in how the running times
of the algorithms each depend upon the size of n. As the value of n is doubled, the
running time of repeat1 typically increases more than fourfold, while the running
time of repeat2 approximately doubles.
www.it-ebooks.info
154 Chapter 4. Algorithm Analysis
www.it-ebooks.info
4.1. Experimental Studies 155
Focusing on the Worst-Case Input
An algorithm may run faster on some inputs than it does on others of the same size.
Thus, we may wish to express the running time of an algorithm as the function of
the input size obtained by taking the average over all possible inputs of the same
size. Unfortunately, such an average-case analysis is typically quite challenging.
It requires us to define a probability distribution on the set of inputs, which is often
a difficult task. Figure 4.2 schematically shows how, depending on the input distri-
bution, the running time of an algorithm can be anywhere between the worst-case
time and the best-case time. For example, what if inputs are really only of types
“A” or “D”?
An average-case analysis usually requires that we calculate expected running
times based on a given input distribution, which usually involves sophisticated
probability theory. Therefore, for the remainder of this book, unless we specify
otherwise, we will characterize running times in terms of the worst case, as a func-
tion of the input size, n, of the algorithm.
Worst-case analysis is much easier than average-case analysis, as it requires
only the ability to identify the worst-case input, which is often simple. Also, this
approach typically leads to better algorithms. Making the standard of success for an
algorithm to perform well in the worst case necessarily requires that it will do well
on every input. That is, designing for the worst case leads to stronger algorithmic
“muscles,” much like a track star who always practices by running up an incline.
5 ms worst-case time
4 ms
average-case time?
Running Time
3 ms
best-case time
2 ms
1 ms
A B C D E F G
Input Instance
Figure 4.2: The difference between best-case and worst-case time. Each bar repre-
sents the running time of some algorithm on a different possible input.
www.it-ebooks.info
156 Chapter 4. Algorithm Analysis
www.it-ebooks.info
4.2. The Seven Functions Used in This Book 157
Computing the logarithm function exactly for any integer n involves the use of
calculus, but we can use an approximation that is good enough for our purposes
without calculus. We recall that the ceiling of a real number, x, is the smallest
integer greater than or equal to x, denoted with ⌈x⌉. The ceiling of x can be viewed
as an integer approximation of x since we have x ≤ ⌈x⌉ < x + 1. For a positive
integer, n, we repeatedly divide n by b and stop when we get a number less than or
equal to 1. The number of divisions performed is equal to ⌈logb n⌉. We give below
three examples of the computation of ⌈logb n⌉ by repeated divisions:
• ⌈log3 27⌉ = 3, because ((27/3)/3)/3 = 1;
• ⌈log4 64⌉ = 3, because ((64/4)/4)/4 = 1;
• ⌈log2 12⌉ = 4, because (((12/2)/2)/2)/2 = 0.75 ≤ 1.
The following proposition describes several important identities that involve
logarithms for any base greater than 1.
Proposition 4.1 (Logarithm Rules): Given real numbers a > 0, b > 1, c > 0,
and d > 1, we have:
1. logb (ac) = logb a + logb c
2. logb (a/c) = logb a − logb c
3. logb (ac ) = c logb a
4. logb a = logd a/ logd b
5. blogd a = alogd b
By convention, the unparenthesized notation log nc denotes the value log(nc ).
We use a notational shorthand, logc n, to denote the quantity, (log n)c , in which the
result of the logarithm is raised to a power.
The above identities can be derived from converse rules for exponentiation that
we will present on page 161. We illustrate these identities with a few examples.
Example 4.2: We demonstrate below some interesting applications of the loga-
rithm rules from Proposition 4.1 (using the usual convention that the base of a
logarithm is 2 if it is omitted).
• log(2n) = log 2 + log n = 1 + log n, by rule 1
• log(n/2) = log n − log 2 = log n − 1, by rule 2
• log n3 = 3 log n, by rule 3
• log 2n = n log 2 = n · 1 = n, by rule 3
• log4 n = (log n)/ log 4 = (log n)/2, by rule 4
• 2log n = nlog 2 = n1 = n, by rule 5.
As a practical matter, we note that rule 4 gives us a way to compute the base-two
logarithm on a calculator that has a base-10 logarithm button, LOG, for
www.it-ebooks.info
158 Chapter 4. Algorithm Analysis
f (n) = n.
That is, given an input value n, the linear function f assigns the value n itself.
This function arises in algorithm analysis any time we have to do a single basic
operation for each of n elements. For example, comparing a number x to each
element of an array of size n will require n comparisons. The linear function also
represents the best running time we can hope to achieve for any algorithm that
processes each of n objects that are not already in the computer’s memory, because
reading in the n objects already requires n operations.
f (n) = n log n,
that is, the function that assigns to an input n the value of n times the logarithm
base-two of n. This function grows a little more rapidly than the linear function and
a lot less rapidly than the quadratic function; therefore, we would greatly prefer an
algorithm with a running time that is proportional to n log n, than one with quadratic
running time. We will see several important algorithms that exhibit a running time
proportional to the n-log-n function. For example, the fastest possible algorithms
for sorting n arbitrary values require time proportional to n log n.
f (n) = n2 .
That is, given an input value n, the function f assigns the product of n with itself
(in other words, “n squared”).
The main reason why the quadratic function appears in the analysis of algo-
rithms is that there are many algorithms that have nested loops, where the inner
loop performs a linear number of operations and the outer loop is performed a
linear number of times. Thus, in such cases, the algorithm performs n · n = n2
operations.
www.it-ebooks.info
4.2. The Seven Functions Used in This Book 159
Nested Loops and the Quadratic Function
The quadratic function can also arise in the context of nested loops where the first
iteration of a loop uses one operation, the second uses two operations, the third uses
three operations, and so on. That is, the number of operations is
1 + 2 + 3 + · · · + (n − 2) + (n − 1) + n.
In other words, this is the total number of operations that will be performed by the
nested loop if the number of operations performed inside the loop increases by one
with each iteration of the outer loop. This quantity also has an interesting history.
In 1787, a German schoolteacher decided to keep his 9- and 10-year-old pupils
occupied by adding up the integers from 1 to 100. But almost immediately one
of the children claimed to have the answer! The teacher was suspicious, for the
student had only the answer on his slate. But the answer, 5050, was correct and the
student, Carl Gauss, grew up to be one of the greatest mathematicians of his time.
We presume that young Gauss used the following identity.
Proposition 4.3: For any integer n ≥ 1, we have:
n(n + 1)
1 + 2 + 3 + · · · + (n − 2) + (n − 1) + n = .
2
We give two “visual” justifications of Proposition 4.3 in Figure 4.3.
n+1
n n
...
...
3 3
2 2
1 1
0 n 0
1 2 3 1 2 n/2
(a) (b)
Figure 4.3: Visual justifications of Proposition 4.3. Both illustrations visualize the
identity in terms of the total area covered by n unit-width rectangles with heights
1, 2, . . . , n. In (a), the rectangles are shown to cover a big triangle of area n2 /2 (base
n and height n) plus n small triangles of area 1/2 each (base 1 and height 1). In
(b), which applies only when n is even, the rectangles are shown to cover a big
rectangle of base n/2 and height n + 1.
www.it-ebooks.info
160 Chapter 4. Algorithm Analysis
The lesson to be learned from Proposition 4.3 is that if we perform an algorithm
with nested loops such that the operations in the inner loop increase by one each
time, then the total number of operations is quadratic in the number of times, n,
we perform the outer loop. To be fair, the number of operations is n2 /2 + n/2,
and so this is just over half the number of operations than an algorithm that uses n
operations each time the inner loop is performed. But the order of growth is still
quadratic in n.
which assigns to an input value n the product of n with itself three times.
The cubic function appears less frequently in the context of algorithm analysis
than the constant, linear, and quadratic functions previously mentioned, but it does
appear from time to time.
Polynomials
The linear, quadratic and cubic functions can each be viewed as being part of a
larger class of functions, the polynomials. A polynomial function has the form,
f (n) = a0 + a1 n + a2 n2 + a3 n3 + · · · + ad nd ,
www.it-ebooks.info
4.2. The Seven Functions Used in This Book 161
Summations
A notation that appears again and again in the analysis of data structures and algo-
rithms is the summation, which is defined as follows:
b
∑ f (i) = f (a) + f (a + 1) + f (a + 2) + · · · + f (b),
i=a
where a and b are integers and a ≤ b. Summations arise in data structure and algo-
rithm analysis because the running times of loops naturally give rise to summations.
Using a summation, we can rewrite the formula of Proposition 4.3 as
n
n(n + 1)
∑i= 2
.
i=1
Thus, the summation notation gives us a shorthand way of expressing sums of in-
creasing terms that have a regular structure.
www.it-ebooks.info
162 Chapter 4. Algorithm Analysis
For example, we have the following:
• 256 = 162 = (24 )2 = 24·2 = 28 = 256 (Exponent Rule 1)
• 243 = 35 = 32+3 = 32 33 = 9 · 27 = 243 (Exponent Rule 2)
• 16 = 1024/64 = 210 /26 = 210−6 = 24 = 16 (Exponent Rule 3)
We can extend the exponential function to exponents that are fractions or real
numbers and to negative exponents, as follows. Given a positive integer k, we de-
fine b1/k to be k th root of b, that is, the number r such that rk = b. For example,
251/2 = 5, since 52 = 25. Likewise, 271/3 = 3 and 161/4 = 2. This approach al-
lows us to define any power whose exponent can be expressed as a fraction, for
ba/c = (ba )1/c , by Exponent Rule 1. For example, 93/2 = (93 )1/2 = 7291/2 = 27.
Thus, ba/c is really just the c th root of the integral exponent ba .
We can further extend the exponential function to define bx for any real number
x, by computing a series of numbers of the form ba/c for fractions a/c that get pro-
gressively closer and closer to x. Any real number x can be approximated arbitrarily
closely by a fraction a/c; hence, we can use the fraction a/c as the exponent of b
to get arbitrarily close to bx . For example, the number 2π is well defined. Finally,
given a negative exponent d, we define bd = 1/b−d , which corresponds to applying
Exponent Rule 3 with a = 0 and c = −d. For example, 2−3 = 1/23 = 1/8.
Geometric Sums
Suppose we have a loop for which each iteration takes a multiplicative factor longer
than the previous one. This loop can be analyzed using the following proposition.
Proposition 4.5: For any integer n ≥ 0 and any real number a such that a > 0 and
a 6= 1, consider the summation
n
∑ ai = 1 + a + a2 + · · · + an
i=0
an+1 − 1
.
a−1
www.it-ebooks.info
4.2. The Seven Functions Used in This Book 163
1044 Exponential
1040 Cubic
1036
Quadratic
1032
1028 N-Log-N
1024
f (n)
Linear
1020
Logarithmic
1016
1012 Constant
108
104
100
100 101 102 103 104 105 106 107 108 109 1010 1011 1012 1013 1014 1015
n
Figure 4.4: Growth rates for the seven fundamental functions used in algorithm
analysis. We use base a = 2 for the exponential function. The functions are plotted
on a log-log chart to compare the growth rates primarily as slopes. Even so, the
exponential function grows too fast to display all its values on the chart.
www.it-ebooks.info
164 Chapter 4. Algorithm Analysis
cg(n)
Running Time
f(n)
n0 Input Size
Figure 4.5: Illustrating the “big-Oh” notation. The function f (n) is O(g(n)), since
f (n) ≤ c · g(n) when n ≥ n0 .
www.it-ebooks.info
4.3. Asymptotic Analysis 165
Example 4.6: The function 8n + 5 is O(n).
Justification: By the big-Oh definition, we need to find a real constant c > 0 and
an integer constant n0 ≥ 1 such that 8n + 5 ≤ cn for every integer n ≥ n0 . It is easy
to see that a possible choice is c = 9 and n0 = 5. Indeed, this is one of infinitely
many choices available because there is a trade-off between c and n0 . For example,
we could rely on constants c = 13 and n0 = 1.
The big-Oh notation allows us to say that a function f (n) is “less than or equal
to” another function g(n) up to a constant factor and in the asymptotic sense as n
grows toward infinity. This ability comes from the fact that the definition uses “≤”
to compare f (n) to a g(n) times a constant, c, for the asymptotic cases when n ≥ n0 .
However, it is considered poor taste to say “ f (n) ≤ O(g(n)),” since the big-Oh
already denotes the “less-than-or-equal-to” concept. Likewise, although common,
it is not fully correct to say “ f (n) = O(g(n)),” with the usual understanding of the
“=” relation, because there is no way to make sense of the symmetric statement,
“O(g(n)) = f (n).” It is best to say, “ f (n) is O(g(n)).”
Alternatively, we can say “ f (n) is order of g(n).” For the more mathematically
inclined, it is also correct to say, “ f (n) ∈ O(g(n)),” for the big-Oh notation, techni-
cally speaking, denotes a whole collection of functions. In this book, we will stick
to presenting big-Oh statements as “ f (n) is O(g(n)).” Even with this interpretation,
there is considerable freedom in how we can use arithmetic operations with the big-
Oh notation, and with this freedom comes a certain amount of responsibility.
www.it-ebooks.info
166 Chapter 4. Algorithm Analysis
Thus, the highest-degree term in a polynomial is the term that determines the
asymptotic growth rate of that polynomial. We consider some additional properties
of the big-Oh notation in the exercises. Let us consider some further examples here,
focusing on combinations of the seven fundamental functions used in algorithm
design. We rely on the mathematical fact that log n ≤ n for n ≥ 1.
Example 4.9: 5n2 + 3n log n + 2n + 5 is O(n2 ).
Justification: 5n2 + 3n log n+ 2n+ 5 ≤ (5+ 3+ 2+ 5)n2 = cn2 , for c = 15, when
n ≥ n0 = 1.
www.it-ebooks.info
4.3. Asymptotic Analysis 167
The seven functions listed in Section 4.2 are the most common functions used
in conjunction with the big-Oh notation to characterize the running times and space
usage of algorithms. Indeed, we typically use the names of these functions to refer
to the running times of the algorithms they characterize. So, for example, we would
say that an algorithm that runs in worst-case time 4n2 + n log n is a quadratic-time
algorithm, since it runs in O(n2 ) time. Likewise, an algorithm running in time at
most 5n + 20 log n + 4 would be called a linear-time algorithm.
Big-Omega
Just as the big-Oh notation provides an asymptotic way of saying that a function is
“less than or equal to” another function, the following notations provide an asymp-
totic way of saying that a function grows at a rate that is “greater than or equal to”
that of another.
Let f (n) and g(n) be functions mapping positive integers to positive real num-
bers. We say that f (n) is Ω(g(n)), pronounced “ f (n) is big-Omega of g(n),” if g(n)
is O( f (n)), that is, there is a real constant c > 0 and an integer constant n0 ≥ 1 such
that
f (n) ≥ cg(n), for n ≥ n0 .
This definition allows us to say asymptotically that one function is greater than or
equal to another, up to a constant factor.
Big-Theta
In addition, there is a notation that allows us to say that two functions grow at the
same rate, up to constant factors. We say that f (n) is Θ(g(n)), pronounced “ f (n)
is big-Theta of g(n),” if f (n) is O(g(n)) and f (n) is Ω(g(n)), that is, there are real
constants c′ > 0 and c′′ > 0, and an integer constant n0 ≥ 1 such that
www.it-ebooks.info
168 Chapter 4. Algorithm Analysis
1, log n, n, n log n, n2 , n3 , 2n .
We illustrate the growth rates of the seven functions in Table 4.3. (See also
Figure 4.4 from Section 4.2.1.)
n log n n n log n n2 n3 2n
8 3 8 24 64 512 256
16 4 16 64 256 4, 096 65, 536
32 5 32 160 1, 024 32, 768 4, 294, 967, 296
64 6 64 384 4, 096 262, 144 1.84 × 1019
128 7 128 896 16, 384 2, 097, 152 3.40 × 1038
256 8 256 2, 048 65, 536 16, 777, 216 1.15 × 1077
512 9 512 4, 608 262, 144 134, 217, 728 1.34 × 10154
Table 4.4: Maximum size of a problem that can be solved in 1 second, 1 minute,
and 1 hour, for various running times measured in microseconds.
www.it-ebooks.info
4.3. Asymptotic Analysis 169
The importance of good algorithm design goes beyond just what can be solved
effectively on a given computer, however. As shown in Table 4.5, even if we
achieve a dramatic speedup in hardware, we still cannot overcome the handicap
of an asymptotically slow algorithm. This table shows the new maximum problem
size achievable for any fixed amount of time, assuming algorithms with the given
running times are now run on a computer 256 times faster than the previous one.
Table 4.5: Increase in the maximum size of a problem that can be solved in a fixed
amount of time, by using a computer that is 256 times faster than the previous one.
Each entry is a function of m, the previous maximum problem size.
www.it-ebooks.info
170 Chapter 4. Algorithm Analysis
If we must draw a line between efficient and inefficient algorithms, therefore,
it is natural to make this distinction be that between those algorithms running in
polynomial time and those running in exponential time. That is, make the distinc-
tion between algorithms with a running time that is O(nc ), for some constant c > 1,
and those with a running time that is O(b n ), for some constant b > 1. Like so many
notions we have discussed in this section, this too should be taken with a “grain of
salt,” for an algorithm running in O(n100 ) time should probably not be considered
“efficient.” Even so, the distinction between polynomial-time and exponential-time
algorithms is considered a robust measure of tractability.
Constant-Time Operations
All of the primitive operations, originally described on page 154, are assumed to
run in constant time; formally, we say they run in O(1) time. We wish to empha-
size several important constant-time operations that involve arrays. Assume that
variable A is an array of n elements. The expression A.length in Java is evaluated
in constant time, because arrays are represented internally with an explicit variable
that records the length of the array. Another central behavior of arrays is that for
any valid index j, the individual element, A[ j], can be accessed in constant time.
This is because an array uses a consecutive block of memory. The j th element can
be found, not by iterating through the array one element at a time, but by validating
the index, and using it as an offset from the beginning of the array in determin-
ing the appropriate memory address. Therefore, we say that the expression A[ j] is
evaluated in O(1) time for an array.
www.it-ebooks.info
4.3. Asymptotic Analysis 171
1 /∗∗ Returns the maximum value of a nonempty array of numbers. ∗/
2 public static double arrayMax(double[ ] data) {
3 int n = data.length;
4 double currentMax = data[0]; // assume first entry is biggest (for now)
5 for (int j=1; j < n; j++) // consider all other entries
6 if (data[j] > currentMax) // if data[j] is biggest thus far...
7 currentMax = data[j]; // record it as the current max
8 return currentMax;
9 }
Code Fragment 4.3: A method that returns the maximum value of an array.
Using the big-Oh notation, we can write the following mathematically precise
statement on the running time of algorithm arrayMax for any computer.
Proposition 4.16: The algorithm, arrayMax, for computing the maximum ele-
ment of an array of n numbers, runs in O(n) time.
Justification: The initialization at lines 3 and 4 and the return statement at line 8
require only a constant number of primitive operations. Each iteration of the loop
also requires only a constant number of primitive operations, and the loop executes
n − 1 times. Therefore, we account for the number of primitive operations being
c′ ·(n−1)+c′′ for appropriate constants c′ and c′′ that reflect, respectively, the work
performed inside and outside the loop body. Because each primitive operation runs
in constant time, we have that the running time of algorithm arrayMax on an input
of size n is at most c′ · (n − 1) + c′′ = c′ · n + (c′′ − c′ ) ≤ c′ · n if we assume, without
loss of generality, that c′′ ≤ c′ . We conclude that the running time of algorithm
arrayMax is O(n).
www.it-ebooks.info
172 Chapter 4. Algorithm Analysis
Composing Long Strings
As our next example, we revisit the experimental study from Section 4.1, in which
we examined two different implementations for composing a long string (see Code
Fragment 4.2). Our first algorithm was based on repeated use of the string concate-
nation operator; for convenience, that method is also given in Code Fragment 4.4.
The most important aspect of this implementation is that strings in Java are
immutable objects. Once created, an instance cannot be modified. The command,
answer += c, is shorthand for answer = (answer + c). This command does not
cause a new character to be added to the existing String instance; instead it produces
a new String with the desired sequence of characters, and then it reassigns the
variable, answer, to refer to that new string.
In terms of efficiency, the problem with this interpretation is that the creation
of a new string as a result of a concatenation, requires time that is proportional
to the length of the resulting string. The first time through this loop, the result
has length 1, the second time through the loop the result has length 2, and so on,
until we reach the final string of length n. Therefore, the overall time taken by this
algorithm is proportional to
1 + 2 + · · · + n,
which we recognize as the familiar O(n2 ) summation from Proposition 4.3. There-
fore, the total time complexity of the repeat1 algorithm is O(n2 ).
We see this theoretical analysis reflected in the experimental results. The run-
ning time of a quadratic algorithm should theoretically quadruple if the size of the
problem doubles, as (2n)2 = 4 · n2 . (We say “theoretically,” because this does not
account for lower-order terms that are hidden by the asymptotic notation.) We see
such an approximate fourfold increase in the running time of repeat1 in Table 4.1
on page 152.
In contrast, the running times in that table for the repeat2 algorithm, which uses
Java’s StringBuilder class, demonstrate a trend of approximately doubling each
time the problem size doubles. The StringBuilder class relies on an advanced tech-
nique with a worst-case running time of O(n) for composing a string of length n;
we will later explore that technique as the focus of Section 7.2.1.
www.it-ebooks.info
4.3. Asymptotic Analysis 173
Three-Way Set Disjointness
Suppose we are given three sets, A, B, and C, stored in three different integer arrays.
We will assume that no individual set contains duplicate values, but that there may
be some numbers that are in two or three of the sets. The three-way set disjointness
problem is to determine if the intersection of the three sets is empty, namely, that
there is no element x such that x ∈ A, x ∈ B, and x ∈ C. A simple Java method to
determine this property is given in Code Fragment 4.5.
This simple algorithm loops through each possible triple of values from the
three sets to see if those values are equivalent. If each of the original sets has size
n, then the worst-case running time of this method is O(n3 ).
We can improve upon the asymptotic performance with a simple observation.
Once inside the body of the loop over B, if selected elements a and b do not match
each other, it is a waste of time to iterate through all values of C looking for a
matching triple. An improved solution to this problem, taking advantage of this
observation, is presented in Code Fragment 4.6.
In the improved version, it is not simply that we save time if we get lucky. We
claim that the worst-case running time for disjoint2 is O(n2 ). There are quadrat-
ically many pairs (a, b) to consider. However, if A and B are each sets of distinct
www.it-ebooks.info
174 Chapter 4. Algorithm Analysis
elements, there can be at most O(n) such pairs with a equal to b. Therefore, the
innermost loop, over C, executes at most n times.
To account for the overall running time, we examine the time spent executing
each line of code. The management of the for loop over A requires O(n) time. The
management of the for loop over B accounts for a total of O(n2 ) time, since that
loop is executed n different times. The test a == b is evaluated O(n2 ) times. The
rest of the time spent depends upon how many matching (a, b) pairs exist. As we
have noted, there are at most n such pairs; therefore, the management of the loop
over C and the commands within the body of that loop use at most O(n2 ) time. By
our standard application of Proposition 4.8, the total time spent is O(n2 ).
Element Uniqueness
A problem that is closely related to the three-way set disjointness problem is the
element uniqueness problem. In the former, we are given three sets and we pre-
sumed that there were no duplicates within a single set. In the element uniqueness
problem, we are given an array with n elements and asked whether all elements of
that collection are distinct from each other.
Our first solution to this problem uses a straightforward iterative algorithm.
The unique1 method, given in Code Fragment 4.7, solves the element uniqueness
problem by looping through all distinct pairs of indices j < k, checking if any of
those pairs refer to elements that are equivalent to each other. It does this using two
nested for loops, such that the first iteration of the outer loop causes n − 1 iterations
of the inner loop, the second iteration of the outer loop causes n − 2 iterations of
the inner loop, and so on. Thus, the worst-case running time of this method is
proportional to
(n − 1) + (n − 2) + · · · + 2 + 1,
www.it-ebooks.info
4.3. Asymptotic Analysis 175
Using Sorting as a Problem-Solving Tool
An even better algorithm for the element uniqueness problem is based on using
sorting as a problem-solving tool. In this case, by sorting the array of elements, we
are guaranteed that any duplicate elements will be placed next to each other. Thus,
to determine if there are any duplicates, all we need to do is perform a single pass
over the sorted array, looking for consecutive duplicates.
A Java implementation of this algorithm is given in Code Fragment 4.8. (See
Section 3.1.3 for discussion of the java.util.Arrays class.)
Sorting algorithms will be the focus of Chapter 12. The best sorting algorithms
(including those used by Array.sort in Java) guarantee a worst-case running time of
O(n log n). Once the data is sorted, the subsequent loop runs in O(n) time, and so
the entire unique2 algorithm runs in O(n log n) time. Exercise C-4.35 explores the
use of sorting to solve the three-way set disjointness problem in O(n log n) time.
Prefix Averages
The next problem we consider is computing what are known as prefix averages of
a sequence of numbers. Namely, given a sequence x consisting of n numbers, we
want to compute a sequence a such that a j is the average of elements x0 , . . . , x j , for
j = 0, . . . , n − 1, that is, j
∑ xi
a j = i=0 .
j+1
Prefix averages have many applications in economics and statistics. For example,
given the year-by-year returns of a mutual fund, ordered from recent to past, an
investor will typically want to see the fund’s average annual returns for the most
recent year, the most recent three years, the most recent five years, and so on. Like-
wise, given a stream of daily Web usage logs, a website manager may wish to track
average usage trends over various time periods. We present two implementation
for computing prefix averages, yet with significantly different running times.
www.it-ebooks.info
176 Chapter 4. Algorithm Analysis
A Quadratic-Time Algorithm
Our first algorithm for computing prefix averages, denoted as prefixAverage1, is
shown in Code Fragment 4.9. It computes each element a j independently, using an
inner loop to compute that partial sum.
1 /∗∗ Returns an array a such that, for all j, a[j] equals the average of x[0], ..., x[j]. ∗/
2 public static double[ ] prefixAverage1(double[ ] x) {
3 int n = x.length;
4 double[ ] a = new double[n]; // filled with zeros by default
5 for (int j=0; j < n; j++) {
6 double total = 0; // begin computing x[0] + ... + x[j]
7 for (int i=0; i <= j; i++)
8 total += x[i];
9 a[j] = total / (j+1); // record the average
10 }
11 return a;
12 }
Code Fragment 4.9: Algorithm prefixAverage1.
www.it-ebooks.info
4.3. Asymptotic Analysis 177
A Linear-Time Algorithm
An intermediate value in the computation of the prefix average is the prefix sum
x0 + x1 + · · · + x j , denoted as total in our first implementation; this allows us to
compute the prefix average a[j] = total / (j + 1). In our first algorithm, the prefix
sum is computed anew for each value of j. That contributed O( j) time for each j,
leading to the quadratic behavior.
For greater efficiency, we can maintain the current prefix sum dynamically,
effectively computing x0 + x1 + · · · + x j as total + x j , where value total is equal to
the sum x0 + x1 + · · ·+ x j−1 , when computed by the previous pass of the loop over j.
Code Fragment 4.10 provides a new implementation, denoted as prefixAverage2,
using this approach.
1 /∗∗ Returns an array a such that, for all j, a[j] equals the average of x[0], ..., x[j]. ∗/
2 public static double[ ] prefixAverage2(double[ ] x) {
3 int n = x.length;
4 double[ ] a = new double[n]; // filled with zeros by default
5 double total = 0; // compute prefix sum as x[0] + x[1] + ...
6 for (int j=0; j < n; j++) {
7 total += x[j]; // update prefix sum to include x[j]
8 a[j] = total / (j+1); // compute average based on current sum
9 }
10 return a;
11 }
Code Fragment 4.10: Algorithm prefixAverage2.
www.it-ebooks.info
178 Chapter 4. Algorithm Analysis
4.4.1 By Example
Some claims are of the generic form, “There is an element x in a set S that has
property P.” To justify such a claim, we only need to produce a particular x in S
that has property P. Likewise, some hard-to-believe claims are of the generic form,
“Every element x in a set S has property P.” To justify that such a claim is false, we
only need to produce a particular x from S that does not have property P. Such an
instance is called a counterexample.
Example 4.17: Professor Amongus claims that every number of the form 2i − 1
is a prime, when i is an integer greater than 1. Professor Amongus is wrong.
Justification: To justify this claim, consider the contrapositive, “If a is odd and
b is odd, then ab is odd.” So, suppose a = 2 j + 1 and b = 2k + 1, for some integers
j and k. Then ab = 4 jk + 2 j + 2k + 1 = 2(2 jk + j + k) + 1; hence, ab is odd.
Besides showing a use of the contrapositive justification technique, the previous
example also contains an application of de Morgan’s law. This law helps us deal
with negations, for it states that the negation of a statement of the form “p or q” is
“not p and not q.” Likewise, it states that the negation of a statement of the form
“p and q” is “not p or not q.”
www.it-ebooks.info
4.4. Simple Justification Techniques 179
Contradiction
Example 4.19: Let a and b be integers. If ab is odd, then a is odd and b is odd.
Justification: Let ab be odd. We wish to show that a is odd and b is odd. So,
with the hope of leading to a contradiction, let us assume the opposite, namely,
suppose a is even or b is even. In fact, without loss of generality, we can assume
that a is even (since the case for b is symmetric). Then a = 2 j for some integer
j. Hence, ab = (2 j)b = 2( jb), that is, ab is even. But this is a contradiction: ab
cannot simultaneously be odd and even. Therefore, a is odd and b is odd.
Induction
We can often justify claims such as those above as true, however, by using the
technique of induction. This technique amounts to showing that, for any particular
n ≥ 1, there is a finite sequence of implications that starts with something known
to be true and ultimately leads to showing that q(n) is true. Specifically, we begin a
justification by induction by showing that q(n) is true for n = 1 (and possibly some
other values n = 2, 3, . . . , k, for some constant k). Then we justify that the inductive
“step” is true for n > k, namely, we show “if q( j) is true for all j < n, then q(n) is
true.” The combination of these two pieces completes the justification by induction.
www.it-ebooks.info
180 Chapter 4. Algorithm Analysis
Proposition 4.20: Consider the Fibonacci function F(n), which is defined such
that F(1) = 1, F(2) = 2, and F(n) = F(n − 2) + F(n − 1) for n > 2. (See Sec-
tion 2.2.3.) We claim that F(n) < 2n .
Since
2n−2 + 2n−1 < 2n−1 + 2n−1 = 2 · 2n−1 = 2n ,
we have that F(n) < 2n , thus showing the inductive hypothesis for n.
Let us do another inductive argument, this time for a fact we have seen before.
Proposition 4.21: (which is the same as Proposition 4.3)
n
n(n + 1)
∑i= 2
.
i=1
n−1
(n − 1)(n − 1 + 1) (n − 1)n
∑i= 2
=
2
.
i=1
Hence, we obtain
n n−1
(n − 1)n 2n + n2 − n n2 + n n(n + 1)
∑i = n+ ∑ i = n+ 2
=
2
=
2
=
2
,
i=1 i=1
www.it-ebooks.info
4.4. Simple Justification Techniques 181
Loop Invariants
The final justification technique we discuss in this section is the loop invariant. To
prove some statement L about a loop is correct, define L in terms of a series of
smaller statements L0 , L1 , . . . , Lk , where:
1. The initial claim, L0 , is true before the loop begins.
2. If L j−1 is true before iteration j, then L j will be true after iteration j.
3. The final statement, Lk , implies the desired statement L to be true.
Let us give a simple example of using a loop-invariant argument to justify the
correctness of an algorithm. In particular, we use a loop invariant to justify that
the method arrayFind (see Code Fragment 4.11) finds the smallest index at which
element val occurs in array A.
1 /∗∗ Returns index j such that data[j] == val, or −1 if no such element. ∗/
2 public static int arrayFind(int[ ] data, int val) {
3 int n = data.length;
4 int j = 0;
5 while (j < n) { // val is not equal to any of the first j elements of data
6 if (data[j] == val)
7 return j; // a match was found at index j
8 j++; // continue to next index
9 // val is not equal to any of the first j elements of data
10 }
11 return −1; // if we reach this, no match found
12 }
Code Fragment 4.11: Algorithm arrayFind for finding the first index at which a
given element occurs in an array.
www.it-ebooks.info
182 Chapter 4. Algorithm Analysis
4.5 Exercises
Reinforcement
R-4.1 Graph the functions 8n, 4n log n, 2n2 , n3 , and 2n using a logarithmic scale for
the x- and y-axes; that is, if the function value f (n) is y, plot this as a point with
x-coordinate at log n and y-coordinate at log y.
R-4.2 The number of operations executed by algorithms A and B is 8n logn and 2n2 ,
respectively. Determine n0 such that A is better than B for n ≥ n0 .
R-4.3 The number of operations executed by algorithms A and B is 40n2 and 2n3 , re-
spectively. Determine n0 such that A is better than B for n ≥ n0 .
R-4.4 Give an example of a function that is plotted the same on a log-log scale as it is
on a standard scale.
R-4.5 Explain why the plot of the function nc is a straight line with slope c on a log-log
scale.
R-4.6 What is the sum of all the even numbers from 0 to 2n, for any integer n ≥ 1?
R-4.7 Show that the following two statements are equivalent:
(a) The running time of algorithm A is always O( f (n)).
(b) In the worst case, the running time of algorithm A is O( f (n)).
R-4.8 Order the following functions by asymptotic growth rate.
4n log n + 2n 210 2log n
3n + 100 logn 4n 2n
n2 + 10n n3 n log n
R-4.9 Give a big-Oh characterization, in terms of n, of the running time of the example1
method shown in Code Fragment 4.12.
R-4.10 Give a big-Oh characterization, in terms of n, of the running time of the example2
method shown in Code Fragment 4.12.
R-4.11 Give a big-Oh characterization, in terms of n, of the running time of the example3
method shown in Code Fragment 4.12.
R-4.12 Give a big-Oh characterization, in terms of n, of the running time of the example4
method shown in Code Fragment 4.12.
R-4.13 Give a big-Oh characterization, in terms of n, of the running time of the example5
method shown in Code Fragment 4.12.
R-4.14 Show that if d(n) is O( f (n)), then ad(n) is O( f (n)), for any constant a > 0.
R-4.15 Show that if d(n) is O( f (n)) and e(n) is O(g(n)), then the product d(n)e(n) is
O( f (n)g(n)).
R-4.16 Show that if d(n) is O( f (n)) and e(n) is O(g(n)), then d(n) + e(n) is O( f (n) +
g(n)).
www.it-ebooks.info