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

Data Structures Into Java Ucb Cs61b Textbook Itebooks download

The document is a resource for the 'Data Structures Into Java' textbook used in UC Berkeley's CS61B course, detailing various data structures and algorithms. It includes links to additional recommended ebooks on related topics. The textbook covers algorithmic complexity, data types, and implementations of various data structures such as trees and search trees.

Uploaded by

amemoromneia
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
15 views

Data Structures Into Java Ucb Cs61b Textbook Itebooks download

The document is a resource for the 'Data Structures Into Java' textbook used in UC Berkeley's CS61B course, detailing various data structures and algorithms. It includes links to additional recommended ebooks on related topics. The textbook covers algorithmic complexity, data types, and implementations of various data structures such as trees and search trees.

Uploaded by

amemoromneia
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 78

Data Structures Into Java Ucb Cs61b Textbook

Itebooks download

https://ebookbell.com/product/data-structures-into-java-ucb-
cs61b-textbook-itebooks-23836184

Explore and download more ebooks at ebookbell.com


Here are some recommended products that we believe you will be
interested in. You can click the link to download.

Serialization And Persistent Objects Turning Data Structures Into


Efficient Databases 1st Edition Jiri Soukup

https://ebookbell.com/product/serialization-and-persistent-objects-
turning-data-structures-into-efficient-databases-1st-edition-jiri-
soukup-4697496

Data Structure Simplified Implementation Using C Jitendra Singh

https://ebookbell.com/product/data-structure-simplified-
implementation-using-c-jitendra-singh-11074350

Data Structures The Fun Way An Amusing Adventure With Coffeefilled


Examples 1st Edition Jeremy Kubica

https://ebookbell.com/product/data-structures-the-fun-way-an-amusing-
adventure-with-coffeefilled-examples-1st-edition-jeremy-
kubica-44883122

Data Structures With C Using Stl 2nd Edition William H Ford William R
Topp

https://ebookbell.com/product/data-structures-with-c-using-stl-2nd-
edition-william-h-ford-william-r-topp-46638186
Data Structures And Algorithms With Javascript Michael Mcmillan

https://ebookbell.com/product/data-structures-and-algorithms-with-
javascript-michael-mcmillan-47308410

Data Structures And Algorithms In C 4th Edition Adam Drozdek

https://ebookbell.com/product/data-structures-and-algorithms-in-c-4th-
edition-adam-drozdek-48951104

Data Structures And Algorithm Analysis In C 3rd Edition Clifford A


Shaffer

https://ebookbell.com/product/data-structures-and-algorithm-analysis-
in-c-3rd-edition-clifford-a-shaffer-49161310

Data Structures And Program Design Using Java Layla S Mayboudi

https://ebookbell.com/product/data-structures-and-program-design-
using-java-layla-s-mayboudi-49422932

Data Structures And Algorithms In Swift Implement Stacks Queues


Dictionaries And Lists In Your Apps 1st Edition Elshad Karimov

https://ebookbell.com/product/data-structures-and-algorithms-in-swift-
implement-stacks-queues-dictionaries-and-lists-in-your-apps-1st-
edition-elshad-karimov-50195754
CS 61B Reader

Data Structures (Into Java)


(Seventh Edition)

Paul N. Hilfinger
University of California, Berkeley
Acknowledgments. Thanks to the following individuals for finding many of the
errors in earlier editions: Dan Bonachea, Michael Clancy, Dennis Hall, Joseph Hui,
Yina Jin, Zhi Lin, Amy Mok, Barath Raghavan Yingssu Tsai, Emily Watt, Howard
Wu, and Zihan Zhou.

Copyright c 2000, 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2009, 2011, 2012,
2013, 2014 by Paul N. Hilfinger. All rights reserved.
Contents

1 Algorithmic Complexity 7
1.1 Asymptotic complexity analysis and order notation . . . . . . . . . . 9
1.2 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.2.1 Demonstrating “Big-Ohness” . . . . . . . . . . . . . . . . . . 13
1.3 Applications to Algorithm Analysis . . . . . . . . . . . . . . . . . . . 13
1.3.1 Linear search . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.3.2 Quadratic example . . . . . . . . . . . . . . . . . . . . . . . . 15
1.3.3 Explosive example . . . . . . . . . . . . . . . . . . . . . . . . 15
1.3.4 Divide and conquer . . . . . . . . . . . . . . . . . . . . . . . . 16
1.3.5 Divide and fight to a standstill . . . . . . . . . . . . . . . . . 17
1.4 Amortization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.5 Complexity of Problems . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.6 Some Properties of Logarithms . . . . . . . . . . . . . . . . . . . . . 21
1.7 A Note on Notation . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

2 Data Types in the Abstract 23


2.1 Iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.1.1 The Iterator Interface . . . . . . . . . . . . . . . . . . . . . . 24
2.1.2 The ListIterator Interface . . . . . . . . . . . . . . . . . . . . 26
2.2 The Java Collection Abstractions . . . . . . . . . . . . . . . . . . . . 26
2.2.1 The Collection Interface . . . . . . . . . . . . . . . . . . . . . 26
2.2.2 The Set Interface . . . . . . . . . . . . . . . . . . . . . . . . . 33
2.2.3 The List Interface . . . . . . . . . . . . . . . . . . . . . . . . 33
2.2.4 Ordered Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
2.3 The Java Map Abstractions . . . . . . . . . . . . . . . . . . . . . . . 39
2.3.1 The Map Interface . . . . . . . . . . . . . . . . . . . . . . . . 41
2.3.2 The SortedMap Interface . . . . . . . . . . . . . . . . . . . . 41
2.4 An Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
2.5 Managing Partial Implementations: Design Options . . . . . . . . . 46

3 Meeting a Specification 49
3.1 Doing it from Scratch . . . . . . . . . . . . . . . . . . . . . . . . . . 52
3.2 The AbstractCollection Class . . . . . . . . . . . . . . . . . . . . . . 52
3.3 Implementing the List Interface . . . . . . . . . . . . . . . . . . . . . 53
3.3.1 The AbstractList Class . . . . . . . . . . . . . . . . . . . . . 53

3
4 CONTENTS

3.3.2 The AbstractSequentialList Class . . . . . . . . . . . . . . . . 56


3.4 The AbstractMap Class . . . . . . . . . . . . . . . . . . . . . . . . . 60
3.5 Performance Predictions . . . . . . . . . . . . . . . . . . . . . . . . . 60

4 Sequences and Their Implementations 65


4.1 Array Representation of the List Interface . . . . . . . . . . . . . . . 65
4.2 Linking in Sequential Structures . . . . . . . . . . . . . . . . . . . . 69
4.2.1 Singly Linked Lists . . . . . . . . . . . . . . . . . . . . . . . . 69
4.2.2 Sentinels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
4.2.3 Doubly Linked Lists . . . . . . . . . . . . . . . . . . . . . . . 70
4.3 Linked Implementation of the List Interface . . . . . . . . . . . . . . 72
4.4 Specialized Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
4.4.1 Stacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
4.4.2 FIFO and Double-Ended Queues . . . . . . . . . . . . . . . . 81
4.5 Stack, Queue, and Deque Implementation . . . . . . . . . . . . . . . 81

5 Trees 91
5.1 Expression trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
5.2 Basic tree primitives . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
5.3 Representing trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
5.3.1 Root-down pointer-based binary trees . . . . . . . . . . . . . 96
5.3.2 Root-down pointer-based ordered trees . . . . . . . . . . . . . 96
5.3.3 Leaf-up representation . . . . . . . . . . . . . . . . . . . . . . 97
5.3.4 Array representations of complete trees . . . . . . . . . . . . 98
5.3.5 Alternative representations of empty trees . . . . . . . . . . . 99
5.4 Tree traversals. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
5.4.1 Generalized visitation . . . . . . . . . . . . . . . . . . . . . . 101
5.4.2 Visiting empty trees . . . . . . . . . . . . . . . . . . . . . . . 103
5.4.3 Iterators on trees . . . . . . . . . . . . . . . . . . . . . . . . . 104

6 Search Trees 107


6.1 Operations on a BST . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
6.1.1 Searching a BST . . . . . . . . . . . . . . . . . . . . . . . . . 109
6.1.2 Inserting into a BST . . . . . . . . . . . . . . . . . . . . . . . 109
6.1.3 Deleting items from a BST. . . . . . . . . . . . . . . . . . . . 111
6.1.4 Operations with parent pointers . . . . . . . . . . . . . . . . 113
6.1.5 Degeneracy strikes . . . . . . . . . . . . . . . . . . . . . . . . 113
6.2 Implementing the SortedSet interface . . . . . . . . . . . . . . . . . . 113
6.3 Orthogonal Range Queries . . . . . . . . . . . . . . . . . . . . . . . . 115
6.4 Priority queues and heaps . . . . . . . . . . . . . . . . . . . . . . . . 119
6.4.1 Heapify Time . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
6.5 Game Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
6.5.1 Alpha-beta pruning . . . . . . . . . . . . . . . . . . . . . . . 129
6.5.2 A game-tree search algorithm . . . . . . . . . . . . . . . . . . 131
CONTENTS 5

7 Hashing 133
7.1 Chaining . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
7.2 Open-address hashing . . . . . . . . . . . . . . . . . . . . . . . . . . 134
7.3 The hash function . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
7.4 Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140

8 Sorting and Selecting 141


8.1 Basic concepts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
8.2 A Little Notation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
8.3 Insertion sorting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
8.4 Shell’s sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
8.5 Distribution counting . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
8.6 Selection sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
8.7 Exchange sorting: Quicksort . . . . . . . . . . . . . . . . . . . . . . . 151
8.8 Merge sorting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
8.8.1 Complexity . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
8.9 Speed of comparison-based sorting . . . . . . . . . . . . . . . . . . . 155
8.10 Radix sorting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
8.10.1 LSD-first radix sorting . . . . . . . . . . . . . . . . . . . . . . 159
8.10.2 MSD-first radix sorting . . . . . . . . . . . . . . . . . . . . . 159
8.11 Using the library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
8.12 Selection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162

9 Balanced Searching 165


9.1 Balance by Construction: B-Trees . . . . . . . . . . . . . . . . . . . 165
9.1.1 B-tree Insertion . . . . . . . . . . . . . . . . . . . . . . . . . . 167
9.1.2 B-tree deletion . . . . . . . . . . . . . . . . . . . . . . . . . . 167
9.1.3 Red-Black Trees: Binary Search Trees as (2,4) Trees . . . . . 172
9.2 Tries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
9.2.1 Tries: basic properties and algorithms . . . . . . . . . . . . . 174
9.2.2 Tries: Representation . . . . . . . . . . . . . . . . . . . . . . 179
9.2.3 Table compression . . . . . . . . . . . . . . . . . . . . . . . . 180
9.3 Restoring Balance by Rotation . . . . . . . . . . . . . . . . . . . . . 181
9.3.1 AVL Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
9.4 Splay Trees . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
9.4.1 Analyzing splay trees . . . . . . . . . . . . . . . . . . . . . . 186
9.5 Skip Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189

10 Concurrency and Synchronization 203


10.1 Synchronized Data Structures . . . . . . . . . . . . . . . . . . . . . . 204
10.2 Monitors and Orderly Communication . . . . . . . . . . . . . . . . . 205
10.3 Message Passing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
6 CONTENTS

11 Pseudo-Random Sequences 209


11.1 Linear congruential generators . . . . . . . . . . . . . . . . . . . . . 209
11.2 Additive Generators . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
11.3 Other distributions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
11.3.1 Changing the range . . . . . . . . . . . . . . . . . . . . . . . 212
11.3.2 Non-uniform distributions . . . . . . . . . . . . . . . . . . . . 213
11.3.3 Finite distributions . . . . . . . . . . . . . . . . . . . . . . . . 214
11.4 Random permutations and combinations . . . . . . . . . . . . . . . . 217

12 Graphs 219
12.1 A Programmer’s Specification . . . . . . . . . . . . . . . . . . . . . . 220
12.2 Representing graphs . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
12.2.1 Adjacency Lists . . . . . . . . . . . . . . . . . . . . . . . . . . 221
12.2.2 Edge sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
12.2.3 Adjacency matrices . . . . . . . . . . . . . . . . . . . . . . . . 227
12.3 Graph Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
12.3.1 Marking. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
12.3.2 A general traversal schema. . . . . . . . . . . . . . . . . . . . 229
12.3.3 Generic depth-first and breadth-first traversal . . . . . . . . . 230
12.3.4 Topological sorting. . . . . . . . . . . . . . . . . . . . . . . . 230
12.3.5 Minimum spanning trees . . . . . . . . . . . . . . . . . . . . . 231
12.3.6 Single-source shortest paths . . . . . . . . . . . . . . . . . . . 234
12.3.7 A* search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
12.3.8 Kruskal’s algorithm for MST . . . . . . . . . . . . . . . . . . 239
Chapter 1

Algorithmic Complexity

The obvious way to answer to the question “How fast does such-and-such a program
run?” is to use something like the UNIX time command to find out directly. There
are various possible objections to this easy answer. The time required by a program
is a function of the input, so presumably we have to time several instances of the
command and extrapolate the result. Some programs, however, behave fine for most
inputs, but sometimes take a very long time; how do we report (indeed, how can we
be sure to notice) such anomalies? What do we do about all the inputs for which we
have no measurements? How do we validly apply results gathered on one machine
to another machine?
The trouble with measuring raw time is that the information is precise, but
limited: the time for this input on this configuration of this machine. On a different
machine whose instructions take different absolute or relative times, the numbers
don’t necessarily apply. Indeed, suppose we compare two different programs for
doing the same thing on the same inputs and the same machine. Program A may
turn out faster than program B. This does not imply, however, that program A will
be faster than B when they are run on some other input, or on the same input, but
some other machine.
In mathematese, we might say that a raw time is the value of a function
Cr (I, P, M ) for some particular input I, some program P , and some “platform”
M (platform here is a catchall term for a combination of machine, operating sys-
tem, compiler, and runtime library support). I’ve invented the function Cr here to
mean “the raw cost of. . . .” We can make the figure a little more informative by
summarizing over all inputs of a particular size

Cw (N, P, M ) = max Cr (I, P, M ),


|I|=N

where |I| denotes the “size” of input I. How one defines the size depends on the
problem: if I is an array to be sorted, for example, |I| might denote I.length. We
say that Cw measures worst-case time of a program. Of course, since the number
of inputs of a given size could be very large (the number of arrays of 5 ints, for
example, is 2160 > 1048 ), we can’t directly measure Cw , but we can perhaps estimate
it with the help of some analysis of P . By knowing worst-case times, we can make

7
8 CHAPTER 1. ALGORITHMIC COMPLEXITY

conservative statements about the running time of a program: if the worst-case


time for input of size N is T , then we are guaranteed that P will consume no more
than time T for any input of size N .
But of course, it always possible that our program will work fine on most inputs,
but take a really long time on one or two (unlikely) inputs. In such cases, we might
claim that Cw is too harsh a summary measure, and we should really look at an
average time. Assuming all values of the input, I, are equally likely, the average
time is X
Cr (I, P, M )
|I|=N
Ca (N, P, M ) =
N
Fair this may be, but it is usually very hard to compute. In this course, there-
fore, I will say very little about average cases, leaving that to your next course on
algorithms.
We’ve summarized over inputs by considering worst-case times; now let’s con-
sider how we can summarize over machines. Just as summarizing over inputs
required that we give up some information—namely, performance on particular
inputs—so summarizing over machines requires that we give up information on
precise performance on particular machines. Suppose that two different models of
computer are running (different translations of) the same program, performing the
same steps in the same order. Although they run at different speeds, and possibly
execute different numbers of instructions, the speeds at which they perform any
particular step tend to differ by some constant factor. By taking the largest and
smallest of these constant factors, we can put bounds around the difference in their
overall execution times. (The argument is not really this simple, but for our pur-
poses here, it will suffice.) That is, the timings of the same program on any two
platforms will tend to differ by no more than some constant factor over all possible
inputs. If we can nail down the timing of a program on one platform, we can use it
for all others, and our results will “only be off by a constant factor.”
But of course, 1000 is a constant factor, and you would not normally be in-
sensitive to the fact that Brand X program is 1000 times slower than Brand Y.
There is, however, an important case in which this sort of characterization is use-
ful: namely, when we are trying to determine or compare the performance of algo-
rithms—idealized procedures for performing some task. The distinction between al-
gorithm and program (a concrete, executable procedure) is somewhat vague. Most
higher-level programming languages allow one to write programs that look very
much like the algorithms they are supposed to implement. The distinction lies in
the level of detail. A procedure that is cast in terms of operations on “sets,” with
no specific implementation given for these sets, probably qualifies as an algorithm.
When talking about idealized procedures, it doesn’t make a great deal of sense to
talk about the number of seconds they take to execute. Rather, we are interested
in what I might call the shape of an algorithm’s behavior: such questions as “If we
double the size of the input, what happens to the execution time?” Given that kind
of question, the particular units of time (or space) used to measure the performance
of an algorithm are unimportant—constant factors don’t matter.
1.1. ASYMPTOTIC COMPLEXITY ANALYSIS AND ORDER NOTATION 9

If we only care about characterizing the speed of an algorithm to within a


constant factor, other simplifications are possible. We need no longer worry about
the timing of each little statement in the algorithm, but can measure time using
any convenient “marker step.” For example, to do decimal multiplication in the
standard way, you multiply each digit of the multiplicand by each digit of the
multiplier, and perform roughly one one-digit addition with carry for each of these
one-digit multiplications. Counting just the one-digit multiplications, therefore, will
give you the time within a constant factor, and these multiplications are very easy
to count (the product of the numbers of digits in the operands).
Another characteristic assumption in the study of algorithmic complexity (i.e.,
the time or memory consumption of an algorithm) is that we are interested in typical
behavior of an idealized program over the entire set of possible inputs. Idealized
programs, of course, being ideal, can operate on inputs of any possible size, and most
“possible sizes” in the ideal world of mathematics are extremely large. Therefore, in
this kind of analysis, it is traditional not to be interested in the fact that a particular
algorithm does very well for small inputs, but rather to consider its behavior “in
the limit” as input gets very large. For example, suppose that one wanted to
analyze algorithms for computing π to any given number of decimal places. I can
make any algorithm look good for inputs up to, say, 1,000,000 by simply storing
the first 1,000,000 digits of π in an array and using that to supply the answer
when 1,000,000 or fewer digits are requested. If you paid any attention to how my
program performed for inputs up to 1,000,000, you could be seriously misled as to
the cleverness of my algorithm. Therefore, when studying algorithms, we look at
their asymptotic behavior —how they behave as they input size goes to infinity.
The result of all these considerations is that in considering the time complexity
of algorithms, we may choose any particular machine and count any convenient
marker step, and we try to find characterizations that are true asymptotically—out
to infinity. This implies that our typical complexity measure for algorithms will
have the form Cw (N, A)—meaning “the worst-case time over all inputs of size N
of algorithm A (in some units).” Since the algorithm will be understood in any
particular discussion, we will usually just write Cw (N ) or something similar. So the
first thing we need to describe algorithmic complexity is a way to characterize the
asymptotic behavior of functions.

1.1 Asymptotic complexity analysis and order notation


As it happens, there is a convenient notational tool—known collectively as order
notation for “order of growth”—for describing the asymptotic behavior of functions.
It may be (and is) used for any kind of integer- or real-valued function—not just
complexity functions. You’ve probably seen it used in calculus courses, for example.
We write
f (n) ∈ O(g(n))

(aloud, this is “f (n) is in big-Oh of g(n)”) to mean that the function f is eventually
10 CHAPTER 1. ALGORITHMIC COMPLEXITY

2|g(n)|

n=M

|f (n)|
|h′ (n)|

f 0.5|g(n)| |g′ (n)|

|h(n)|
n n

(a) (b)

Figure 1.1: Illustration of big-Oh notation. In graph (a), we see that |f (n)| ≤ 2|g(n)|
for n > M , so that f (n) ∈ O(g(n)) (with K = 2). Likewise, h(n) ∈ O(g(n)),
illustrating the g can be a very over-cautious bound. The function f is also bounded
below by both g (with, for example, K = 0.5 and M any value larger than 0) and by
h. That is, f (n) ∈ Ω(g(n)) and f (n) ∈ Ω(h(n)). Because f is bounded above and
below by multiples of g, we say f (n) ∈ Θ(g(n)). On the other hand, h(n) 6∈ Ω(g(n)).
In fact, assuming that g continues to grow as shown and h to shrink, h(n) ∈ o(g(n)).
Graph (b) shows that o(·) is not simply the set complement of Ω(·); h′ (n) 6∈ Ω(g ′ (n)),
but h′ (n) 6∈ o(g ′ (n)), either.

bounded by some multiple of |g(n)|. More precisely, f (n) ∈ O(g(n)) iff

|f (n)| ≤ K · |g(n)|, for all n > M,

for some constants K > 0 and M . That is, O(g(n)) is the set of functions that
“grow no more quickly than” |g(n)| does as n gets sufficiently large. Somewhat
confusingly, f (n) here does not mean “the result of applying f to n,” as it usually
does. Rather, it is to be interpreted as the body of a function whose parameter is n.
Thus, we often write things like O(n2 ) to mean “the set of all functions that grow
no more quickly than the square of their argument1 .” Figure 1.1a gives an intuitive
idea of what it means to be in O(g(n)).
Saying that f (n) ∈ O(g(n)) gives us only an upper bound on the behavior of f .
For example, the function h in Figure 1.1a—and for that matter, the function that
1
If we wanted to be formally correct, we’d use lambda notation to represent functions (such as
Scheme uses) and write instead O(λn. n2 ), but I’m sure you can see how such a degree of rigor
would become tedious very soon.
1.2. EXAMPLES 11

is 0 everywhere—are both in O(g(n)), but certainly don’t grow like g. Accordingly,


we define f (n) ∈ Ω(g(n)) iff for all n > M, |f (n)| ≥ K|g(n)| for n > M , for
some constants K > 0 and M . That is, Ω(g(n)) is the set of all functions that
“grow at least as fast as” g beyond some point. A little algebra suffices to show the
relationship between O(·) and Ω(·):
|f (n)| ≥ K|g(n)| ≡ |g(n)| ≤ (1/K) · |f (n)|
so
f (n) ∈ Ω(g(n)) ⇐⇒ g(n) ∈ O(f (n))
Because of our cavalier treatment of constant factors, it is possible for a function
f (n) to be bounded both above and below by another function g(n): f (n) ∈ O(g(n))
and f (n) ∈ Ω(g(n)). For brevity, we write f (n) ∈ Θ(g(n)), so that Θ(g(n)) =
O(g(n)) ∩ Ω(g(n)).
Just because we know that f (n) ∈ O(g(n)), we don’t necessarily know that
f (n) gets much smaller than g(n), or even (as illustrated in Figure 1.1a) that it
is ever smaller than g(n). We occasionally do want to say something like “h(n)
becomes negligible compared to g(n).” You sometimes see the notation h(n) ≪ g(n),
meaning “h(n) is much smaller than g(n),” but this could apply to a situation where
h(n) = 0.001g(n). Not being interested in mere constant factors like this, we need
something stronger. A traditional notation is “little-oh,” defined as follows.
h(n) ∈ o(g(n)) ⇐⇒ lim h(n)/g(n) = 0.
n→∞

It’s easy to see that if h(n) ∈ o(g(n)), then h(n) 6∈ Ω(g(n)); no constant K can
work in the definition of Ω(·). It is not the case, however, that all functions that
are outside of Ω(g(n)) must be in o(g(n)), as illustrated in Figure 1.1b.

1.2 Examples
You may have seen the big-Oh notation already in calculus courses. For example,
Taylor’s theorem tells us2 that (under appropriate conditions)
xn [n] X xk
f (x) = f (y) + f [k](0)
|n! {z } 0≤k<n k!
error term | {z }
approximation

for some y between 0 and x, where f [k] represents the kth derivative of f . Therefore,
if g(x) represents the maximum absolute value of f [n] between 0 and x, then we
could also write the error term as
X xk
f (x) − f [k](0)
0≤k<n
k!
xn
∈ O( g(x)) = O(xn g(x))
n!
2
Yes, I know it’s a Maclaurin series here, but it’s still Taylor’s theorem.
12 CHAPTER 1. ALGORITHMIC COMPLEXITY

f (n) Is contained in Is not contained in



1, 1 + 1/n O(10000), O( n), O(n), O(1/n), O(e−n )
O(n2 ), O(lg n), O(1 − 1/n)

Ω(1), Ω(1/n), Ω(1 − 1/n) Ω(n), Ω( n), Ω(lg n), Ω(n2 )

Θ(1), Θ(1 − 1/n) Θ(n), Θ(n2 ), Θ(lg n), Θ( n)

o(n), o( n), o(n2 ) o(100 + e−n ), o(1)

logk n, ⌊logk n⌋, O(n), O(nǫ ), O( n), O(logk′ n) O(1)
⌈logk n⌉ O(⌊logk′ n⌋), O(n/ logk′ n)

Ω(1), Ω(log k′ n), Ω(⌊logk′ n⌋) Ω(nǫ ), Ω( n)
Θ(logk′ n), Θ(⌊logk′ n⌋), Θ(log2k′ n), Θ(logk′ n + n)
Θ(logk′ n + 1000)
o(n), o(nǫ )
n, 100n + 15 O(.0005n − 1000), O(n2 ), O(10000), O(lg n),

O(n lg n) O(n − n2 /10000), O( n)

Ω(50n + 1000), Ω( n), Ω(n2 ), Ω(n lg n)
Ω(n + lg n), Ω(1/n)
Θ(50n + 100), Θ(n + lg n) Θ(n2 ), Θ(1)
o(n3 ), o(n lg n) o(1000n), o(n2 sin n)
n2 , 10n2 + n O(n2 + 2n + 12), O(n3 ), O(n), O(n lg n), O(1)

O(n2 + n) o(50n2 + 1000)
Ω(n2 + 2n + 12), Ω(n), Ω(1), Ω(n3 ), Ω(n2 lg n)
Ω(n lg n)
Θ(n2 + 2n + 12), Θ(n2 + lg n) Θ(n), Θ(n · sin n)
np O(pn ), O(np + 1000np−1 ) O(np−1 ), O(1)
Ω(np−ǫ ), Ω(np+ǫ), Ω(pn )
Θ(np + np−ǫ ) Θ(np+ǫ), Θ(1)
o(pn ), o(n!), o(np+ǫ ) o((n + k)p )
2n , 2n + np O(n!), O(2n − np ), O(3n ), O(2n+p ) O(np ), O((2 − δ)n )
Ω(np ), Ω((2 − δ)n ), Ω((2 + ǫ)n ), Ω(n!)
Θ(2n + np ) Θ(22n )
o(n2n ), o(n!), o(2n+ǫ ), o((2 + ǫ)n )

Table 1.1: Some examples of order relations. In the above, names other than n
represent constants, with ǫ > 0, 0 ≤ δ ≤ 1, p > 1, and k, k ′ > 1.
1.3. APPLICATIONS TO ALGORITHM ANALYSIS 13

for fixed n. This is, of course, a much weaker statement than the original (it allows
the error to be much bigger than it really is).
You’ll often seen statements like this written with a little algebraic manipulation:

X xk
f (x) ∈ f [k](0) + O(xn g(x)).
0≤k<n
k!

To make sense of this sort of statement, we define addition (and so on) between
functions (a, b, etc.) and sets of functions (A, B, etc.):

a + b = λx.a(x) + b(x)
A + B = {a + b | a ∈ A, b ∈ B}
A + b = {a + b | a ∈ A}
a + B = {a + b | b ∈ B}

Similar definitions apply for multiplication, subtraction, and division. So if a is x

and b is lg x, then a + b is a function whose value is x + lg x for every (postive)
x. O(a(x)) + O(b(x)) (or just O(a) + O(b)) is then the set of functions you can get

by adding a member of O( x) to a member of O(lg x). For example, O(a) contains
√ √
5 x+3 and O(b) contains 0.01 lg x−16, so O(a)+O(b) contains 5 x+0.01 lg k−13,
among many others.

1.2.1 Demonstrating “Big-Ohness”



Suppose we want to show that 5n2 + 10 n ∈ O(n2 ). That is, we need to find K
and M so that

|5n2 + 10 n| ≤ |Kn2 |, for n > M .
√ √
We realize that n2 grows faster than n, so it eventually gets bigger than 10 n as
well. So perhaps we can take K = 6 and find M > 0 such that

5n2 + 10 n ≤ 5n2 + n2 = 6n2

To get 10 n < n2 , we need 10 < n3/2 , or n > 102/3 ≈ 4.7. So choosing M > 5
certainly works.

1.3 Applications to Algorithm Analysis


In this course, we will be usually deal with integer-valued functions arising from
measuring the complexity of algorithms. Table 1.1 gives a few common examples
of orders that we deal with and their containment relations, and the sections below
give examples of simple algorithmic analyses that use them.
14 CHAPTER 1. ALGORITHMIC COMPLEXITY

1.3.1 Linear search


Let’s apply all of this to a particular program. Here’s a tail-recursive linear search
for seeing if a particular value is in a sorted array:
/** True iff X is one of A[k]...A[A.length-1].
* Assumes A is increasing, k>= 0. */
static boolean isIn (int[] A, int k, int X) {
if (k >= A.length)
return false;
else if (A[k] > X)
return false;
else if (A[k] == X)
return true;
else
return isIn (A, k+1, X);
}

This is essentially a loop. As a measure of its complexity, let’s define CisIn (N )


as the maximum number of instructions it executes for a call with k = 0 and
A.length= N . By inspection, you can see that such a call will execute the first if
test up to N + 1 times, the second and third up to N times, and the tail-recursive
call on isIn up to N times. With one compiler3 , each recursive call of isIn executes
at most 14 instructions before returning or tail-recursively calling isIn. The initial
call executes 18. That gives a total of at most 14N + 18 instructions. If instead we
count the number of comparisons k>=A.length, we get at most N + 1. If we count
the number of comparisons against X or the number of fetches of A[0], we get at
most 2N . We could therefore say that the function giving the largest amount of
time required to process an input of size N is either in O(14N + 18), O(N + 1),
or O(2N ). However, these are all the same set, and in fact all are equal to O(N ).
Therefore, we may throw away all those messy integers and describe CisIn (N ) as
being in O(N ), thus illustrating the simplifying power of ignoring constant factors.
This bound is a worst-case time. For all arguments in which X<=A[0], the isIn
function runs in constant time. That time bound—the best-case bound—is seldom
very useful, especially when it applies to so atypical an input.
Giving an O(·) bound to CisIn (N ) doesn’t tell us that isIn must take time
proportional to N even in the worst case, only that it takes no more. In this
particular case, however, the argument used above shows that the worst case is, in
fact, at least proportional to N , so that we may also say that CisIn (N ) ∈ Ω(N ).
Putting the two results together, CisIn (N ) ∈ Θ(N ).
In general, then, asymptotic analysis of the space or time required for a given
algorithm involves the following.

• Deciding on an appropriate measure for the size of an input (e.g., length of


an array or a list).
3
a version of gcc with the -O option, generating SPARC code for a Sun Sparcstation IPC
workstation.
1.3. APPLICATIONS TO ALGORITHM ANALYSIS 15

• Choosing a representative quantity to measure—one that is proportional to


the “real” space or time required.

• Coming up with one or more functions that bound the quantity we’ve decided
to measure, usually in the worst case.

• Possibly summarizing these functions by giving O(·), Ω(·), or Θ(·) character-


izations of them.

1.3.2 Quadratic example


Here is a bit of code for sorting integers:
static void sort (int[] A) {
for (int i = 1; i < A.length; i += 1) {
int x = A[i];
int j;
for (j = i; j > 0 && x < A[j-1]; j -= 1)
A[j] = A[j-1];
A[j] = x;
}
}

If we define Csort (N ) as the worst-case number of times the comparison x < A[j-1]
is executed for N = A.length, we see that for each value of i from 1 to A.length-1,
the program executes the comparison in the inner loop (on j) at most i times.
Therefore,

Csort (N ) = 1 + 2 + . . . + N − 1
= N (N − 1)/2
∈ Θ(N 2 )

This is a common pattern for nested loops.

1.3.3 Explosive example


Consider a function with the following form.
static int boom (int M, int X) {
if (M == 0)
return H (X);
return boom (M-1, Q(X))
+ boom (M-1, R(X));
}

and suppose we want to compute Cboom (M )—the number of times Q is called for
a given M in the worst case. If M = 0, this is 0. If M > 0, then Q gets executed
once in computing the argument of the first recursive call, and then it gets executed
however many times the two inner calls of boom with arguments of M − 1 execute
16 CHAPTER 1. ALGORITHMIC COMPLEXITY

it. In other words,


Cboom (0) = 0
Cboom (i) = 2Cboom (i − 1) + 1
A little mathematical massage:
Cboom (M ) = 2Cboom (M − 1) + 1,
for M ≥ 1
= 2(2Cboom (M − 2) + 1) + 1,
for M ≥ 2
..
.
= 2(· · · (2 ·0 + 1) + 1) · · · + 1
| {z } | {z }
M M
X
= 2j
0≤j≤M −1

= 2M − 1
and so Cboom (M ) ∈ Θ(2M ).

1.3.4 Divide and conquer


Things become more interesting when the recursive calls decrease the size of pa-
rameters by a multiplicative rather than an additive factor. Consider, for example,
binary search.
/** Returns true iff X is one of
* A[L]...A[U]. Assumes A increasing,
* L>=0, U-L < A.length. */
static boolean isInB (int[] A, int L, int U, int X) {
if (L > U)
return false;
else {
int m = (L+U)/2;
if (A[m] == X)
return true;
else if (A[m] > X)
return isInB (A, L, m-1, X);
else
return isInB (A, m+1, U, X);
}
}
The worst-case time here depends on the number of elements of A under consid-
eration, U − L + 1, which we’ll call N . Let’s use the number of times the first line
is executed as the cost, since if the rest of the body is executed, the first line also
had to have been executed4 . If N > 1, the cost of executing isInB is 1 comparison
4
For those of you seeking arcane knowledge, we say that the test L>U dominates all other
statements.
1.3. APPLICATIONS TO ALGORITHM ANALYSIS 17

of L and U followed by the cost of executing isInB either with ⌊(N − 1)/2⌋ or with
⌈(N − 1)/2⌉ as the new value of N 5 . Either quantity is no more than ⌈(N − 1)/2⌉.
If N ≤ 1, there are two comparisons against N in the worst case.
Therefore, the following recurrence describes the cost, CisInB (i), of executing
this function when U − L + 1 = i.
CisInB (1) = 2
CisInB (i) = 1 + CisInB (⌈(i − 1)/2⌉), i > 1.
This is a bit hard to deal with, so let’s again make the reasonable assumption that
the value of the cost function, whatever it is, must increase as N increases. Then

we can compute a cost function, CisInB that is slightly larger than CisInB , but
easier to compute.

CisInB (1) = 2
′ ′
CisInB (i) = 1 + CisInB (i/2), i > 1 a power of 2.
This is a slight over-estimate of CisInB , but that still allows us to compute upper

bounds. Furthermore, CisInB is defined only on powers of two, but since isInB’s
cost increases as N increases, we can still bound CisInB (N ) conservatively by

computing CisInB of the next higher power of 2. Again with the massage:
′ ′
CisInB (i) = 1 + CisInB (i/2), i > 1 a power of 2.

= 1 + 1 + CisInB (i/4), i > 2 a power of 2.
..
.
· · + 1} +2
= |1 + ·{z
lg N

The quantity lg N is the logarithm of N base 2, or roughly “the number of times one
can divide N by 2 before reaching 1.” In summary, we can say CisIn (N ) ∈ O(lg N ).
Similarly, one can in fact derive that CisIn (N ) ∈ Θ(lg N ).

1.3.5 Divide and fight to a standstill


Consider now a subprogram that contains two recursive calls.

static void mung (int[] A, L, U) {


if (L < U) {
int m = (L+U)/2;
mung (A, L, m);
mung (A, m+1, U);
}
}

5
The notation ⌊x⌋ means the result of rounding x down (toward −∞) to an integer, and ⌈x⌉
means the result of rounding x up to an integer.
18 CHAPTER 1. ALGORITHMIC COMPLEXITY

We can approximate the arguments of both of the internal calls by N/2 as before,
ending up with the following approximation, Cmung (N ), to the cost of calling mung
with argument N = U − L + 1 (we are counting the number of times the test in the
first line executes).

Cmung (1) = 1
Cmung (i) = 1 + 2Cmung (i/2), i > 1 a power of 2.

So,

Cmung (N ) = 1 + 2(1 + 2Cmung (N/4)), N > 2 a power of 2.


..
.
= 1 + 2 + 4 + . . . + N/2 + N · 3

This is a sum of a geometric series (1 + r + r 2 + · · · + r m ), with a little extra added


on. The general rule for geometric series is
X
r k = (r m+1 − 1)/(r − 1)
0≤k≤m

so, taking r = 2,
Cmung (N ) = 4N − 1
or Cmung (N ) ∈ Θ(N ).

1.4 Amortization
So far, we have considered the time spent by individual operations, or individual
calls on a certain function of interest. Sometimes, however, it is fruitful to consider
the cost of whole sequence of calls, especially when each call affects the cost of later
calls.
Consider, for example, a simple binary counter. Incrementing this counter causes
it to go through a sequence like this:

0 0 0 0 0
0 0 0 0 1
0 0 0 1 0
0 0 0 1 1
0 0 1 0 0
···
0 1 1 1 1
1 0 0 0 0
···

Each step consists of flipping a certain number of bits, converting bit b to 1 − b.


More precisely, the algorithm for going from one step to another is
1.4. AMORTIZATION 19

Increment: Flip the bits of the counter from right to left, up to and including the
first 0-bit encountered (if any).

Clearly, if we are asked to give a worst-case bound on the cost of the increment
operation for an N -bit counter (in number of flips), we’d have to say that it is
Θ(N ): all the bits can be flipped. Using just that bound, we’d then have to say
that the cost of performing M increment operations is Θ(M · N ).
But the costs of consecutive increment operations are related. For example, if
one increment flips more than one bit, the next increment will always flip exactly
one (why?). In fact, if you consider the pattern of bit changes, you’ll see that the
units (rightmost) bit flips on every increment, the 2’s bit on every second increment,
the 4’s bit on every fourth increment, and in general, then 2k ’s bit on every (2k )th
increment. Therefore, over any sequence of M consecutive increments, starting at
0, there will be

M
|{z} + ⌊M/2⌋ + ⌊M/4⌋ + . . . + ⌊M/2n ⌋, where n = ⌊lg M ⌋
| {z } | {z } | {z }
unit’s flips 2’s flips 4’s flips 2n ’s
flips
2n−2 + . . . + 1} +(M − 2n )
= |2n + 2n−1 + {z
=2n+1 −1
n
= 2 −1+M
< 2M flips

In other words, this is the same result we would get if we performed M incre-
ments each of which had a worst-case cost of 2 flips, rather than N . We call 2 flips
the amortized cost of an increment. To amortize in the context of algorithms is to
treat the cost of each individual operation in a sequence as if it were spread out
among all the operations in the sequence6 . Any particular increment might take up
to N flips, but we treat that as N/M flips credited to each increment operation in
the sequence (and likewise count each increment that takes only one flip as 1/M flip
for each increment operation). The result is that we get a more realistic idea of how
much time the entire program will take; simply multiplying the ordinary worst-case
time by M gives us a very loose and pessimistic estimate. Nor is amortized cost the
same as average cost; it is a stronger measure. If a certain operation has a given
average cost, that leaves open the possibility that there is some unlikely sequence
of inputs that will make it look bad. A bound on amortized worst-case cost, on the
other hand, is guaranteed to hold regardless of input.
Another way to reach the same result uses what is called the potential method7 .The
idea here is that we associate with our data structure (our bit sequence in this case)
a non-negative potential that represents work we wish to spread out over several op-
erations. If ci represents the actual cost of the ith operation on our data structure,
6
The word amortize comes from an Old French word meaning “to death.” The original meaning
from which the computer-science usage comes (introduced by Sleator and Tarjan), is “to gradually
write off the initial cost of something.”
7
Also due to D. Sleator.
20 CHAPTER 1. ALGORITHMIC COMPLEXITY

we define the amortized cost of the ith operation, ai so that

ai = ci + Φi+1 − Φi , (1.1)

where Φi denotes the saved-up potential before the ith operation. That is, we give
ourselves the choice of increasing Φ a little on any given operation and charging this
increase against ai , causing ai > ci when Φ increases. Alternatively, we can also
decrease ai below ci by having an operation reduce Φ, in effect using up previously
saved increases. Assuming we start with Φ0 = 0, the total cost of n operations is
X X
ci ≤ (ai + Φi − Φi + 1) (1.2)
0≤i<n 0≤i<n
X
= ( ai ) + Φ 0 − Φ n
0≤i<n
X
= ( ai ) − Φ n
0≤i<n
X
≤ ai ,
0≤i<n

since we require that Φi ≥ 0. These ai therefore provide conservative estimates of


the cumulative cost of the operations at each point.
For example, with our bit-flipping example, we’ll define Φi as the total number
of 1-bits before the ith operation. The cost of the ith increment is always one plus
the number of 1-bits that flip back to 0, which, because of how we’ve defined it,
can never be more than Φi (which of course is never negative). So defining ai = 2
for every operation satisfies Equation 1.1, again proving that we can bound the
amortized cost of an increment by 2 bit-flips.
I fudged a bit here by assuming that our bit counter always starts at 0. If it
started instead at N0 > 0, and we stopped after a single increment, then the total
cost (in bit flips) could be as much as 1 + ⌊lg(N0 + 1)⌋. Since we want to insure
that the inequality 1.2 holds for any n, we’ll have to do some adjusting to handle
this case. A simple trick is to redefine Φ0 = 0, keep other values of the Φi the same
(the number of 1-bits before the ith operation, and finally define a0 = c0 + Φ1 . In
effect, we charge a0 with the start-up costs of our counting sequence. Of course, this
means a0 can be arbitrarily large, but that merely reflects reality; the remaining ai
are still constant.

1.5 Complexity of Problems


So far, I have discussed only the analysis of an algorithm’s complexity. An algo-
rithm, however, is just a particular way of solving some problem. We might therefore
consider asking for complexity bounds on the problem’s complexity. That is, can we
bound the complexity of the best possible algorithm? Obviously, if we have a partic-
ular algorithm and its time complexity is O(f (n)), where n is the size of the input,
then the complexity of the best possible algorithm must also be O(f (n)). We call
f (n), therefore, an upper bound on the (unknown) complexity of the best-possible
1.6. SOME PROPERTIES OF LOGARITHMS 21

algorithm. But this tells us nothing about whether the best-possible algorithm is
any faster than this—it puts no lower bound on the time required for the best al-
gorithm. For example, the worst-case time for isIn is Θ(N ). However, isInB is
much faster. Indeed, one can show that if the only knowledge the algorithm can
have is the result of comparisons between X and elements of the array, then isInB
has the best possible bound (it is optimal), so that the entire problem of finding an
element in an ordered array has worst-case time Θ(lg N ).
Putting an upper bound on the time required to perform some problem simply
involves finding an algorithm for the problem. By contrast, putting a good lower
bound on the required time is much harder. We essentially have to prove that no
algorithm can have a better execution time than our bound, regardless of how much
smarter the algorithm designer is than we are. Trivial lower bounds, of course, are
easy: every problem’s worst-case time is Ω(1), and the worst-case time of any prob-
lem whose answer depends on all the data is Ω(N ), assuming that one’s idealized
machine is at all realistic. Better lower bounds than those, however, require quite
a bit of work. All the better to keep our theoretical computer scientists employed.

1.6 Some Properties of Logarithms


Logarithms occur frequently in analyses of complexity, so it might be useful to review
a few facts about them. In most math courses, you encounter the natural logarithm,
ln x = loge x, but computer scientists tend to use the base-2 logarithm, lg x = log2 x,
and in general this is what I mean when I say “logarithm.” Of course, all logarithms
are related by a constant factor: since by definition aloga x = x = blogb x , it follows
that
loga x = loga blogb x = (loga b) logb x.
Their connection to the exponential dictates their familiar properties:

lg xy = lg x + lg y
lg x/y = lg x − lg y
lg xp = p lg x

In complexity arguments, we are often interested in inequalities. The logarithm


is a very slow-growing function:

lim lg x/xp = 0, for allp > 0.


x→∞

It is strictly increasing and strictly concave, meaning that its values lie above any line
segment joining points (x, lg x) and (z, lg z). To put it algebraically, if 0 < x < y < z,
then
y−x z−y
lgy > lg x + lg z.
z−x z−x
Therefore, if 0 < x + y < k, the value of lg x + lg y is maximized when x = y = k/2.
22 CHAPTER 1. ALGORITHMIC COMPLEXITY

1.7 A Note on Notation


Other authors use notation such as f (n) = O(n2 ) rather than f (n) ∈ O(n2 ). I don’t
because I consider it nonsensical. To justify the use of ‘=’, one either has to think
of f (n) as a set of functions (which it isn’t), or think of O(n2 ) as a single function
that differs with each separate appearance of O(n2 ) (which is bizarre). I can see no
disadvantages to using ‘∈’, which makes perfect sense, so that’s what I use.

Exercises
1.1. Demonstrate the following, or give counter-examples where indicated. Show-
ing that a certain O(·) formula is true means producing suitable K and M for
the definition at the beginning of §1.1. Hint: sometimes it is useful to take the
logarithms of two functions you are comparing.

a. O(max(|f0 (n)|, |f1 (n)|)) = O(f0 (n)) + O(f1 (n)).

b. If f (n) is a polynomial in n, then lg f (n) ∈ O(lg n).

c. O(f (n) + g(n)) = O(f (n)) + O(g(n)). This is a bit of trick question, really,
to make you look at the definitions carefully. Under what conditions is the
equation true?

d. There is a function f (x) > 0 such that f (x) 6∈ O(x) and f (x) 6∈ Ω(x).

e. There is a function f (x) such that f (0) = 0, f (1) = 100, f (2) = 10000, f (3) =
106 , but f (n) ∈ O(n).

f. n3 lg n ∈ O(n3.0001 ).

g. There is no constant k such that n3 lg n ∈ Θ(nk ).

1.2. Show each of the following false by exhibiting a counterexample. Assume


that f and g are any real-valued functions.

a. O(f (x) · s(x)) = o(f (x)), assuming limx→∞ s(x) = 0.

b. If f (x) ∈ O(x3 ) and g(x) ∈ O(x) then f (x)/g(x) ∈ O(x2 ).

c. If f (x) ∈ Ω(x) and g(x) ∈ Ω(x) then f (x) + g(x) ∈ Ω(x).

d. If f (100) = 1000 and f (1000) = 1000000 then f cannot be O(1).

e. If f1 (x), f2 (x), . . . are a bunch of functions that are all in Ω(1), then
X
F (N ) = |fi (x)| ∈ Ω(N ).
1≤i≤N
Chapter 2

Data Types in the Abstract

Most of the “classical” data structures covered in courses like this represent some
sort of collection of data. That is, they contain some set or multiset1 of values,
possibly with some ordering on them. Some of these collections of data are asso-
ciatively indexed; they are search structures that act like functions mapping certain
indexing values (keys) into other data (such as names into street addresses).
We can characterize the situation in the abstract by describing sets of opera-
tions that are supported by different data structures—that is by describing possible
abstract data types. From the point of view of a program that needs to represent
some kind of collection of data, this set of operations is all that one needs to know.
For each different abstract data type, there are typically several possible imple-
mentations. Which you choose depends on how much data your program has to
process, how fast it has to process the data, and what constraints it has on such
things as memory space. It is a dirty little secret of the trade that for quite a few
programs, it hardly matters what implementation you choose. Nevertheless, the
well-equipped programmer should be familiar with the available tools.
I expect that many of you will find this chapter frustrating, because it will talk
mostly about interfaces to data types without talking very much at all about the
implementations behind them. Get used to it. After all, the standard library behind
any widely used programming language is presented to you, the programmer, as a
set of interfaces—directions for what parameters to pass to each function and some
commentary, generally in English, about what it does. As a working programmer,
you will in turn spend much of your time producing modules that present the same
features to your clients.

2.1 Iterators
If we are to develop some general notion of a collection of data, there is at least one
generic question we’ll have to answer: how are we going to get items out of such a
collection? You are familiar with one kind of collection already—an array. Getting
1
A multiset or bag is like a set except that it may contain multiple copies of a particular data
value. That is, each member of a multiset has a multiplicity: a number of times that it appears.

23
24 CHAPTER 2. DATA TYPES IN THE ABSTRACT

items out of an array is easy; for example, to print the contents of an array, you
might write

for (int i = 0; i < A.length; i += 1)


System.out.print (A[i] + ", ");

Arrays have a natural notion of an nth element, so such loops are easy. But what
about other collections? Which is the “first penney” in a jar of penneys? Even if
we do arbitrarily choose to give every item in a collection a number, we will find
that the operation “fetch the nth item” may be expensive (consider lists of things
such as in Scheme).
The problem with attempting to impose a numbering on every collection of items
as way to extract them is that it forces the implementor of the collection to provide
a more specific tool than our problem may require. It’s a classic engineering trade-
off: satisfying one constraint (that one be able to fetch the nth item) may have
other costs (fetching all items one by one may become expensive).
So the problem is to provide the items in a collection without relying on indices,
or possibly without relying on order at all. Java provides two conventions, realized as
interfaces. The interface java.util.Iterator provides a way to access all the items
in a collection in some order. The interface java.util.ListIterator provides a
way to access items in a collection in some specific order, but without assigning an
index to each item2 .

2.1.1 The Iterator Interface


The Java library defines an interface, java.util.Iterator, shown in Figure 2.1,
that captures the general notion of “something that sequences through all items
in a collection” without any commitment to order. This is only a Java interface;
there is no implementation behind it. In the Java library, the standard way for a
class that represents a collection of data items to provide a way to sequence through
those items is to define a method such as

Iterator<SomeType> iterator () { ... }

that allocates and returns an Iterator (Figure 3.3 includes an example). Often the
actual type of this iterator will be hidden (even private); all the user of the class
needs to know is that the object returned by iterator provides the operations
hasNext and next (and sometimes remove). For example, a general way to print
all elements of a collection of Strings (analogous to the previous array printer)
might be

for (Iterator<String> i = C.iterator (); i.hasNext (); )


System.out.print (i.next () + " ");
2
The library also defines the interface java.util.Enumeration, which is essentially an older
version of the same idea. We won’t talk about that interface here, since the official position is that
Iterator is preferred for new programs.
2.1. ITERATORS 25

package java.util;
/** An object that delivers each item in some collection of items
* each of which is a T. */
public interface Iterator <T> {
/** True iff there are more items to deliver. */
boolean hasNext ();
/** Advance THIS to the next item and return it. */
T next ();
/** Remove the last item delivered by next() from the collection
* being iterated over. Optional operation: may throw
* UnsupportedOperationException if removal is not possible. */
void remove ();
}

Figure 2.1: The java.util.Iterator interface.

The programmer who writes this loop needn’t know what gyrations the object i
has to go through to produce the requested elements; even a major change in how
C represents its collection requires no modification to the loop.
This particular kind of for loop is so common and useful that in Java 2, version
1.5, it has its own “syntactic sugar,” known as an enhanced for loop. You can write
for (String i : C)
System.out.print (i + " ");
to get the same effect as the previous for loop. Java will insert the missing pieces,
turning this into
for (Iterator<String> ρ = C.iterator (); ρ.hasNext(); ) {
String i = ρ.next ();
System.out.println (i + " ");
}
where ρ is some new variable introduced by the compiler and unused elsewhere
in the program, and whose type is taken from that of C.iterator(). This en-
hanced for loop will work for any object C whose type implements the interface
java.lang.Iterable, defined simply
public interface Iterable<T> {
Iterator<T> iterator ();
}
Thanks to the enhanced for loop, simply by defining an iterator method on a type
you define, you provide a very convenient way to sequence through any subparts
that objects of that type might contain.
Well, needless to say, having introduced this convenient shorthand for Iterators,
Java’s designers were suddenly in the position that iterating through the elements
26 CHAPTER 2. DATA TYPES IN THE ABSTRACT

of an array was much clumsier than iterating through those of a library class. So
they extended the enhanced for statement to encompass arrays. So, for example,
these two methods are equivalent:
/** The sum of the /** The sum of the elements
* elements of A */ * of A */
int sum (int[] A) { int sum (int[] A) {
int S; int S;
S = 0; S = 0;
for (int x : A) =⇒ for (int κ = 0; κ < A.length; κ++)
S += x; {
} int x = A[κ];
S += x;
}
}
where κ is a new variable introduced by the compiler.

2.1.2 The ListIterator Interface


Some collections do have a natural notion of ordering, but it may still be expensive
to extract an arbitrary item from the collection by index. For example, you may
have seen linked lists in the Scheme language: given an item in the list, it requires
n operations to find the nth succeeding item (in contrast to a Java array, which
requires only one Java operation or a few machine operations to retrieve any item).
The standard Java library contains the interface java.util.ListIterator, which
captures the idea of sequencing through an ordered sequence without fetching each
explicitly by number. It is summarized in Figure 2.2. In addition to the “nav-
igational” methods and the remove method of Iterator (which it extends), the
ListIterator class provides operations for inserting new items or replacing items
in a collection.

2.2 The Java Collection Abstractions


The Java library (beginning with JDK 1.2) provides a hierarchy of interfaces rep-
resenting various kinds of collection, plus a hierarchy of abstract classes to help
programmers provide implementations of these interfaces, as well as a few actual
(“concrete”) implementations. These classes are all found in the package java.util.
Figure 2.4 illustrates the hierarchy of classes and interfaces devoted to collections.

2.2.1 The Collection Interface


The Java library interface java.util.Collection, whose methods are summarized
in Figures 2.5 and 2.6, is supposed to describe data structures that contain collec-
tions of values, where each value is a reference to some Object (or null). The
term “collection” as opposed to “set” is appropriate here, because Collection is
supposed to be able describe multisets (bags) as well as ordinary mathematical sets.
2.2. THE JAVA COLLECTION ABSTRACTIONS 27

package java.util;
/** Abstraction of a position in an ordered collection. At any
* given time, THIS represents a position (called its cursor )
* that is just after some number of items of type T (0 or more) of
* a particular collection, called the underlying collection. */
public interface ListIterator<T> extends Iterator<T> {
/* Exceptions: Methods that return items from the collection throw
* NoSuchElementException if there is no appropriate item. Optional
* methods throw UnsupportedOperationException if the method is not
* supported. */

/* Required methods: */

/** True unless THIS is past the last item of the collection */
boolean hasNext ();

/** True unless THIS is before the first item of the collection */
boolean hasPrevious ();

/** Returns the item immediately after the cursor, and


* moves the current position to just after that item.
* Throws NoSuchElementException if there is no such item. */
T next ();

/** Returns the item immediately before the cursor, and


* moves the current position to just before that item.
* Throws NoSuchElementException if there is no such item. */
T previous ();

/** The number of items before the cursor */


int nextIndex ();

/* nextIndex () - 1 */
int previousIndex ();

Figure 2.2: The java.util.ListIterator interface.


28 CHAPTER 2. DATA TYPES IN THE ABSTRACT

/* Optional methods: */

/** Insert item X into the underlying collection immediately before


* the cursor (X will be returned by previous()). */
void add (T x);

/** Remove the item returned by the most recent call to .next ()
* or .previous (). There must not have been a more recent
* call to .add(). */
void remove ();

/** Replace the item returned by the most recent call to .next ()
* or .previous () with X in the underlying collection.
* There must not have been a more recent call to .add() or .remove. */
void set (T x);
}

Figure 2.2, continued: Optional methods in the ListIterator class.

Map

AbstractMap SortedMap

HashMap WeakHashMap TreeMap

Figure 2.3: The Java library’s Map-related types (from java.util). Ellipses rep-
resent interfaces; dashed boxes are abstract classes, and solid boxes are concrete
(non-abstract) classes. Solid arrows indicate extends relationships, and dashed
arrows indicate implements relationships. The abstract classes are for use by
implementors wishing to add new collection classes; they provide default implemen-
tations of some methods. Clients apply new to the concrete classes to get instances,
and (at least ideally), use the interfaces as formal parameter types so as to make
their methods as widely applicable as possible.
2.2. THE JAVA COLLECTION ABSTRACTIONS 29

Collection

List Set

SortedSet

AbstractCollection

AbstractList AbstractSet

AbstractSequentialList ArrayList Vector HashSet TreeSet

LinkedList Stack

Figure 2.4: The Java library’s Collection-related types (from java.util). See Fig-
ure 2.3 for the notation.
30 CHAPTER 2. DATA TYPES IN THE ABSTRACT

Since this is an interface, the documentation comments describing the operations


need not be accurate; an inept or mischievous programmer can write a class that
implements Collection in which the add method removes values. Nevertheless,
any decent implementor will honor the comments, so that any method that accepts
a Collection, C, as an argument can expect that, after executing C.add(x), the
value x will be in C.
Not every kind of Collection needs to implement every method—specifically,
not the optional methods in Figure 2.6—but may instead choose to raise the stan-
dard exception UnsupportedOperationException. See §2.5 for a further dis-
cussion of this particular design choice. Classes that implement only the required
methods are essentially read-only collections; they can’t be modified once they are
created.
The comment concerning constructors in Figure 2.5 is, of course, merely a com-
ment. Java interfaces do not have constructors, since they do not represent specific
types of concrete object. Nevertheless, you ultimately need some constructor to cre-
ate a Collection in the first place, and the purpose of the comment is to suggest
some useful uniformity.
At this point, you may well be wondering of what possible use the Collection
class might be, inasmuch as it is impossible to create one directly (it is an interface),
and you are missing details about what its members do (for example, can a given
Collection have two equal elements?). The point is that any function that you
can write using just the information provided in the Collection interface will work
for all implementations of Collection.
For example, here is simple method to determine if the elements of one Collection
are a subset of another:

/** True iff C0 is a subset of C1, ignoring repetitions. */


public static boolean subsetOf (Collection<?> C0, Collection<?> C1) {
for (Object i : C0)
if (! C1.contains (i))
return false;
// Note: equivalent to
// for (Iterator<?> iter = C0.iterator(); iter.hasNext (); ) {
// Object i = iter.next ();
// ...
return true;
}

We have no idea what kinds of objects C0 and C1 are (they might be completely
different implementations of Collection), in what order their iterators deliver ele-
ments, or whether they allow repetitions. This method relies solely on the properties
described in the interface and its comments, and therefore always works (assuming,
as always, that the programmers who write classes that implement Collection
do their jobs). We don’t have to rewrite it for each new kind of Collection we
implement.
2.2. THE JAVA COLLECTION ABSTRACTIONS 31

package java.util;
/** A collection of values, each an Object reference. */
public interface Collection<T> extends Iterable<T> {
/* Constructors. Classes that implement Collection should
* have at least two constructors:
* CLASS (): Constructs an empty CLASS
* CLASS (C): Where C is any Collection, constructs a CLASS that
* contains the same elements as C. */

/* Required methods: */

/** The number of values in THIS. */


int size ();

/** True iff size () == 0. */


boolean isEmpty ();

/** True iff THIS contains X: that is, if for some z in


* THIS, either z and X are null, or z.equals (X). */
boolean contains (Object x);

/** True iff contains(x) for all elements x in C. */


boolean containsAll (Collection<?> c);

/** An iterator that yields all the elements of THIS, in some


* order. */
Iterator<T> iterator ();

/** A new array containing all elements of THIS. */


Object[] toArray ();

/** Assuming ANARRAY has dynamic type T[] (where T is some


* reference type), the result is an array of type T[] containing
* all elements of THIS. The result is ANARRAY itself, if all of
* these elements fit (leftover elements of ANARRAY are set to null).
* Otherwise, the result is a new array. It is an error if not
* all items in THIS are assignable to T. */
<T> T[] toArray (T[] anArray);

Figure 2.5: The interface java.util.Collection, required members.


32 CHAPTER 2. DATA TYPES IN THE ABSTRACT

// Interface java.util.Collection, continued.


/* Optional methods. Any of these may do nothing except to
* throw UnsupportedOperationException. */

/** Cause X to be contained in THIS. Returns true if the Collection */


* changes as a result. */
boolean add (T x);

/** Cause all members of C to be contained in THIS. Returns true


* if the object THIS changes as a result. */
boolean addAll (Collection<? extends T> c);

/** Remove all members of THIS. */


void clear ();

/** Remove a Object .equal to X from THIS, if one exists,


* returning true iff the object THIS changes as a result. */
boolean remove (Object X);

/** Remove all elements, x, such that C.contains(x) (if any


* are present), returning true iff there were any
* objects removed. */
boolean removeAll (Collection<?> c);

/** Intersection: Remove all elements, x, such that C.contains(x)


* is false, returning true iff any items were removed. */
boolean retainAll (Collection<?> c);
}

Figure 2.6: Optional members of the interface java.util.Collection


2.2. THE JAVA COLLECTION ABSTRACTIONS 33

2.2.2 The Set Interface


In mathematics, a set is a collection of values in which there are no duplicates. This
is the idea also for the interface java.util.Set. Unfortunately, this provision is
not directly expressible in the form of a Java interface. In fact, as far as the Java
compiler is concerned, the following serves as a perfectly good definition:

package java.util;
public interface Set<T> extends Collection<T> { }

The methods, that is, are all the same. The differences are all in the comments.
The one-copy-of-each-element rule is reflected in more specific comments on several
methods. The result is shown in Figure 2.7. In this definition, we also include the
methods equals and hashCode. These methods are automatically part of any inter-
face, because they are defined in the Java class java.lang.Object, but I included
them here because their semantic specification (the comment) is more stringent than
for the general Object. The idea, of course, is for equals to denote set equality.
We’ll return to hashCode in Chapter 7.

2.2.3 The List Interface


As the term is used in the Java libraries, a list is a sequence of items, possibly with
repetitions. That is, it is a specialized kind of Collection, one in which there is a
sequence to the elements—a first item, a last item, an nth item—and items may be
repeated (it can’t be considered a Set). As a result, it makes sense to extend the
interface (relative to Collection) to include additional methods that make sense
for well-ordered sequences. Figure 2.8 displays the interface.
A great deal of functionality here is wrapped up in the listIterator method
and the object it returns. As you can see from the interface descriptions, you can
insert, add, remove, or sequence through items in a List either by using methods
in the List interface itself, or by using listIterator to create a list iterator with
which you can do the same. The idea is that using the listIterator to process
an entire list (or some part of it) will generally be faster than using get and other
methods of List that use numeric indices to denote items of interest.

Views

The subList method is particularly interesting. A call such as L.subList(i,j) is


supposed to produce another List (which will generally not be of the same type as
L) consisting of the ith through the (j-1)th items of L. Furthermore, it is to do
this by providing a view of this part of L—that is, an alternative way of accessing
the same data containers. The idea is that modifying the sublist (using methods
such as add, remove, and set) is supposed to modify the corresponding portion of
L as well. For example, to remove all but the first k items in list L, you might write

L.subList (k, L.size ()).clear ();


34 CHAPTER 2. DATA TYPES IN THE ABSTRACT

package java.util;
/** A Collection that contains at most one null item and in which no
* two distinct non-null items are .equal. The effects of modifying
* an item contained in a Set so as to change the value of .equal
* on it are undefined. */
public interface Set<T> extends Collection<T> {
/* Constructors. Classes that implement Set should
* have at least two constructors:
* CLASS (): Constructs an empty CLASS
* CLASS (C): Where C is any Collection, constructs a CLASS that
* contains the same elements as C, with duplicates removed. */

/** Cause X to be contained in THIS. Returns true iff X was */


* not previously a member. */
boolean add (T x);

/** True iff S is a Set (instanceof Set) and is equal to THIS as a


* set (size()==S.size() each of item in S is contained in THIS). */
boolean equals (Object S);

/** The sum of the values of x.hashCode () for all x in THIS, with
* the hashCode of null taken to be 0. */
int hashCode ();

/* Other methods inherited from Collection:


* size, isEmpty, contains, containsAll, iterator, toArray,
* addAll, clear, remove, removeAll, retainAll */
}

Figure 2.7: The interface java.util.Set. Only methods with comments that are
more specific than those of Collection are shown.
2.2. THE JAVA COLLECTION ABSTRACTIONS 35

package java.util;
/** An ordered sequence of items, indexed by numbers 0 .. N-1,
* where N is the size() of the List. */
public interface List<T> extends Collection<T> {

/* Required methods: */

/** The Kth element of THIS, where 0 <= K < size(). Throws
* IndexOutOfBoundsException if K is out of range. */
T get (int k);

/** The first value k such that get(k) is null if X==null,


* X.equals (get(k)), otherwise, or -1 if there is no such k. */
int indexOf (Object x);

/** The largest value k such that get(k) is null if X==null,


* X.equals (get(k)), otherwise, or -1 if there is no such k. */
int lastIndexOf (Object x);

/* NOTE: The methods iterator, listIterator, and subList produce


* views that become invalid if THIS is structurally modified by
* any other means (see text). */

/** An iterator that yields all the elements of THIS, in proper


* index order. (NOTE: it is always valid for iterator() to
* return the same value as would listIterator, below.) */
Iterator<T> iterator ();

/** A ListIterator that yields the elements K, K+1, ..., size()-1


* of THIS, in that order, where 0 <= K <= size(). Throws
* IndexOutOfBoundsException if K is out of range. */
ListIterator<T> listIterator (int k);

/** Same as listIterator (0) */


ListIterator<T> listIterator ();

/** A view of THIS consisting of the elements L, L+1, ..., U-1,


* in that order. Throws IndexOutOfBoundsException unless
* 0 <= L <= U <= size(). */
List<T> subList (int L, int U);

/* Other methods inherited from Collection:


* add, addAll, size, isEmpty, contains, containsAll, remove, toArray */

Figure 2.8: Required methods of interface java.util.List, beyond those inherited


from Collection.
36 CHAPTER 2. DATA TYPES IN THE ABSTRACT

/* Optional methods: */

/** Cause item K of THIS to be X, and items K+1, K+2, ... to contain
* the previous values of get(K), get(K+1), .... Throws
* IndexOutOfBoundsException unless 0<=K<=size(). */
void add (int k, T x);

/** Same effect as add (size (), x); always returns true. */
boolean add (T x);

/** If the elements returned by C.iterator () are x0, x1,..., in


* that order, then perform the equivalent of add(K,x0),
* add(K+1,x1), ..., returning true iff there was anything to
* insert. IndexOutOfBoundsException unless 0<=K<=size(). */
boolean addAll (int k, Collection<T> c);

/** Same as addAll(size(), c). */


boolean addAll (Collection<T> c);

/** Remove item K, moving items K+1, ... down one index position,
* and returning the removed item. Throws
* IndexOutOfBoundsException if there is no item K. */
Object remove (int k);

/** Remove the first item equal to X, if any, moving subsequent


* elements one index position lower. Return true iff anything
* was removed. */
boolean remove (Object x);

/** Replace get(K) with X, returning the initial (replaced) value of


* get(K). Throws IndexOutOfBoundsException if there is no item K. */
Object set (int k, T x);

/* Other methods inherited from Collection: removeAll, retainAll */


}

Figure 2.8, continued: Optional methods of interface java.util.List, beyond


from those inherited from Collection.
2.2. THE JAVA COLLECTION ABSTRACTIONS 37

As a result, there are a lot of possible operations on List that don’t have to be
defined, because they fall out as a natural consequence of operations on sublists.
There is no need for a version of remove that deletes items i through j of a list, or
for a version of indexOf that starts searching at item k.
Iterators (including ListIterators) provide another example of a view of Col-
lections. Again, you can access or (sometimes) modify the current contents of a
Collection through an iterator that its methods supply. For that matter, any Col-
lection is itself a view—the “identity view” if you want.
Whenever there are two possible views of the same entity, there is a possibility
that using one of them to modify the entity will interfere with the other view. It’s
not just that changes in one view are supposed to be seen in other views (as in the
example of clearing a sublist, above), but straightforward and fast implementations
of some views may malfunction when the entity being viewed is changed by other
means. What is supposed to happen when you call remove on an iterator, but the
item that is supposed to be removed (according to the specification of Iterator)
has already been removed directly (by calling remove on the full Collection)? Or
suppose you have a sublist containing items 2 through 4 of some full list. If the full
list is cleared, and then 3 items are added to it, what is in the sublist view?
Because of these quandries, the full specification of many view-producing meth-
ods (in the List interface, these are iterator, listIterator, and subList) have
a provision that the view becomes invalid if the underlying List is structurally mod-
ified (that is, if items are added or removed) through some means other than that
view. Thus, the result of L.iterator() becomes invalid if you perform L.add(...),
or if you perform remove on some other Iterator or sublist produced from L. By
contrast, we will also encounter views, such as those produced by the values method
on Map (see Figure 2.12), that are supposed to remain valid even when the under-
lying object is structurally modified; it is an obligation on the implementors of new
kinds of Map that they see that this is so.

2.2.4 Ordered Sets


The List interface describes data types that describe sequences in which the pro-
grammer explicitly determines the order of items in the sequence by the order or
place in which they are added to the sequence. By contrast, the SortedSet in-
terface is intended to describe sequences in which the data determine the ordering
according to some selected relation. Of course, this immediately raises a question:
in Java, how do we represent this “selected relation” so that we can specify it? How
do we make an ordering relation a parameter?

Orderings: the Comparable and Comparator Interfaces


There are various ways for functions to define an ordering over some set of objects.
One way is to define boolean operations equals, less, greater, etc., with the
obvious meanings. Libraries in the C family of languages (which includes Java)
tend to combine all of these into a single function that returns an integer whose
sign denotes the relation. For example, on the type String, x.compareTo("cat")
38 CHAPTER 2. DATA TYPES IN THE ABSTRACT

package java.lang;
/** Describes types that have a natural ordering. */
public interface Comparable<T> {
/** Returns
* * a negative value iff THIS < Y under the natural ordering
* * a positive value iff THIS > Y;
* * 0 iff X and Y are "equivalent".
* Throws ClassCastException if X and Y are incomparable. */
int compareTo (T y);
}

Figure 2.9: The interface java.lang.Comparable, which marks classes that define
a natural ordering.

returns an integer that is zero, negative, or positive, depending on whether x equals


"cat", comes before it in lexicographic order, or comes after it. Thus, the ordering
x ≤ y on Strings corresponds to the condition x.compareTo(y)<=0.
For the purposes of the SortedSet interface, this ≤ (or ≥) ordering represented
by compareTo (or compare, described below) is intended to be a total ordering.
That is, it is supposed to be transitive (x ≤ y and y ≤ z implies x ≤ z), reflexive
(x ≤ x), and antisymmetric (x ≤ y and y ≤ x implies that x equals y). Also, for all
x and y in the function’s domain, either x ≤ y or y ≤ x.
Some classes (such as String) define their own standard comparison operation.
The standard way to do so is to implement the Comparable interface, shown in
Figure 2.9. However, not all classes have such an ordering, nor is the natural
ordering necessarily what you want in any given case. For example, one can sort
Strings in dictionary order, reverse dictionary order, or case-insensitive order.
In the Scheme language, there is no particular problem: an ordering relation is
just a function, and functions are perfectly good values in Scheme. To a certain
extent, the same is true in languages like C and Fortran, where functions can be
used as arguments to subprograms, but unlike Scheme, have access only to global
variables (what are called static fields or class variables in Java). Java does not di-
rectly support functions as values, but it turns out that this is not a limitation. The
Java standard library defines the Comparator interface (Figure 2.10) to represent
things that may be used as ordering relations.
The methods provided by both of these interfaces are supposed to be proper to-
tal orderings. However, as usual, none of the conditions can actually be enforced by
the Java language; they are just conventions imposed by comment. The program-
mer who violates these assumptions may cause all kinds of unexpected behavior.
Likewise, nothing can keep you from defining a compare operation that is inconsis-
tent with the .equals function. We say that compare (or compareTo) is consistent
with equals if x.equals(y) iff C.compare(x,y)==0. It’s generally good practice to
maintain this consistency in the absence of a good reason to the contrary.
2.3. THE JAVA MAP ABSTRACTIONS 39

package java.util;
/** An ordering relation on certain pairs of objects. If */
public interface Comparator<T> {
/** Returns
* * a negative value iff X < Y according to THIS ordering;
* * a positive value iff X > Y;
* * 0 iff X and Y are "equivalent" under the order;
* Throws ClassCastException if X and Y are incomparable.
*/
int compare (T x, T y);

/** True if ORD is "same" ordering as THIS. It is legal to return


* false (conservatively) even if ORD does define the same ordering,
* but should return true only if ORD.compare (X, Y) and
* THIS.compare(X, Y) always have the same value. */
boolean equals (Object ord);
}

Figure 2.10: The interface java.util.Comparator, which represents ordering rela-


tions between Objects.

The SortedSet Interface


The SortedSet interface shown in Figure 2.11 extends the Set interface so that
its iterator method delivers an Iterator that sequences through its contents “in
order.” It also provides additional methods that make sense only when there is
such an order. There are intended to be two ways to define this ordering: either the
programmer supplies a Comparator when constructing a SortedSet that defines
the order, or else the contents of the set must Comparable, and their natural order
is used.

2.3 The Java Map Abstractions


The term map or mapping is used in computer science and elsewhere as a synonym
for function in the mathematical sense—a correspondence between items in some
set (the domain) and another set (the codomain) in which each item of the domain
corresponds to (is mapped to by) a single item of the codomain3 .
It is typical among programmers to take a rather operational view, and say
that a map-like data structure “looks up” a given key (domain value) to find the
associated value (codomain value). However, from a mathematical point of view, a
perfectly good interpretation is that a mapping is a set of pairs, (d, c), where d is a
3
Any number of members of the domain, including zero, may correspond to a given member of
the codomain. The subset of the codomain that is mapped to by some member of the domain is
called the range of the mapping, or the image of the domain under the mapping.
40 CHAPTER 2. DATA TYPES IN THE ABSTRACT

package java.util;
public interface SortedSet<T> extends Set<T> {
/* Constructors. Classes that implement SortedSet should define
* at least the constructors
* CLASS (): An empty set ordered by natural order (compareTo).
* CLASS (CMP): An empty set ordered by the Comparator CMP.
* CLASS (C): A set containing the items in Collection C, in
* natural order.
* CLASS (S): A set containing a copy of SortedSet S, with the
* same order.
*/

/** The comparator used by THIS, or null if natural ordering used. */


Comparator<? super T> comparator ();

/** The first (smallest) item in THIS according to its ordering */


T first ();

/** The last (largest) item in THIS according to its ordering */


T last ();

/* NOTE: The methods headSet, tailSet, and subSet produce


* views that become invalid if THIS is structurally modified by
* any other means. */

/** A view of all items in THIS that are strictly less than X. */
SortedSet<T> headSet (T x);

/** A view of all items in THIS that are strictly >= X. */


SortedSet<T> tailSet (T x);

/** A view of all items, y, in THIS such that X0 <= y < X1. */
SortedSet<T> subSet (T X0, T X1);
}

Figure 2.11: The interface java.util.SortedSet.


2.4. AN EXAMPLE 41

member of the domain, and c of the codomain.

2.3.1 The Map Interface


The standard Java library uses the java.util.Map interface, displayed in Fig-
ures 2.12 and 2.13, to capture these notions of “mapping.” This interface provides
both the view of a map as a look-up operation (with the method get), but also the
view of a map as a set of ordered pairs (with the method entrySet). This in turn re-
quires some representation for “ordered pair,” provided here by the nested interface
Map.Entry. A programmer who wishes to introduce a new kind of map therefore
defines not only a concrete class to implement the Map interface, but another one
to implement Map.Entry.

2.3.2 The SortedMap Interface


An object that implements java.util.SortedMap is supposed to be a Map in which
the set of keys is ordered. As you might expect, the operations are analogous to
those of the interface SortedSet, as shown in Figure 2.15.

2.4 An Example
Consider the problem of reading in a sequence of pairs of names, (ni , mi ). We wish
to create a list of all the first members, ni , in alphabetical order, and, for each of
them, a list of all names mi that are paired with them, with each mi appearing
once, and listed in the order of first appearance. Thus, the input

John Mary George Jeff Tom Bert George Paul John Peter
Tom Jim George Paul Ann Cyril John Mary George Eric

might produce the output

Ann: Cyril
George: Jeff Paul Eric
John: Mary Peter
Tom: Bert Jim

We can use some kind of SortedMap to handle the ni and for each, a List to handle
the mi . A possible method (taking a Reader as a source of input and a PrintWriter
as a destination for output) is shown in Figure 2.16.
42 CHAPTER 2. DATA TYPES IN THE ABSTRACT

package java.util;
public interface Map<Key, Val> {
/* Constructors: Classes that implement Map should
* have at least two constructors:
* CLASS (): Constructs an empty CLASS
* CLASS (M): Where M is any Map, constructs a CLASS that
* denotes the same abstract mapping as C. */

/* Required methods: */

/** The number of keys in the domain of THIS map. */


int size ();
/** True iff size () == 0 */
boolean isEmpty ();

/* NOTE: The methods keySet, values, and entrySet produce views


* that remain valid even if THIS is structurally modified. */

/** The domain of THIS. */


Set<Key> keySet ();
/** The range of THIS. */
Collection<Val> values ();
/** A view of THIS as the set of all its (key,value) pairs. */
Set<Map.Entry<Key, Val>> entrySet ();
/** The value mapped to by KEY, or null if KEY is not
* in the domain of THIS. */
/** True iff keySet().contains (KEY) */
boolean containsKey (Object key);
/** True iff values().contains (VAL). */
boolean containsValue (Object val);
Object get (Object key);
/** True iff M is a Map and THIS and M represent the same mapping. */
boolean equals (Object M);
/** The sum of the hashCode values of all members of entrySet(). */
int hashCode ();

static interface Entry { ... // See Figure 2.14 }

Figure 2.12: Required methods of the interface java.util.Map.


2.4. AN EXAMPLE 43

// Interface java.util.Map, continued

/* Optional methods: */

/** Set the domain of THIS to the empty set. */


void clear();
/** Cause get(KEY) to yield VAL, without disturbing other values. */
Object put(Key key, Val val);
/** Add all members of M.entrySet() to the entrySet() of THIS. */
void putAll(Map<? extends Key, ? extends Val> M);
/** Remove KEY from the domain of THIS. */
Object remove(Object key);
}

Figure 2.13: Optional methods of the interface java.util.Map.

/** Represents a (key,value) pair from some Map. In general, an Entry


* is associated with a particular underlying Map value. Operations that
* change the Entry (specifically setValue) are reflected in that
* Map. Once an entry has been removed from a Map as a result of
* remove or clear, further operations on it may fail. */
static interface Entry<Key,Val> {
/** The key part of THIS. */
Key getKey();
/** The value part of THIS. */
Val getValue();
/** Cause getValue() to become VAL, returning the previous value. */
Val setValue(Val val);

/** True iff E is a Map.Entry and both represent the same (key,value)
* pair (i.e., keys are both null, or are .equal, and likewise for
* values).
boolean equals(Object e);
/** An integer hash value that depends only on the hashCode values
* of getKey() and getValue() according to the formula:
* (getKey() == null ? 0 : getKey().hashCode ())
* ^ (getValue() == null ? 0 : getValue.hashCode ()) */
int hashCode();
}

Figure 2.14: The nested interface java.util.Map.Entry, which is nested within


the java.util.Map interface.
44 CHAPTER 2. DATA TYPES IN THE ABSTRACT

package java.util;
public interface SortedMap<Key,Val> extends Map<Key,Val> {
/* Constructors: Classes that implement SortedMap should
* have at least four constructors:
* CLASS (): An empty map whose keys are ordered by natural order.
* CLASS (CMP): An empty map whose keys are ordered by the Comparator CMP.
* CLASS (M): A map that is a copy of Map M, with keys ordered
* in natural order.
* CLASS (S): A map containing a copy of SortedMap S, with
* keys obeying the same ordering.
*/

/** The comparator used by THIS, or null if natural ordering used. */


Comparator<? super Key> comparator ();

/** The first (smallest) key in the domain of THIS according to


* its ordering */
Key firstKey ();

/** The last (largest) item in the domain of THIS according to


* its ordering */
Key lastKey ();

/* NOTE: The methods headMap, tailMap, and subMap produce views


* that remain valid even if THIS is structurally modified. */

/** A view of THIS consisting of the restriction to all keys in the


* domain that are strictly less than KEY. */
SortedMap<Key,Val> headMap (Key key);

/** A view of THIS consisting of the restriction to all keys in the


* domain that are greater than or equal to KEY. */
SortedMap<Key,Val> tailMap (Key key);

/** A view of THIS restricted to the domain of all keys, y,


* such that KEY0 <= y < KEY1. */
SortedMap<Key,Val> subMap (Key key0, Key key1);
}

Figure 2.15: The interface java.util.SortedMap, showing methods not included


in Map.
2.4. AN EXAMPLE 45

import java.util.*;
import java.io.*;

class Example {

/** Read (ni , mi ) pairs from INP, and summarize all


* pairings for each $n_i$ in order on OUT. */
static void correlate (Reader inp, PrintWriter out)
throws IOException
{
Scanner scn = new Scanner (inp);
SortedMap<String, List<String>> associatesMap
= new TreeMap<String,List<String>> ();
while (scn.hasNext ()) {
String n = scn.next ();
String m = scn.next ();
if (m == null || n == null)
throw new IOException ("bad input format");
List<String> associates = associatesMap.get (n);
if (associates == null) {
associates = new ArrayList<String> ();
associatesMap.put (n, associates);
}
if (! associates.contains (m))
associates.add (m);
}

for (Map.Entry<String, List<String>> e : associatesMap.entrySet ()) {


System.out.format ("%s:", e.getKey ());
for (String s : e.getValue ())
System.out.format (" %s", s);
System.out.println ();
}
}
}

Figure 2.16: An example using SortedMaps and Lists.


46 CHAPTER 2. DATA TYPES IN THE ABSTRACT

2.5 Managing Partial Implementations: Design Options


Throughout the Collection interfaces, you saw (in comments) that certain opera-
tions were “optional.” Their specifications gave the implementor leave to use

throw new UnsupportedOperationException ();

as the body of the operation. This provides an elegant enough way not to implement
something, but it raises an important design issue. Throwing an exception is a
dynamic action. In general, the compiler will have no comment about the fact that
you have written a program that must inevitably throw such an exception; you will
discover only upon testing the program that the implementation you have chosen
for some data structure is not sufficient.
An alternative design would split the interfaces into smaller pieces, like this:

public interface ConstantIterator<T> {


Required methods of Iterator
}

public interface Iterator<T> extends ConstantIterator<T> {


void remove ();
}

public interface ConstantCollection<T> {


Required methods of Collection
}

public interface Collection<T> extends ConstantCollection<T> {


Optional methods of Collection
}

public interface ConstantSet<T> extends ConstantCollection<T> {


}

public interface Set<T> extends ConstantSet<T>, Collection<T> {


}

public interface ConstantList<T> extends ConstantCollection<T> {


Required methods of List
}

public interface List<T> extends Collection<T>, ConstantList<T> {


Optional methods of List
}

etc.. . .
2.5. MANAGING PARTIAL IMPLEMENTATIONS: DESIGN OPTIONS 47

With such a design the compiler could catch attempts to call unsupported methods,
so that you wouldn’t need testing to discover a gap in your implementation.
However, such a redesign would have its own costs. It’s not quite as simple as
the listing above makes it appear. Consider, for example, the subList method in
ConstantList. Presumably, this would most sensibly return a ConstantList, since
if you are not allowed to alter a list, you cannot be allowed to alter one of its views.
That means, however, that the type List would need two subList methods (with
differing names), the one inherited from ConstantList, and a new one that produces
a List as its result, which would allow modification. Similar considerations apply
to the results of the iterator method; there would have to be two—one to return a
ConstantIterator, and the other to return Iterator. Furthermore, this proposed
redesign would not deal with an implementation of List that allowed one to add
items, or clear all items, but not remove individual items. For that, you would either
still need the UnsupportedOperationException or an even more complicated nest
of classes.
Evidently, the Java designers decided to accept the cost of leaving some problems
to be discovered by testing in order to simplify the design of their library. By
contrast, the designers of the corresponding standard libraries in C++ opted to
distinguish operations that work on any collections from those that work only on
“mutable” collections. However, they did not design their library out of interfaces;
it is awkward at best to introduce new kinds of collection or map in the C++ library.
48 CHAPTER 2. DATA TYPES IN THE ABSTRACT
Chapter 3

Meeting a Specification

In Chapter 2, we saw and exercised a number of abstract interfaces—abstract in the


sense that they describe the common features, the method signatures, of whole fam-
ilies of types without saying anything about the internals of those types and without
providing a way to create any concrete objects that implement those interfaces.
In this chapter, we get a little closer to concrete representations, by showing
one way to fill in the blanks. In one sense, these won’t be serious implementations;
they will use “naive,” rather slow data structures. Our purpose, rather, will be one
of exercising the machinery of object-oriented programming to illustrate ideas that
you can apply elsewhere.
To help implementors who wish to introduce new implementations of the ab-
stract interfaces we’ve covered, the Java standard library provides a parallel collec-
tion of abstract classes with some methods filled in. Once you’ve supplied a few
key methods that remain unimplemented, you get all the rest “for free”. These
partial implementation classes are not intended to be used directly in most ordi-
nary programs, but only as implementation aids for library writers. Here is a list
of these classes and the interfaces they partially implement (all from the package
java.util):

Abstract Class Interfaces


AbstractCollection Collection
AbstractSet Collection, Set
AbstractList Collection, List
AbstractSequentialList Collection, List
AbstractMap Map

The idea of using partial implementations in this way is an instance of a design


pattern called Template Method. The term design pattern in the context of object-
oriented programming has come to mean “the core of a solution to a particular
commonly occurring problem in program design1 .” The Abstract... classes are
1
The seminal work on the topic is the excellent book by E. Gamma, R. Helm, R. Johnson, and J.
Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1995.
This group and their book are often referred to as “The Gang of Four.”

49
50 CHAPTER 3. MEETING A SPECIFICATION

import java.util.*;
import java.lang.reflect.Array;
public class ArrayCollection<T> implements Collection<T> {
private T[] data;

/** An empty Collection */


public ArrayCollection () { data = (T[]) new Object[0]; }

/** A Collection consisting of the elements of C */


public ArrayCollection (Collection<? extends T> C) {
data = C.toArray((T[]) new Object[C.size ()]);
}

/** A Collection consisting of a view of the elements of A. */


public ArrayCollection (T[] A) { data = T; }

public int size () { return data.length; }

public Iterator<T> iterator () {


return new Iterator<T> () {
private int k = 0;
public boolean hasNext () { return k < size (); }
public T next () {
if (! hasNext ()) throw new NoSuchElementException ();
k += 1;
return data[k-1];
}
public void remove () {
throw new UnsupportedOperationException ();
}
};
}

public boolean isEmpty () { return size () == 0; }

public boolean contains (Object x) {


for (T y : this) {
if (x == null && y == null
|| x != null && x.equals (y))
return true;
}
return false;
}

Figure 3.1: Implementation of a new kind of read-only Collection “from scratch.”


Other documents randomly have
different content
much time was consumed in seeking the way. I have a vivid
recollection of a gully of more than usual perplexity at the side of
the Great Tower, with minute ledges and steep walls; of the ledges
dwindling down, and at last ceasing; of finding myself, with arms
and legs divergent, fixed as if crucified, pressing against the rock,
and feeling each rise and fall of my chest as I breathed; of screwing
my head round to look for a hold and not seeing any, and of jumping
sideways on to the other side....
[The gully] was an untrodden vestibule, which led to a scene so wild
that even the most sober description of it must seem an
exaggeration. There was a change in the quality of the rock, and
there was a change in the appearance of the ridge. The rocks
(talcose gneiss) below this spot were singularly firm,—it was rarely
necessary to test one’s hold: the way led over the living rock, and
not up rent-off fragments. But here all was decay and ruin. The crest
of the ridge was shattered and cleft, and the feet sank in the chips
which had drifted down; while above, huge blocks, hacked and
carved by the hand of time, nodded to the sky, looking like the
gravestones of giants. Out of curiosity I wandered to a notch in the
ridge, between two tottering piles of immense masses which seemed
to need but a few pounds on one or the other side to make them
fall, so nicely poised that they would literally have rocked in the
wind, for they were put in motion by a touch, and based on support
so frail that I wondered they did not collapse before my eyes. In the
whole range of my Alpine experience I have seen nothing more
striking than this desolate, ruined, and shattered ridge at the back of
the Great Tower. I have seen stranger shapes,—rocks which mimic
the human form, with monstrous leering faces, and isolated
pinnacles sharper and greater than any here,—but I have never seen
exhibited so impressively the tremendous effects which may be
produced by frost, and by the long-continued action of forces whose
individual effects are imperceptible.
It is needless to say that it is impossible to climb by the crest of the
ridge at this part; still, one is compelled to keep near to it, for there
is no other way. Generally speaking, the angles on the Matterhorn
are too steep to allow the formation of considerable beds of snow,
but here there is a corner which permits it to accumulate, and it is
turned to gratefully, for by its assistance one can ascend four times
as rapidly as upon the rocks.
The Tower was now almost out of sight, and I looked over the
central Pennine Alps to the Grand Combin and to the chain of Mont
Blanc. My neighbor, the Dent d’Hérens, still rose above me, although
but slightly, and the height which had been attained could be
measured by its help. So far, I had no doubts about my capacity to
descend that which had been ascended; but in a short time, on
looking ahead, I saw that the cliffs steepened, and I turned back
(without pushing on to them and getting into inextricable
difficulties), exulting in the thought that they would be passed when
we returned together, and that I had without assistance got nearly
to the height of the Dent d’Hérens, and considerably higher than any
one had been before. My exultation was a little premature.
About five P.M. I left the tent again, and thought myself as good as
at Breuil. The friendly rope and claw had done good service, and had
smoothed all the difficulties. I lowered myself through the Chimney,
however, by making a fixture of the rope, which I then cut off and
left behind, as there was enough and to spare. My axe had proved a
great nuisance in coming down, and I left it in the tent. It was not
attached to the bâton, but was a separate affair,—an old navy
boarding-axe. While cutting up the different snow-beds on the
ascent, the bâton trailed behind fastened to the rope; and when
climbing the axe was carried behind, run through the rope tied
round my waist, and was sufficiently out of the way, but in
descending, when coming down face outward (as is always best
where it is possible), the head or the handle of the weapon caught
frequently against the rocks, and several times nearly upset me. So,
out of laziness if you will, it was left in the tent. I paid dearly for the
imprudence.
The Col du Lion was passed, and fifty yards more would have placed
me on the “Great Staircase,” down which one can run. But on
arriving at an angle of the cliffs of the Tête du Lion, while skirting
the upper edge of the snow which abuts against them, I found that
the heat of the two past days had nearly obliterated the steps which
had been cut when coming up. The rocks happened to be
impracticable just at this corner, so nothing could be done except
make the steps afresh. The snow was too hard to beat or tread
down, and at the angle it was all but ice: half a dozen steps only
were required, and then the ledges could be followed again. So I
held to the rock with my right hand, and prodded at the snow with
the point of my stick until a good step was made, and then, leaning
round the angle, did the same for the other side. So far well, but in
attempting to pass the corner (to the present moment I cannot tell
how it happened) I slipped and fell.
The slope was steep on which this took place, and descended to the
top of a gully that led down through two subordinate buttresses
towards the Glacier du Lion, which was just seen, a thousand feet
below. The gully narrowed and narrowed until there was a mere
thread of snow lying between two walls of rock, which came to an
abrupt termination at the top of a precipice that intervened between
it and the glacier. Imagine a funnel cut in half through its length,
placed at an angle of forty-five degrees, with its point below and its
concave side uppermost, and you will have a fair idea of the place.
The knapsack brought my head down first, and I pitched into some
rocks about a dozen feet below: they caught something, and
tumbled me off the edge, head over heels, into the gully. The bâton
was dashed from my hands, and I whirled downward in a series of
bounds, each longer than the last,—now over ice, now into rocks,—
striking my head four or five times, each time with increased force.
The last bound sent me spinning through the air, in a leap of fifty or
sixty feet, from one side of the gully to the other, and I struck the
rocks, luckily, with the whole of my left side. They caught my clothes
for a moment, and I fell back on to the snow with motion arrested:
my head fortunately came the right side up, and a few frantic
catches brought me to a halt in the neck of the gully and on the
verge of the precipice. Bâton, hat, and veil skimmed by and
disappeared, and the crash of the rocks which I had started, as they
fell on to the glacier, told how narrow had been the escape from
utter destruction. As it was, I fell nearly two hundred feet in seven
or eight bounds. Ten feet more would have taken me in one gigantic
leap of eight hundred feet on to the glacier below.
The situation was still sufficiently serious. The rocks could not be left
go for a moment, and the blood was spurting out of more than
twenty cuts. The most serious ones were in the head, and I vainly
tried to close them with one hand while holding on with the other. It
was useless: the blood jerked out in blinding jets at each pulsation.
At last, in a moment of inspiration, I kicked out a big lump of snow
and stuck it as a plaster on my head. The idea was a happy one, and
the flow of blood diminished: then, scrambling up, I got, not a
moment too soon, to a place of safety and fainted away. The sun
was setting when consciousness returned, and it was pitch dark
before the Great Staircase was descended; but by a combination of
luck and care the whole four thousand eight hundred feet of descent
to Breuil was accomplished without a slip or once missing the way.
I slunk past the cabin of the cowherds, who were talking and
laughing inside, utterly ashamed of the state to which I had been
brought by my imbecility, and entered the inn stealthily, wishing to
escape to my room unnoticed. But Favre met me in the passage,
demanded, “Who is it?” screamed with fright when he got a light,
and aroused the household. Two dozen heads then held solemn
council over mine, with more talk than action. The natives were
unanimous in recommending that hot wine (syn. vinegar), mixed
with salt, should be rubbed into the cuts. I protested, but they
insisted. It was all the doctoring they received. Whether their rapid
healing was to be attributed to that simple remedy or to a good
state of health, is a question; they closed up remarkably soon, and
in a few days I was able to move again....
As it seldom happens that one survives such a fall, it may be
interesting to record what my sensations were during its occurrence.
I was perfectly conscious of what was happening, and felt each
blow, but, like a patient under chloroform, experienced no pain.
Each blow was, naturally, more severe than that which preceded it,
and I distinctly remember thinking, “Well, if the next is harder still,
that will be the end!” Like persons who have been rescued from
drowning, I remember that the recollection of a multitude of things
rushed through my head, many of them trivialities or absurdities
which had been forgotten long before; and, more remarkable, this
bounding through space did not feel disagreeable. But I think that in
no very great distance more consciousness as well as sensation
would have been lost, and upon that I base my belief, improbable as
it seems, that death by a fall from a great height is as painless an
end as can be experienced.
The battering was very rough, yet no bones were broken. The most
severe cuts were, one four inches long on the top of the head, and
another of three inches on the right temple; this latter bled
frightfully. There was a formidable-looking cut, of about the same
size as the last, on the palm of the left hand, and every limb was
grazed or cut more or less seriously. The tips of the ears were taken
off, and a sharp rock cut a circular bit out of the side of the left boot,
sock, and ankle at one stroke. The loss of blood, although so great,
did not seem to be permanently injurious. The only serious effect
has been the reduction of a naturally retentive memory to a very
commonplace one; and although my recollections of more distant
occurrences remain unshaken, the events of that particular day
would be clean gone but for the few notes which were written down
before the accident.
A TYPICAL DUTCH CITY.
EDMONDO DE AMICIS.
[De Amicis, a traveller of Italian birth, has given us a number of highly
interesting records of travel, including works on Algeria, Spain, Holland,
Paris, Constantinople, etc. Among these, “Holland and its People” is perhaps
the most entertaining, and as a specimen of its manner we select from it the
description of Rotterdam, as a typical example of a Dutch city. This selection
is from the translation by Caroline Tilton, published by G. P. Putnam’s Sons.]

When we arrived in sight of Rotterdam it rained and was foggy; we


could see, as through a veil, only an immense confusion of ships,
houses, windmills, towers, trees, and people in motion on the dykes
and bridges; there were lights everywhere; a great city with such an
aspect as I had never seen before, and which fog and darkness soon
hid from me altogether. When I had taken leave of my travelling
companions, and had put my luggage in order, it was night. “So
much the better,” I thought, as I entered a carriage; “I shall see the
first Dutch city by night, which must be a strange spectacle.” And,
indeed, when M. Bismarck was at Rotterdam he wrote to his wife
that at night he saw spectres on the roofs.
It is difficult to make much of the city of Rotterdam, entering it at
night. The carriage passed almost immediately over a bridge that
resounded hollowly beneath it; and while I thought myself, and was,
in fact, within the city, I saw with amazement on my right and left
two rows of ships vanishing in the gloom.
Leaving the bridge, we passed through a street, lighted, and full of
people, and found ourselves upon another bridge, and between two
rows of vessels as before, and so on from bridge to street, from
street to bridge, and, to increase the confusion, an illumination of
lamps at the corners of houses, lanterns on masts of ships, light-
houses on the bridges, small lights under the houses, and all these
lights reflected in the water. All at once the carriage stopped, people
crowded about; I looked out and saw a bridge in the air. In answer
to my question, some one said that a vessel was passing. We went
on again, seeing a perspective of canals and bridges crossing and
recrossing each other, until we came to a great square, sparkling
with lights, and bristling with masts of ships, and finally we reached
our inn in an adjacent street.
My first care on entering my room was to see whether Dutch
cleanliness deserved its fame. It did, indeed, and may be called the
religion of cleanliness. The linen was snow-white, the windows
transparent as the air, the furniture shining like a crystal, the floors
so clean that a microscope could not discover a black speck. There
was a basket for waste paper, a tablet for scratching matches, a dish
for cigar-ashes, a box for cigar-stumps, a spittoon, and a boot-jack;
in short, there was no possible pretext for soiling anything.
My room examined, I spread a map of Rotterdam upon the table,
and made some preparatory studies for the morrow.
It is a singular thing that the great cities of Holland, although built
upon a shifting soil, and amid difficulties of every kind, have all great
regularity of form. Amsterdam is a semicircle, the Hague square,
Rotterdam an equilateral triangle. The base of the triangle is an
immense dyke, which defends the city from the Meuse, and is called
the Boompjes, signifying, in Dutch, small trees, from a row of little
elms, now very tall, that were planted when it was first constructed.
Another great dyke forms a second bulwark against the river, which
divides the city into two almost equal parts, and from the middle of
the left side to the opposite angle. That part of Rotterdam which is
comprised between the dykes is all canals, islands, and bridges, and
is the new city; that which extends beyond the second dyke is the
old city. Two great canals extend along the other two sides of the
town to the apex, where they meet, and receive the waters of the
river Rotte, which, with the affix of dam, or dyke, gives its name to
the city.
Having thus fulfilled my conscientious duty as a traveller, and with
many precautions not to soil, even by a breath, the purity of that
jewel of a chamber, I abandoned myself with humility to my first
Dutch bed.
Dutch beds—I speak of those in the hotels—are generally short and
wide, and occupied, in a great part, by an immense feather pillow in
which a giant’s head would be overwhelmed. I may add that the
ordinary light is a copper candlestick, of the size of a dinner-plate,
which might sustain a torch, but holds, instead, a tiny candle about
the size of a Spanish lady’s finger.
In the morning I made haste to rise and issue forth into the strange
streets, unlike anything in Europe. The first I saw was the Hoog
Straat, a long, straight thoroughfare, running along the interior dyke.
The unplastered houses, of every shade of brick, from the darkest
red to light rose-color, chiefly two windows wide and two stories
high, have the front wall rising above and concealing the roof, and in
the shape of a blunt triangle surmounted by a parapet. Some of
these pointed façades rise into two curves, like a long neck without a
head; some are cut into steps like the houses that children build with
blocks; some present the aspect of a conical pavilion, some of a
village church, some of theatrical cabins. The parapets are in general
surrounded by white stripes, coarse arabesques in plaster, and other
ornaments in very bad taste; the doors and windows are bordered
by broad white stripes; other white lines divide the different stories;
the spaces between the doors in front are marked by white wooden
panels, so that two colors, white and red, prevail everywhere, and as
in the distance the darker red looks black, the prospect is half
festive, half funereal, all the houses looking as if they were hung
with white linen. At first I had an inclination to laugh, for it seemed
impossible that it could have been done seriously, and that quite
sober people lived in those houses. They looked as if they had been
run up for a festival, and would presently disappear, like the paper
frame-work of a grand display of fireworks.
While I stood looking vaguely at the street, I noticed one house that
puzzled me somewhat; and, thinking that my eyes had been
deceived, I looked more carefully at it, and compared it with its
neighbors. Turning into the next street, the same thing met my
astonished gaze. There is no doubt about it: the whole city of
Rotterdam presents the appearance of a town that has been shaken
smartly by an earthquake, and is on the point of falling into ruin.

A TYPICAL DUTCH WINDMILL


All the houses—in any street one may count the exceptions on their
fingers—lean more or less, but the greater part of them so much
that at the roof they lean forward at least a foot beyond their
neighbors, which may be straight, or not so visibly inclined; one
leans forward as if it would fall into the street; another backward,
another to the left, another to the right; at some points six or seven
contiguous houses all lean forward together, those in the middle
most, those at the ends less, looking like a paling with the crowd
pressing against it. At another point two houses lean together as if
supporting one another. In certain streets the houses for a long
distance lean all one way, like trees beaten by a prevailing wind; and
then another long row will lean in the opposite direction, as if the
wind had changed. Sometimes there is a certain regularity of
inclination that is scarcely noticeable; and again, at crossings and in
the smallest streets there is an indescribable confusion of lines, a
real architectural frolic, a dance of houses, a disorder that seems
animated. There are houses that nod forward as if asleep, others
that start backward as if frightened; some bending towards each
other, their roofs almost touching, as if in secret conference; some
falling upon one another as if they were drunk; some leaning
backward between others that lean forward like malefactors dragged
onward by their guards; rows of houses that courtesy to a steeple,
groups of small houses all inclined towards one in the middle, like
conspirators in conclave.
Observe them attentively one by one, from top to bottom, and they
are interesting as pictures.
In some, upon the summit of the façade, there projects from the
middle of the parapet a beam with cord and pulley to pull up baskets
and buckets. In others, jutting from a round window, is the carved
head of a deer, a sheep, or a goat. Under the head, a line of
whitewashed stone or wood cuts the whole façade in half. Under this
line there are two broad windows with projecting awnings of striped
linen. Under these again, over the upper panes, a little green
curtain. Below this green curtain two white ones, divided in the
middle to show a suspended bird-cage or a basket of flowers. And
below the basket or the cage, the lower panes are covered by a net-
work of fine wire that prevents the passer-by from seeing into the
room. Within, behind the netting, there stands a table covered with
objects in porcelain, crystal, flowers, and toys of various kinds.
Outside on the stone sill is a row of small flower-pots. From the
stone sill or from one side projects an iron stem curving upward,
which sustains two small mirrors joined in the form of a book,
movable, and surmounted by another, also movable, so that those
inside the house can see, without being seen, everything that passes
in the street.
On some of the houses there is a lamp projecting between the two
windows, and below is the door of the house or a shop door. If it is a
shop, over the door there is the carved head of a Moor with his
mouth wide open, or that of a Turk with a hideous grimace;
sometimes there is an elephant or a goose; sometimes a horse’s or a
bull’s head, a serpent, a half-moon, a windmill, or an arm extended,
the hand holding some object of the kind sold in the shop. If it is the
house-door,—always kept closed,—there is a brass plate with the
name of the occupant, another with a slit for letters, another with
the handle of a bell, the whole, including the locks and bolts, shining
like gold. Before the door there is a small bridge of wood, because in
many of the houses the ground-floor or basement is much lower
than the street; and before the bridge two little stone columns
surmounted by two balls; two more columns in front of these are
united by iron chains, the large links of which are in the form of
crosses, stars, and polygons; in the space between the street and
the house are pots of flowers; and at the windows of the ground-
floor more flower-pots and curtains. In the more retired streets there
are bird-cages on both sides of the windows, boxes full of green
growing things, clothes hung out to air or dry, a thousand objects
and colors, like a universal fair.
But without going out of the older town, one need only to go away
from the centre to see something new at every step.
In some narrow, straight streets one may see the end suddenly
closed as if by a curtain concealing the view; but it disappears as it
came, and is recognized as the sail of a vessel moving in a canal. In
other streets a net-work of cordage seems to stop the way; the
rigging of vessels lying in some basin. In one direction there is a
drawbridge raised, and looking like a gigantic swing provided for the
diversion of the people who live in those preposterous houses; and
in another there is a windmill, tall as a steeple and black as an
antique tower, moving its arms like a monstrous firework. On every
side, finally, among the houses, above the roofs, between the distant
trees, are seen masts of vessels, flags, and sails and rigging,
reminding us that we are surrounded by water, and that the city is a
seaport.
Meantime, the shops were opened and the streets became full of
people. There was great animation, but no hurry, the absence of
which distinguishes the streets of Rotterdam from those of London,
between which some travellers find great resemblance, especially in
the color of the houses and the grave aspect of the inhabitants.
White faces, pallid faces, faces the color of Parmesan cheese; light
hair, very light hair, reddish, yellowish; broad beardless visages,
beards under the chin and around the neck; blue eyes, so light as to
seem almost without a pupil; women stumpy, fat, rosy, slow, with
white caps and ear-rings in the form of corkscrews,—these are the
first things one observes in the crowd.
But for the moment it was not the people that first stimulated my
curiosity. I crossed the Hoog Street, and found myself in the new
city. Here it is impossible to say if it be port or city, if land or water
predominate, if there are more ships than houses, or vice versa.
Broad and long canals divide the city into so many islands, united by
drawbridges, turning bridges, and bridges of stone. On either side of
every canal extends a street, flanked by trees on one side and
houses on the other. All these canals are deep enough to float large
vessels, and all are full of them from one end to the other, except a
space in the middle left for passage in and out,—an immense fleet
imprisoned in a city.
When I arrived it was the busiest hour, so I planted myself upon the
highest bridge over the principal crossing. From thence were visible
four canals, four forests of ships, bordered by eight files of trees; the
streets were crammed with people and merchandise; droves of
cattle were crossing the bridges; bridges were rising in the air, or
opening in the middle, to allow vessels to pass through, and were
scarcely replaced or closed before they were inundated by a throng
of people, carts, and carriages; ships came and went in the canals,
shining like models in a museum, and with the wives and children of
the sailors on the decks; boats darted from vessel to vessel; the
shops drove a busy trade; servant-women washed the walls and
windows; and all this moving life was rendered more gay and
cheerful by the reflections in the water, the green of the trees, the
red of the houses, the tall windmills showing their dark tops and
white sails against the azure of the sky, and still more by an air of
quiet simplicity not seen in any other northern city.
I took observations of a Dutch vessel. Almost all the ships crowded
in the canals of Rotterdam are built for the Rhine and Holland; they
have one mast only, and are broad, stout, and variously colored like
toy ships. The hull is generally of a bright grass-green, ornamented
with a red or a white stripe, or sometimes several stripes, looking
like a band of different-colored ribbons. The poop is usually gilded.
The deck and mast are varnished and shining like the cleanest of
house-floors. The outside of the hatches, the buckets, the barrels,
the yards, the planks, are all painted red, with white or blue stripes.
The cabin where the sailors’ families are is colored like a Chinese
kiosk, and has its windows of clear glass, and its white muslin
curtains tied up with knots of rose-colored ribbon. In every moment
of spare time sailors, women, and children are busy washing,
sweeping, polishing every part with infinite care and pains; and
when their little vessel makes its exit from the port, all fresh and
shining like a holiday-coach, they all stand on the poop and accept
with dignity the mute compliments which they gather from the
glances of the spectators along the canals.
From canal to canal, and from bridge to bridge, I finally reached the
dyke of the Boompjes upon the Meuse, where boils and bubbles all
the life of the great commercial city.
On the left extends a long row of small many-colored steamboats,
which start every hour in the day for Dordrecht, Arnhem, Gouda,
Schiedam, Brilla, Zealand, and continually send forth clouds of white
smoke and the sound of their cheerful bells. To the right lie the large
ships which make the voyage to various European ports, mingled
with fine three-masted vessels bound for the East Indies, with
names written in golden letters,—Java, Sumatra, Borneo, Samarang,
—carrying the fancy to those distant and savage countries like the
echoes of distant voices. In front the Meuse, covered with boats and
barks, and the distant shore with a forest of beech-trees, windmills,
and towers; and over all the unquiet sky, full of gleams of light and
gloomy clouds, fleeting and changing in their constant movement, as
if repeating the restless labor on the earth below.
ANTWERP AND ITS PEOPLE.
ROSE G. KINGSLEY.
[The traveller to whom we owe the following selection makes it part of a
paper on “The Home of Rubens,” in which she appreciatively describes that
artist’s works. Her account of the city in which the greatest of these works
are enshrined is more to our purpose, and is here given.]

It had rained in England for a month without stopping, when, weary


of sodden gray clouds above and sodden green grass below, M——
and I determined to seek new sketching-grounds under a more
kindly sky. We had but a fortnight to spend on our trip. Where,
therefore, could we find a richer field of work than in Flanders? for
there quaint cities, beautiful buildings, glorious pictures, and, if we
were minded to go deeper, a tangled mass of historic interest, lay
within easy reach.
Thus it came to pass that the 30th of September found us driving
through the streets of Brussels, and three days later we were
steaming out into the (to us) unknown, on our way to Antwerp. Our
three days had been chiefly spent in making closer acquaintance
with Flemish art in the museum of the capital,—a collection most
valuable and typical, a collection too often ignored or hastily glanced
through by the tourist, who, if by chance he cares for such things,
hurries on to see Memling at Bruges, Van Eyck at Ghent, or Rubens
at Antwerp. He forgets, or does not know, that, as Fromentin justly
says, “Belgium is a magnificent book of art, of which, happily for
provincial glory, the chapters are scattered everywhere, but of which
the preface is at Brussels, and only at Brussels. To all who are
tempted to skip the preface in order to get at the book, I should say
they are wrong,—that they open the book too soon and will read it
ill.” We therefore studied the preface with some care, and now were
about to turn the first page of the book itself....
Everything seemed new, pretty, and amusing, as the train cleared
the last of the suburbs of Brussels. The sun shone on the long lines
of poplars, just burnished with autumn’s gold, which cast their
shadows on damp green meadows ruled off into squares with almost
mathematical precision. Here a man in a brown apron and brilliant
crimson sleeves was raking up the aftermath off a water-meadow.
There a girl in a blue frock was herding black and white cows, and
we began to think of Cuyp. Then we saw, across flat stretches of
smiling country, pointed steeples and red roofs, showing behind
thick groups of trees in a soft blue haze, while an old windmill on
blackened wooden stilts, a little donkey-cart, and a group of
crimson-jacketed peasants in the foreground made us think of some
of Teniers the Younger’s landscapes, and recollect that we must be
close to Drei Torren, his house at Perck. Then came Malines, our first
brown canal, with red-sailed, green-and black-painted barges, the
great cathedral rising through a screen of trees over scarlet house-
roofs, a picturesque crowd on the platform of burly shovel-hatted
priests, nuns with black shawls over their white caps, men with blue
blouses and brilliant yellow sabots,—and we thought of Prout. It was
all so absurdly like what we had expected, with a difference,—just
the difference between art and nature.
Then came more flat country, more canals, more fields, more absurd
cocky little wheat-ricks, with hardly corn enough in them to make a
loaf of bread, more white and purple lupins on the embankments,
more red-tiled roofs, half thatch, half tile, which M—— pronounced
“most æsthetic,” more sun, yes, that was perhaps the best of all.
Then a great green fort, and we were at Antwerp.
We hardly gave ourselves time to swallow a hasty déjeûner, and
then set forth with the charming feeling that we had nothing to do
but amuse ourselves. We had not an idea of where we were going,
or what we meant to see. All was new, therefore all to us was worth
seeing. Only a vague impression floated in our minds that we ought
before long to find our way to the cathedral. It was not hard to find;
in fact, it was impossible to miss it, for, as we sauntered down the
Place de Meir, the golden clock-face on the steeple shone before us
like a beacon over the high house-roofs, and
“Far up, the carillon did search
The wind.”

We pushed our way past the odious touters, clamorously asking in


vile French and still viler English if we wished to see the cathedral?
had we seen it? did we know we ought to see it? finally, of course,
should they show it to us? We were in too mighty a presence to
heed them. Above us, almost painfully high, rose the great steeple,
pointing up to the clear blue sky. We stood at a corner of the old
Marché and gazed and gazed, hardly able at first to take in the idea
of its real height, foreshortened as it is when one stands so near. It
grew upon us, revealed itself to us, as we looked and wondered, and
ever after, while in the city, we seemed to feel its protecting
presence, even though it might be hidden from our eyes. And we
thought how often must weary sailors, beating up the stormy waters
of the North Sea, have longed for a glimpse of that weather-stained
tower, token to them of home and safety after some perilous voyage
to bring gold and sugar from the New World, or priceless stuffs and
spices from the Indies and far Cathay! Or as painters, after long
study in the schools of Rome and Venice, made their slow way
northward once more across the Alps, to add fresh glory to the Guild
of St. Luke, how eagerly they must have watched for the first sight
of their cathedral, pointing heavenward out of the flat misty plain, as
if to lift their minds from earth into some purer atmosphere!
Yet, splendid as is the casket, still more precious is the treasure it
contains. Many men have built cathedrals. There has been but one
Rubens; and of all Rubens’s works, the “Descent from the Cross”
enshrined in Antwerp Cathedral is, one may venture to say without
fear of criticism, unquestionably the most wonderful and beautiful.
There is a sobriety, a reticence, about it in color, in movement, in
drawing, in the exquisite balance of light and shade, in the nobility
and yet tenderness of conception, which one hardly looks for in the
painter, splendid though he be, of the Assumption of the Virgin over
the high altar close by, still less of the gorgeous but revolting Marie
de Medici series in the Louvre. To quote Fromentin once more, “Tout
y est contenu, concis, laconique comme dans une page du texte
sacré.” Let those who judge him merely by pictures such as the last
go to Antwerp, and, casting aside all preconceived ideas, say then
whether Peter Paul Rubens shall not be pardoned all his
carelessness, his coarseness,—yes, even his horrors,—and be to
them henceforth the painter of the noble and majestic “Descent
from the Cross.”
It was long before we could summon resolution to leave the
cathedral. Half a dozen times we started, as many times we turned
back to the great triptych to impress some detail more firmly on our
minds; and at last, when the door swung to behind us, and we saw
the great master’s statue standing in dusty sunshine in the Place
Verte, we were in no humor for more sight-seeing. So we wandered
happily and aimlessly on, now enchanted by some pignon espagnol,
the quaint gable running up in a series of steps, which was
introduced, some say, by the Spaniards, now stopping to scribble
down the details of a bit of costume, or to look at a street shrine on
a corner house, with its figure and lamp and tinsel flowers, until at
last we found ourselves on the quays.
Here, where Van Noort, where Rubens, where Jordaens made
studies among the rude fishermen for their pictures of the
Miraculous Draught,—here, where generations of painters from their
day down to our own have loved to dwell upon the changing aspects
of the quiet river, the hurrying quays, the picturesque people,—here
was indeed a spot where we humble disciples of Apelles might hope
to gather inspiration from the example of the great departed. So we
hunted out a pile of wood on the very brink of the river, a quiet
corner where we ran no risk of being trampled underfoot by gigantic
Flemish dray-horses or knocked down by heavily laden wagons; and
there we sat peacefully, sketching the long reaches of the Scheldt
bathed in a flood of golden haze. Up it sailed long low boats, floating
past us with full red sails, flat, faint, wooded shores behind them, a
tall smoking chimney or little church-spire breaking the blue line of
the trees here and there. The river reaches were full of repose to
eye and mind alike, and our thoughts turned instinctively to Van de
Velde, to his glassy water, where little gleams catch the curl of some
lazy ripple, and his skiffs and schooners floating in a veil of filmy
gold, which warms his usual pearly grays, while they in turn give a
sober undertone to the golden glory. A contrast to the quiet river
was the foreground of the picture, where a steamer was lading for
some distant voyage, funnels, rigging, hull, a great mass of black
and brown against the pale golden water, and the bustling quay,
where horses, men, carriages, foot-passengers, long low trollies,—
apparently on only two wheels, so minute were the front pair,—piled
high with bales and barrels, were jumbled in inextricable confusion.

THE WATERLOO PYRAMID


We were working away, thankful that every one was too full of his
own business to care to look at us, when suddenly a pleasant smell
of burning made us wonder whether the municipality were trying to
fumigate the town and overpower the very unsavory odors around
us. Presently blacks began to settle on our sketch-books. Then
burning morsels flew through the air, and, turning round, we saw
that a quantity of bales standing on the quay twenty yards behind us
were on fire. Half a dozen bystanders looked on with true Flemish
phlegm. A woman in blue and gray, with yellow sabots, stood
watching on a fallen mast. Then others began to arrive, and as the
flames rose higher some slight interest arose with them. The gray
woman turned and ran for the pompiers. The interest grew and
spread among the gathering crowd. Soldiers just landing from the
Tête de Flandre caught sight of the crackling flames and rushed
towards them. Stevedores left the lading of their steamer, and,
leaping across masts and spars, with sacks over their heads and
their blue blouses puffed into balloons by the wind, rushed to the
scene of action. M—— and I thought it prudent to retire to a street-
corner, away from the turmoil.
Such a street! all in warm shade, with rich reds and grays and
browns among its high-roofed houses. Out of the Fish-Market close
by poured a motley crowd,—men in blue jerseys, men in red jerkins,
men in shirt-sleeves, little lads in sailor-clothes with bright yellow
sabots, women with yellow sabots and blue stockings, or yellow
stockings and black sabots, or black shoes and pink stockings,
women in three-cornered shawls, women in long black cloaks. The
tardily-awakened interest had grown into intense excitement. Every
one ran,—soldiers, ladies, porters, priests; and as we left the Quai
Vandyck to go home, and looked up at the stone lace-work of the
cathedral tower against the bright blue sky, the pompiers raced past
us with their little hand-engine, to find that the fire had burnt itself
out.
Too tired by our long day to walk any more, but unwilling to waste
the evening in our rooms, we chartered a comfortable little carriage
and drove down to the Port just after sunset. The cathedral tower
stood stately and sombre against a pale-pink sky. Against this
delicate background, too, we caught fantastic irregular outlines of
old houses at every turn of the streets. The busy Quai Vandyck we
now saw under a completely changed aspect. The pink of the upper
sky melted into yellow, the yellow into a heavy blue-purple blending
with the farther shore of the river. The bands of color, intensified by
black masts and sails rising from yet blacker hulls lying under the
bank, were reflected in the opalescent water; while fluttering
pennons on a forest of fishing-boats looked, as M—— said, “like a
shoal of minnows.”
As we drove along in the growing darkness the scene was weird and
strange. We caught glimpses of black figures, with heavy burdens on
their shoulders, rushing up and down gangways of loading steamers
like the demons of some Walpurgisnacht, lighted by oil-cans flaming
from their two spouts. Then came a street of ancient houses,—we
could see only the steps of their gables against the sky,—and,
instead of a roadway below, the street was full of water and ships,
sails half furled, lights, red, green, and yellow, repeating themselves
in long reflections amid the black boats on the smooth surface of the
canal. Across the river steamer-lights crept to and fro. Low carts,
with huge horses that brought to mind Paul Potter’s etching of “The
Friesland Horse,” grazed past us. Then came a black mass,—the
house of the Hanseatic League. Then great docks like the sea,
stretching away infinitely into the darkness, a mysterious confusion
of masts, spars, cordage, chimneys, lights, water, black hulls. On
shore a tangle of carts and trollies standing horseless, barrels,
cotton-bales, wool-sacks. A locomotive snorted past us in dangerous
proximity, appearing one knew not from whence, disappearing again
into the gloom. Electric lights flashed on ahead far up the line. We
passed more huge warehouses, more canals, more narrow streets.
Then the Port and its strange life, its flaming oil-cans, its murky
darkness, were left behind, and we found ourselves back in
nineteenth-century civilization, driving down the new Frenchified
boulevards, with only the statue of David Teniers and the Italian
facade of Rubens’s house to remind us where we were.
ART MUSEUMS OF DRESDEN.
ELIZABETH PEAKE.
[“Pen Pictures of Europe,” by Elizabeth Peake, is amply worth reading by all
who wish to gain a rapid acquaintance with what is worth seeing on that
continent. Its interesting descriptions are so many and varied that choice
among them is not easy to make, and we present what our traveller saw in
Dresden and at Potsdam simply as examples of the whole.]

We have been to the picture-gallery. There were between two and


three thousand pictures. There were Raphael, Holbein, Correggio,
Titian, Carlo Dolce, Paul Veronese, Rubens, Rembrandt, Vandyke,
Guido, Ruysdael, Wouvermans, Claude, Poussin, and I do not know
who else; but I would give them all, and more besides, for the
portraits of Charlemagne and Sigismund by Dürer, and the historical
painting of the peace of Westphalia, with its forty-seven original
portraits by Sandrart. I do really think that I have seen a million of
paintings, and have come to the sad conclusion that I have precious
little love for pictures,—for paintings.
The magnificent frescos I admire as much as any one. But the
thousands of Madonnas,—Raphael’s “Madonna di San Sisto,” which
cost forty thousand dollars, I like better than any I have yet seen,
next to that old painting of Leonardo da Vinci in the old church not
far from Milan,—all the Madonnas have pretty eyes, pretty faces,
pretty attitudes; but they do not come up to my idea of the Virgin.
Then there are so many nude Venuses, and all sorts of nudities, that
the artists who painted them ought to have been condemned to go
without clothes, even in cold weather, to see how they would like it;
and when they died they should have every bone in the human body
carved as ornaments on their tombstone as I saw somewhere in my
travels. The heads of the old men are exceedingly fine and natural;
Welcome to our website – the perfect destination for book lovers and
knowledge seekers. We believe that every book holds a new world,
offering opportunities for learning, discovery, and personal growth.
That’s why we are dedicated to bringing you a diverse collection of
books, ranging from classic literature and specialized publications to
self-development guides and children's books.

More than just a book-buying platform, we strive to be a bridge


connecting you with timeless cultural and intellectual values. With an
elegant, user-friendly interface and a smart search system, you can
quickly find the books that best suit your interests. Additionally,
our special promotions and home delivery services help you save time
and fully enjoy the joy of reading.

Join us on a journey of knowledge exploration, passion nurturing, and


personal growth every day!

ebookbell.com

You might also like