Algorithms
Algorithms
Wikibooks.org
On the 28th of April 2012 the contents of the English as well as German Wikibooks and Wikipedia
projects were licensed under Creative Commons Attribution-ShareAlike 3.0 Unported license. An
URI to this license is given in the list of figures on page 81. If this document is a derived work from
the contents of one of these projects and the content was still licensed by the project under this
license at the time of derivation this document has to be licensed under the same, a similar or a
compatible license, as stated in section 4b of the license. The list of contributors is included in chapter
Contributors on page 77. The licenses GPL, LGPL and GFDL are included in chapter Licenses on
page 85, since this book and/or parts of it may or may not be licensed under one or more of these
licenses, and thus require inclusion of these licenses. The licenses of the figures are given in the list
of figures on page 81. This PDF was generated by the LATEX typesetting software. The LATEX source
code is included as an attachment (source.7z.txt) in this PDF file. To extract the source from the
PDF file, we recommend the use of http://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/
utility or clicking the paper clip attachment symbol on the lower left of your PDF Viewer, selecting
Save Attachment. After extracting it from the PDF file you have to rename it to source.7z. To
uncompress the resulting archive we recommend the use of http://www.7-zip.org/. The LATEX
source itself was generated by a program written by Dirk Hnniger, which is freely available under
an open source license from http://de.wikibooks.org/wiki/Benutzer:Dirk_Huenniger/wb2pdf.
This distribution also contains a configured version of the pdflatex compiler with all necessary
packages and fonts needed to compile the LATEX source included in this PDF file.
Contents
1
.
.
.
.
.
.
3
3
4
4
7
8
9
Mathematical Background
2.1 Asymptotic Notation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.2 Algorithm Analysis: Solving Recurrence Equations . . . . . . . . . . . . . .
2.3 Amortized Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
11
15
17
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
19
19
22
24
27
29
30
32
Randomization
4.1 Ordered Statistics
4.2 Quicksort . . . . .
4.3 Shuffling an Array
4.4 Equal Multivariate
4.5 Skip Lists . . . . .
4.6 Treaps . . . . . . .
4.7 Derandomization .
4.8 Exercises . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
35
35
39
40
40
40
43
43
44
Backtracking
5.1 Longest Common Subsequence (exhaustive version)
5.2 Shortest Path Problem (exhaustive version) . . . . .
5.3 Largest Independent Set . . . . . . . . . . . . . . . .
5.4 Bounding Searches . . . . . . . . . . . . . . . . . . .
5.5 Constrained 3-Coloring . . . . . . . . . . . . . . . .
5.6 Traveling Salesperson Problem . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
45
45
47
48
48
48
48
Introduction
1.1 Prerequisites . . . . . . . . . .
1.2 When is Efficiency Important?
1.3 Inventing an Algorithm . . . .
1.4 Understanding an Algorithm .
1.5 Overview of the Techniques . .
1.6 Algorithm and code example .
.
.
.
.
.
.
. . . . . . . .
. . . . . . . .
. . . . . . . .
Polynomials
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
III
Contents
6
.
.
.
.
49
49
53
53
56
Greedy Algorithms
7.1 Event Scheduling Problem . . . . . . . . . . . . . . . . . . . . . . . . . . .
7.2 Dijkstra's Shortest Path Algorithm . . . . . . . . . . . . . . . . . . . . . . .
7.3 Minimum spanning tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
57
59
60
Hill
8.1
8.2
8.3
8.4
.
.
.
.
61
62
63
67
67
Ada Implementation
9.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9.2 Chapter 1: Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9.3 Chapter 6: Dynamic Programming . . . . . . . . . . . . . . . . . . . . . . .
69
69
69
70
Dynamic Programming
6.1 Fibonacci Numbers . . . . . . . . . . . . . .
6.2 Longest Common Subsequence (DP version)
6.3 Matrix Chain Multiplication . . . . . . . . .
6.4 Parsing Any Context-Free Grammar . . . . .
Climbing
Newton's Root Finding Method
Network Flow . . . . . . . . . .
The Ford-Fulkerson Algorithm .
Applications of Network Flow . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
10 Contributors
77
List of Figures
81
11 Licenses
11.1 GNU GENERAL PUBLIC LICENSE . . . . . . . . . . . . . . . . . . . . .
11.2 GNU Free Documentation License . . . . . . . . . . . . . . . . . . . . . . .
11.3 GNU Lesser General Public License . . . . . . . . . . . . . . . . . . . . . .
85
85
86
87
1 Introduction
This book covers techniques for the design and analysis of algorithms. The algorithmic
techniques covered include: divide and conquer, backtracking, dynamic programming, greedy
algorithms, and hill-climbing.
Any solvable problem generally has at least one algorithm of each of the following types:
1.
2.
3.
4.
the
the
the
the
obvious way;
methodical way;
clever way; and
miraculous way.
On the first and most basic level, the "obvious" solution might try to exhaustively search for
the answer. Intuitively, the obvious solution is the one that comes easily if you're familiar
with a programming language and the basic problem solving techniques.
The second level is the methodical level and is the heart of this book: after understanding
the material presented here you should be able to methodically turn most obvious algorithms
into better performing algorithms.
The third level, the clever level, requires more understanding of the elements involved in
the problem and their properties or even a reformulation of the algorithm (e.g., numerical
algorithms exploit mathematical properties that are not obvious). A clever algorithm may be
hard to understand by being non-obvious that it is correct, or it may be hard to understand
that it actually runs faster than what it would seem to require.
The fourth and final level of an algorithmic solution is the miraculous level: this is reserved
for the rare cases where a breakthrough results in a highly non-intuitive solution.
Naturally, all of these four levels are relative, and some clever algorithms are covered in this
book as well, in addition to the methodical techniques. Let's begin.
1.1 Prerequisites
To understand the material presented in this book you need to know a programming language
well enough to translate the pseudocode in this book into a working solution. You also need
to know the basics about the following data structures: arrays, stacks, queues, linked-lists,
trees, heaps (also called priority queues), disjoint sets, and graphs.
Additionally, you should know some basic algorithms like binary search, a sorting algorithm
(merge sort, heap sort, insertion sort, or others), and breadth-first or depth-first search.
Introduction
If you are unfamiliar with any of these prerequisites you should review the material in the
Data Structures1 book first.
http://en.wikibooks.org/wiki/Computer%20Science%3AData%20Structures
Inventing an Algorithm
What first comes to your mind when you think about solving this problem? Perhaps these
two considerations crossed your mind:
1. Every character in str needs to be looked at
2. A routine for converting a single character to lower case is required
The first point is "obvious" because a character that needs to be converted might appear
anywhere in the string. The second point follows from the first because, once we consider
each character, we need to do something with it. There are many ways of writing the
tolower function for characters:
function tolower(character c): character
http://en.wikibooks.org/wiki/Ada_Programming%2FAlgorithms%23To_Lower
The loop is the result of our ability to translate "every character needs to be looked at"
into our native programming language. It became obvious that the tolower subroutine call
should be in the loop's body. The final step required to bring the high-level task into an
implementation was deciding how to build the resulting string. Here, we chose to start with
the empty string and append characters to the end of it.
Introduction
Now suppose you want to write a function for comparing two strings that tests if they are
equal, ignoring case:
// equal-ignore-case -- returns true if s and t are equal, ignoring case
function equal-ignore-case(string s, string t): boolean
http://en.wikibooks.org/wiki/Ada_Programming%2FAlgorithms%23Equal_Ignore_Case
Or, if you thought of the problem in terms of functional decomposition instead of iterations,
you might have thought of a function more like this:
// equal-ignore-case -- returns true if s or t are equal, ignoring case
function equal-ignore-case(string s, string t): boolean
return tolower(s).equals(tolower(t))
end
Alternatively, you may feel neither of these solutions is efficient enough, and you would
prefer an algorithm that only ever made one pass of s or t. The above two implementations
each require two-passes: the first version computes the lengths and then compares each
character, while the second version computes the lowercase versions of the string and then
compares the results to each other. (Note that for a pair of strings, it is also possible to
have the length precomputed to avoid the second pass, but that can have its own drawbacks
Understanding an Algorithm
at times.) You could imagine how similar routines can be written to test string equality
that not only ignore case, but also ignore accents.
Already you might be getting the spirit of the pseudocode in this book. The pseudocode
language is not meant to be a real programming language: it abstracts away details that
you would have to contend with in any language. For example, the language doesn't assume
generic types or dynamic versus static types: the idea is that it should be clear what is
intended and it should not be too hard to convert it to your native language. (However, in
doing so, you might have to make some design decisions that limit the implementation to
one particular type or form of data.)
There was nothing special about the techniques we used so far to solve these simple string
problems: such techniques are perhaps already in your toolbox, and you may have found
better or more elegant ways of expressing the solutions in your programming language of
choice. In this book, we explore general algorithmic techniques to expand your toolbox
even further. Taking a naive algorithm and making it more efficient might not come so
immediately, but after understanding the material in this book you should be able to
methodically apply different solutions, and, most importantly, you will be able to ask
yourself more questions about your programs. Asking questions can be just as important as
answering questions, because asking the right question can help you reformulate the problem
and think outside of the box.
To understand this example, you need to know that integer division uses truncation and
therefore when the if-condition is true then the least-significant bit in a is zero (which means
that a must be even). Additionally, the code uses a string printing API and is itself the
definition of a function to be used by different modules. Depending on the programming task,
you may think on the layer of hardware, on down to the level of processor branch-prediction
or the cache.
Often an understanding of binary is crucial, but many modern languages have abstractions
far enough away "from the hardware" that these lower-levels are not necessary. Somewhere
the abstraction stops: most programmers don't need to think about logic gates, nor is
the physics of electronics necessary. Nevertheless, an essential part of programming is
multiple-layer thinking.
But stepping away from computer programs toward algorithms requires another layer:
mathematics. A program may exploit properties of binary representations. An algorithm
Introduction
can exploit properties of set theory or other mathematical constructs. Just as binary itself is
not explicit in a program, the mathematical properties used in an algorithm are not explicit.
Typically, when an algorithm is introduced, a discussion (separate from the code) is needed
to explain the mathematics used by the algorithm. For example, to really understand a
greedy algorithm (such as Dijkstra's algorithm) you should understand the mathematical
properties that show how the greedy strategy is valid for all cases. In a way, you can think
of the mathematics as its own kind of subroutine that the algorithm invokes. But this
"subroutine" is not present in the code because there's nothing to call. As you read this
book try to think about mathematics as an implicit subroutine.
1.6.2 Level 2
1. Talking to computer Lv 18 With algorithm and several different programming languages
2. Sorting-bubble sort9 With algorithm and several different programming languages
3.
1.6.3 Level 3
1. Talking to computer Lv 210 With algorithm and several different programming languages
2
3
4
5
6
7
8
9
10
http://en.wikibooks.org/wiki/..%2FFind%20maximum
http://en.wikibooks.org/wiki/..%2FFind%20minimum
http://en.wikibooks.org/wiki/..%2FFind%20average
http://en.wikibooks.org/wiki/..%2FFind%20mode
http://en.wikibooks.org/wiki/..%2FFind%20total
http://en.wikibooks.org/wiki/..%2FCounting
http://en.wikibooks.org/wiki/..%2FTalking%20to%20computer%20Lv%201
http://en.wikibooks.org/wiki/..%2FSorting-bubble%20sort
http://en.wikibooks.org/wiki/..%2FTalking%20to%20computer%20Lv%202
Introduction
1.6.4 Level 4
1. Talking to computer Lv 311 With algorithm and several different programming languages
2. Find approximate maximum12
languages
1.6.5 Level 5
1. Quicksort13
11
12
13
10
http://en.wikibooks.org/wiki/..%2FTalking%20to%20computer%20Lv%203
http://en.wikibooks.org/wiki/..%2FFind%20approximate%20maximum
http://en.wikibooks.org/wiki/Algorithm_Implementation%2FSorting%2FQuicksort
2 Mathematical Background
Before we begin learning algorithmic techniques, we take a detour to give ourselves some
necessary mathematical tools. First, we cover mathematical definitions of terms that are used
later on in the book. By expanding your mathematical vocabulary you can be more precise
and you can state or formulate problems more simply. Following that, we cover techniques
for analysing the running time of an algorithm. After each major algorithm covered in this
book we give an analysis of its running time as well as a proof of its correctness
11
Mathematical Background
on its inputs. Concretely, consider these two functions, representing the computation time
required for each size of input dataset:
f (n) = n3 12n2 + 20n + 110
g(n) = n3 + n2 + 5n + 5
They look quite different, but how do they behave? Let's look at a few plots of the function
(f (n) is in red, g(n) in blue):
Figure 1
5
Figure 2
15
Figure 3
100
Figure 4
1000
In the first, very-limited plot the curves appear somewhat different. In the second plot they
start going in sort of the same way, in the third there is only a very small difference, and at
last they are virtually identical. In fact, they approach n3 , the dominant term. As n gets
larger, the other terms become much less significant in comparison to n3 .
As you can see, modifying a polynomial-time algorithm's low-order coefficients doesn't help
much. What really matters is the highest-order coefficient. This is why we've adopted a
notation for this kind of analysis. We say that:
f (n) = n3 12n2 + 20n + 110 = O(n3 )
We ignore the low-order terms. We can say that:
12
Asymptotic Notation
n4 = O(n4 )
If we use the equal sign as an equality we can get very strange results, such as:
n3 = n4
which is obviously nonsense. This is why the set-definition is handy. You can avoid these
things by thinking of the equal sign as a one-way equality, i.e.:
n3 = O(n4 )
does not imply
13
Mathematical Background
O(n4 ) = n3
Always keep the O on the right hand side.
14
2.2.2 Summations
[TODO: show the closed forms of commonly needed summations and prove them]
T (n) = aT
15
Mathematical Background
for a 1, b > 1 and k 0. Here, a is the number of recursive calls made per call to the
function, n is the input size, b is how much smaller the input gets, and k is the polynomial
order of an operation that occurs each time the function is called (except for the base cases).
For example, in the merge sort algorithm covered later, we have
n
+ O(n)
2
T (n) = 2T
because two subproblems are called for each non-base case iteration, and the size of the
array is divided in half each time. The O(n) at the end is the "conquer" part of this divide
and conquer algorithm: it takes linear time to merge the results from the two recursive calls
into the final result.
Thinking of the recursive calls of T as forming a tree, there are three possible cases to
determine where most of the algorithm is spending its time ("most" in this sense is concerned
with its asymptotic behaviour):
1. the tree can be top heavy, and most time is spent during the initial calls near the
root;
2. the tree can have a steady state, where time is spread evenly; or
3. the tree can be bottom heavy, and most time is spent in the calls near the leaves
Depending upon which of these three states the tree is in T will have different complexities:
The Master Theorem
Given T (n) = aT
n
b
T (n) = 2T
we have
a = 2, b = 2, k = 1=bk = 2
thus, a = bk and so this is also in the "steady state": By the master theorem, the complexity
of merge sort is thus
T (n) = O(n1 log n) = O(n log n)
16
Amortized Analysis
17
The first algorithm we'll present using this methodology is the merge sort.
Following the divide and conquer methodology, how can a be broken up into smaller
subproblems? Because a is an array of n elements, we might want to start by breaking the
array into two arrays of size n/2 elements. These smaller arrays will also be unsorted and it
is meaningful to sort these smaller problems; thus we can consider these smaller arrays
"similar". Ignoring the base case for a moment, this reduces the problem into a different
one: Given two sorted arrays, how can they be combined to form a single sorted array that
contains all the elements of both given arrays:
19
So far, following the methodology has led us to this point, but what about the base case?
The base case is the part of the algorithm concerned with what happens when the problem
cannot be broken into smaller subproblems. Here, the base case is when the array only has
one element. The following is a sorting algorithm that faithfully sorts arrays of only zero or
one elements:
// base-sort -- given an array of one element (or empty), return a copy of the
// array sorted
function base-sort(array a[1..n]): array
assert (n <= 1)
return a.copy()
end
Putting this together, here is what the methodology has told us to write so far:
// sort -- returns a sorted copy of array a
function sort(array a[1..n]): array
if n <= 1: return a.copy()
else:
let sub_size := n / 2
let first_half := sort(a[1,..,sub_size])
let second_half := sort(a[sub_size + 1,..,n])
return merge(first_half, second_half)
fi
end
And, other than the unimplemented merge subroutine, this sorting algorithm is done!
Before we cover how this algorithm works, here is how merge can be written:
// merge -- given a and b (assumed to be sorted) returns a merged array that
// preserves order
function merge(array a[1..n], array b[1..m]): array
let result := new array[n + m]
let i, j := 1
for k := 1 to n + m:
if i >= n: result[k] := b[j]; j += 1
else-if j >= m: result[k] := a[i]; i += 1
else:
if a[i] < b[j]:
result[k] := a[i]; i += 1
else:
result[k] := b[j]; j += 1
fi
fi
repeat
end
20
Merge Sort
[TODO: how it works; including correctness proof] This algorithm uses the fact that, given
two sorted arrays, the smallest element is always in one of two places. It's either at the head
of the first array, or the head of the second.
3.1.1 Analysis
Let T (n) be the number of steps the algorithm takes to run on input of size n.
Merging takes linear time and we recurse each time on two sub-problems of half the original
size, so
n
+ O(n).
2
T (n) = 2 T
By the master theorem, we see that this recurrence has a "steady state" tree. Thus, the
runtime is:
T (n) = O(n log n).
This works because each sublist of length 1 in the array is, by definition, sorted. Each
iteration through the array (using counting variable i) doubles the size of sorted sublists by
21
Note that all recursive calls made are tail-calls, and thus the algorithm is iterative. We
can explicitly remove the tail-calls if our programming language does not do that for us
already by turning the argument values passed to the recursive call into assignments, and
22
Binary Search
then looping to the top of the function body again:
// binary-search -- returns the index of value in the given array, or
// -1 if value cannot be found. Assumes array is sorted in ascending order
function binary-search(value, array A[1,..n]): integer
let start := 1
let end := n + 1
loop:
if start == end: return -1 fi
// not found
Even though we have an iterative algorithm, it's easier to reason about the recursive version.
If the number of steps the algorithm takes is T (n), then we have the following recurrence
that defines T (n):
n
+ O(1).
2
T (n) = 1 T
The size of each recursive call made is on half of the input size (n), and there is a constant
amount of time spent outside of the recursion (i.e., computing length and mid will take the
same amount of time, regardless of how many elements are in the array). By the master
theorem, this recurrence has values a = 1, b = 2, k = 0, which is a "steady state" tree, and
thus we use the steady state case that tells us that
T (n) = (nk log n) = (log n).
Thus, this algorithm takes logarithmic time. Typically, even when n is large, it is safe to let
the stack grow by log n activation records through recursive calls.
difficulty in initially correct binary search implementations
The article on wikipedia on Binary Search also mentions the difficulty in writing a correct
binary search algorithm: for instance, the java Arrays.binarySearch(..) overloaded function
implementation does an interative binary search which didn't work when large integers
overflowed a simple expression of mid calculation mid = ( end + start) / 2 i.e. end + start
> max_positive_integer . Hence the above algorithm is more correct in using a length =
23
1
2
24
http://en.wikibooks.org/wiki/boundary%20values
A (mathematical) integer larger than the largest "int" directly supported by your computer's hardware
is often called a "BigInt". Working with such large numbers is often called "multiple precision arithmetic".
There are entire books on the various algorithms for dealing with such numbers, such as:
Modern Computer Arithmetic {http://www.loria.fr/~zimmerma/mca/pub226.html} , Richard Brent
and Paul Zimmermann, Cambridge University Press, 2010.
Donald E. Knuth, The Art of Computer Programming , Volume 2: Seminumerical Algorithms (3rd
edition), 1997.
People who implement such algorithms may
write a one-off implementation for one particular application
write a library that you can use for many applications, such as GMP, the GNU Multiple Precision Arithmetic Library {http://gmplib.org/} or McCutchen's Big Integer Library {https:
//mattmccutchen.net/bigint/} or various libraries http://www.leemon.com/crypto/BigInt.html
https://github.com/jasondavies/jsbn https://github.com/libtom/libtomcrypt http://www.gnu.
org/software/gnu-crypto/ http://www.cryptopp.com/ used to demonstrate RSA encryption
put those algorithms in the compiler of a programming language that you can use (such as Python and
Lisp) that automatically switches from standard integers to BigInts when necessary
Integer Multiplication
easier in binary, because we only multiply by 1 or 0:
x6 x5 x4 x3 x2 x1 x0
y6 y5 y4 y3 y2 y1 y0
----------------------x6 x5 x4 x3 x2 x1 x0 (when
x6 x5 x4 x3 x2 x1 x0 0 (when
x6 x5 x4 x3 x2 x1 x0 0 0 (when
x6 x5 x4 x3 x2 x1 x0 0 0 0 (when
... et cetera
y0
y1
y2
y3
is
is
is
is
1;
1;
1;
1;
0
0
0
0
otherwise)
otherwise)
otherwise)
otherwise)
The subroutine add adds two binary integers and returns the result, and the subroutine
pad adds an extra digit to the end of the number (padding on a zero is the same thing as
shifting the number to the left; which is the same as multiplying it by two). Here, we loop n
times, and in the worst-case, we make n calls to add. The numbers given to add will at
most be of length 2n. Further, we can expect that the add subroutine can be done in linear
time. Thus, if n calls to a O(n) subroutine are made, then the algorithm takes O(n2 ) time.
25
xh
xl
yh
yl
let
let
let
let
a
b
c
d
:=
:=
:=
:=
:=
:=
:=
:=
x[n/2 + 1,
x[0, .., n
y[n/2 + 1,
y[0, .., n
multiply(xh,
multiply(xh,
multiply(xl,
multiply(xl,
b := add(b, c)
a := shift(a, n)
b := shift(b, n/2)
return add(a, b, d)
end
.., n]
/ 2]
.., n]
/ 2]
yh)
yl)
yh)
yl)
//
//
//
//
array
array
array
array
slicing,
slicing,
slicing,
slicing,
//
//
//
//
recursive
recursive
recursive
recursive
//
//
//
//
call;
call;
call;
call;
O(n)
O(n)
O(n)
O(n)
T(n/2)
T(n/2)
T(n/2)
T(n/2)
We can use the master theorem to analyze the running time of this algorithm. Assuming
that the algorithm's running time is T (n), the comments show how much time each step
takes. Because there are four recursive calls, each with an input of size n/2, we have:
T (n) = 4T (n/2) + O(n)
Here, a = 4, b = 2, k = 1, and given that 4 > 21 we are in the "bottom heavy" case and thus
plugging in these values into the bottom heavy case of the master theorem gives us:
T (n) = O(nlog2 4 ) = O(n2 ).
Thus, after all of that hard work, we're still no better off than the grade school algorithm!
Luckily, numbers and polynomials are a data set we know additional information about. In
fact, we can reduce the running time by doing some mathematical tricks.
First, let's replace the 2n/2 with a variable, z:
x y = xh yh z 2 + (xh yl + xl yh )z + xl yl
This appears to be a quadratic formula, and we know that you only need three co-efficients
or points on a graph in order to uniquely describe a quadratic formula. However, in our
above algorithm we've been using four multiplications total. Let's try recasting x and y as
linear functions:
26
Base Conversion
Px (z) = xh z + xl
Py (z) = yh z + yl
Now, for x y we just need to compute (Px Py )(2n/2 ). We'll evaluate Px (z) and Py (z) at
three points. Three convenient points to evaluate the function will be at (Px Py )(1), (Px
Py )(0), (Px Py )(1):
[TODO: show how to make the two-parts breaking more efficient; then mention that the
best multiplication uses the FFT, but don't actually cover that topic (which is saved for the
advanced book)]
27
This is virtually a working Java function and it would look very much the same in C++
and require only a slight modification for C. As you see, at some point the function calls
itself with a different first argument. One may say that the function is defined in terms of
itself. Such functions are called recursive. (The best known recursive function is factorial:
n!=n*(n-1)!.) The function calls (applies) itself to its arguments, and then (naturally)
applies itself to its new arguments, and then ... and so on. We can be sure that the process
will eventually stop because the sequence of arguments (the first ones) is decreasing. Thus
sooner or later the first argument will be less than the second and the process will start
emerging from the recursion, still a step at a time.
28
The function is by far longer than its recursive counterpart; but, as I said, sometimes it's
the one you want to use, and sometimes it's the only one you may actually use.
29
30
so,
T (n) = 2T (n/2) + O(n)
which, according the Master Theorem, result
31
3.7.1 Rules
1) Only one disc can be moved in each step.
2) Only the disc at the top can be moved.
3) Any disc can only be placed on the top of a larger disc.
3.7.2 Solution
Intuitive Idea
In order to move the largest disc from the left peg to the middle peg, the smallest discs
must be moved to the right peg first. After the largest one is moved. The smaller discs are
then moved from the right peg to the middle peg.
32
Analysis
The analysis is trivial. T (n) = 2T (n 1) + O(1) = O(2n )
33
4 Randomization
As deterministic algorithms are driven to their limits when one tries to solve hard problems
with them, a useful technique to speed up the computation is randomization. In randomized
algorithms, the algorithm has access to a random source, which can be imagined as tossing
coins during the computation. Depending on the outcome of the toss, the algorithm may
split up its computation path.
There are two main types of randomized algorithms: Las Vegas algorithms and Monte-Carlo
algorithms. In Las Vegas algorithms, the algorithm may use the randomness to speed up
the computation, but the algorithm must always return the correct answer to the input.
Monte-Carlo algorithms do not have the former restriction, that is, they are allowed to give
wrong return values. However, returning a wrong return value must have a small probability,
otherwise that Monte-Carlo algorithm would not be of any use.
Many approximation algorithms use randomization.
4.1.1 find-max
First, it's relatively straightforward to find the largest element:
// find-max -- returns the maximum element
function find-max(array vals[1..n]): element
let result := vals[1]
for i from 2 to n:
result := max(result, vals[i])
repeat
return result
end
35
Randomization
An initial assignment of to result would work as well, but this is a useless call to the
max function since the first element compared gets set to result. By initializing result
as such the function only requires n-1 comparisons. (Moreover, in languages capable of
metaprogramming, the data type may not be strictly numerical and there might be no good
way of assigning ; using vals[1] is type-safe.)
A similar routine to find the minimum element can be done by calling the min function
instead of the max function.
4.1.2 find-min-max
But now suppose you want to find the min and the max at the same time; here's one solution:
// find-min-max -- returns the minimum and maximum element of the given array
function find-min-max(array vals): pair
return pair {find-min(vals), find-max(vals)}
end
Because find-max and find-min both make n-1 calls to the max or min functions (when
vals has n elements), the total number of comparisons made in find-min-max is 2n 2.
However, some redundant comparisons are being made. These redundancies can be removed
by "weaving" together the min and max functions:
// find-min-max -- returns the minimum and maximum element of the given array
function find-min-max(array vals[1..n]): pair
let min :=
let max :=
if n is odd:
min := max := vals[1]
vals := vals[2,..,n]
n := n - 1
fi
for i:=1 to n by 2:
// consider pairs of values in vals
if vals[i] < vals[i + n by 2]:
let a := vals[i]
let b := vals[i + n by 2]
else:
let a := vals[i + n by 2]
let b := vals[i]
// invariant: a <= b
fi
if a < min: min := a fi
if b > max: max := b fi
repeat
return pair {min, max}
end
Here, we only loop n/2 times instead of n times, but for each iteration we make three
comparisons. Thus, the number of comparisons made is (3/2)n = 1.5n, resulting in a 3/4
speed up over the original algorithm.
36
Ordered Statistics
Only three comparisons need to be made instead of four because, by construction, it's always
the case that a b. (In the first part of the "if", we actually know more specifically that
a < b, but under the else part, we can only conclude that a b.) This property is utilized by
noting that a doesn't need to be compared with the current maximum, because b is already
greater than or equal to a, and similarly, b doesn't need to be compared with the current
minimum, because a is already less than or equal to b.
In software engineering, there is a struggle between using libraries versus writing customized
algorithms. In this case, the min and max functions weren't used in order to get a faster findmin-max routine. Such an operation would probably not be the bottleneck in a real-life
program: however, if testing reveals the routine should be faster, such an approach should be
taken. Typically, the solution that reuses libraries is better overall than writing customized
solutions. Techniques such as open implementation and aspect-oriented programming may
help manage this contention to get the best of both worlds, but regardless it's a useful
distinction to recognize.
4.1.3 find-median
Finally, we need to consider how to find the median value. One approach is to sort the
array then extract the median from the position vals[n/2]:
// find-median -- returns the median element of vals
function find-median(array vals[1..n]): element
assert (n > 0)
sort(vals)
return vals[n / 2]
end
If our values are not numbers close enough in value (or otherwise cannot be sorted by a
radix sort) the sort above is going to require O(n log n) steps.
However, it is possible to extract the nth-ordered statistic in O(n) time. The key is
eliminating the sort: we don't actually require the entire array to be sorted in order to find
the median, so there is some waste in sorting the entire array first. One technique we'll use
to accomplish this is randomness.
Before presenting a non-sorting find-median function, we introduce a divide and conquerstyle operation known as partitioning. What we want is a routine that finds a random
element in the array and then partitions the array into three parts:
1. elements that are less than or equal to the random element;
2. elements that are equal to the random element; and
3. elements that are greater than or equal to the random element.
These three sections are denoted by two integers: j and i. The partitioning is performed "in
place" in the array:
// partition -- break the array three partitions based on a randomly picked element
function partition(array vals): pair{j, i}
37
Randomization
Note that when the random element picked is actually represented three or more times in
the array it's possible for entries in all three partitions to have the same value as the random
element. While this operation may not sound very useful, it has a powerful property that
can be exploited: When the partition operation completes, the randomly picked element
will be in the same position in the array as it would be if the array were fully sorted!
This property might not sound so powerful, but recall the optimization for the find-minmax function: we noticed that by picking elements from the array in pairs and comparing
them to each other first we could reduce the total number of comparisons needed (because
the current min and max values need to be compared with only one value each, and not
two). A similar concept is used here.
While the code for partition is not magical, it has some tricky boundary cases:
// partition -- break the array into three ordered partitions from a random element
function partition(array vals): pair{j, i}
let m := 0
let n := vals.length - 1
let irand := random(m, n)
// returns any value from m to n
let x := vals[irand]
// values in vals[n..] are greater than x
// values in vals[0..m] are less than x
while (m <= n)
if vals[m] <= x
m++
else
swap(m,n)
// exchange vals[n] and vals[m]
n-endif
endwhile
// partition: [0..m-1]
[]
[n+1..]
note that m=n+1
// if you need non empty sub-arrays:
swap(irand,n)
// partition: [0..n-1]
[n..n]
[n+1..]
end
38
Quicksort
One consideration that might cross your mind is "is the random call really necessary?"
For example, instead of picking a random pivot, we could always pick the middle element
instead. Given that our algorithm works with all possible arrays, we could conclude that the
running time on average for all of the possible inputs is the same as our analysis that used
the random function. The reasoning here is that under the set of all possible arrays, the
middle element is going to be just as "random" as picking anything else. But there's a pitfall
in this reasoning: Typically, the input to an algorithm in a program isn't random at all.
For example, the input has a higher probability of being sorted than just by chance alone.
Likewise, because it is real data from real programs, the data might have other patterns in
it that could lead to suboptimal results.
To put this another way: for the randomized median finding algorithm, there is a very
small probability it will run suboptimally, independent of what the input is; while for a
deterministic algorithm that just picks the middle element, there is a greater chance it will
run poorly on some of the most frequent input types it will receive. This leads us to the
following guideline:
Randomization Guideline:
If your algorithm depends upon randomness, be sure you introduce the randomness
yourself instead of depending upon the data to be random.
Note that there are "derandomization" techniques that can take an average-case fast algorithm
and turn it into a fully deterministic algorithm. Sometimes the overhead of derandomization
is so much that it requires very large datasets to get any gains. Nevertheless, derandomization
in itself has theoretical value.
The randomized find algorithm was invented by C. A. R. "Tony" Hoare. While Hoare is
an important figure in computer science, he may be best known in general circles for his
quicksort algorithm, which we discuss in the next section.
4.2 Quicksort
The median-finding partitioning algorithm in the previous section is actually very close to
the implementation of a full blown sorting algorithm. Until this section is written, building
a Quicksort Algorithm is left as an exercise for the reader.
[TODO: Quicksort Algorithm]
39
Randomization
40
Skip Lists
He then goes to assert that a 2 node can be modeled as binary node which has a black link
from its parent, and a black link to each of its two children, and a link's color is carried by
a color attribute on the child node of the link; and a 3-node is modeled as a binary node
which has a red link between a child to a parent node whose other child is marked black,
and the parent itself is marked black ; hence the 2 nodes of a 3-node is the parent red-linked
to the red child. A 4-node which must be split, then becomes any 3 node combination which
has 2 red links, or 2 nodes marked red, and one node , a top most parent, marked black.
The 4 node can occur as a straight line of red links, between grandparent, red parent, and
red grand child, a zig-zag, or a bow , black parent, with 2 red children, but everything gets
converted to the last in order to simplify splitting of the 4-node.
To make calculations easier, if the red child is on the right of the parent, then a rotation
should be done to make the relationship a left child relationship. This left rotation1 is done
by saving the red child's left child, making the parent the child's left child, the child's old
left child the parents right child ( formerly the red child), and the old parent's own parent
the parent of the red child (by returning a reference to the old red child instead of the old
parent), and then making the red child black, and the old parent red. Now the old parent
has become a left red child of the former red child, but the relationship is still the two same
keys making up the 3-node. When an insertion is done, the node to be inserted is always
red, to model the 2-3 tree behavior of always adding to an existing node initially ( a 2-node
with all black linked children marked black, where terminal null links are black, will become
a 3-node with a new red child).
Again, if the red child is on the right, a left rotation is done. It then suffices to see if the
parent of the newly inserted red node is red (actually, Sedgewick does this as a recursive
post insertion check, as each recursive function returns and the tree is walked up again, he
checks for a node whose left child is red, and whose left child has a red left child ) ; and
if so, a 4-node exists, because there are two red links together. Since all red children have
been rotated to be left children, it suffices to right rotate at the parent's parent, in order to
make the middle node of the 4- node the ancestor linked node, and splitting of the 4 node
and passing up the middle node is done by then making left and right nodes black, and the
center node red, (which is equivalent to making the end keys of a 4-node into two 2-nodes,
and the middle node passed up and merged with node above it). Then , the above process
beginning with "if the red child is on the right of the parent ..." should then be carried out
recursively with the red center node, until all red nodes are left child's , and the parent is
not marked red.
The main operation of rotation is probably only marginally more difficult than understanding
insertion into a singly linked list, which is what skip lists are built on , except that the
nodes to be inserted have a variable height.
http://en.wikibooks.org/wiki/..%2FLeft%20rotation
41
Randomization
referenced as the header of the skip list, and must be as high as the highest node in the skip
list , because an element at a given index(height) only point to another element at the same
index in another node, and hence is only reachable if the header node has the given level
(height).
In order to get a height of a certain level n, a loop is iterated n times where a random
function has successful generated a value below a certain threshold on n iterations. So for
an even chance threshold of 0.5, and a random function generating 0 to 1.0, to achieve a
height of 4, it would be 0.5 4 total probability or (1/2)4 = 1/16. Therefore, high nodes
are much less common than short nodes , which have a probability of 0.5 of the threshold
succeeding the first time.
Insertion of a newly generated node of a randomized height, begins with a search at the
highest level of the skip list's node, or the highest level of the inserting node, whichever is
smaller. Once the position is found, if the node has an overall height greater than the skip
list header, the skip list header is increased to the height of the excess levels, and all the
new level elements of the header node are made to point to the inserting node ( with the
usual rule of pointing only to elements of the same level).
Beginning with header node of the skip list ,a search is made as in an ordered linked list
for where to insert the node. The idea is to find the last node which has a key smaller
than insertion key by finding a next node's key greater than the inserting key; and this last
smaller node will be the previous node in a linked list insertion. However, the previous node
is different at different levels, and so an array must be used to hold the previous node at
each level. When the smallest previous node is found, search is recommenced at the next
level lower, and this continues until the lowest level . Once the previous node is known for
all levels of the inserting node, the inserting node's next pointer at each level must be made
to point to the next pointer of the node in the saved array at the same level . Then all the
nodes in the array must have their next pointer point to the newly inserted node. Although
it is claimed to be easier to implement, there is two simultaneous ideas going on, and the
locality of change is greater than say just recursively rotating tree nodes, so it is probably
easier to implement, if the original paper by Pugh is printed out and in front of you, and
you copy the skeleton of the spelled out algorithm as pseudocode from the paper down into
comments, and then implement the comments. It is still basically singly linked list insertion,
with a handle to the node just before , whose next pointer must be copied as the inserted
node's next pointer, before the next pointer is updated as the inserted node; but there are
other tricky things to remember, such as having two nested loops, a temporary array of
previous node references to remember which node is the previous node at which level ; not
inserting a node if the key already exists and is found, making sure the list header doesn't
need to be updated because the height of the inserting node is the greatest encountered so
far, and making multiple linked list insertion by iterating through the temporary array of
previous pointers.
42
Treaps
tree balancing in tree algorithms. Since the higher level list have more widely separated
elements, but the search algorithm moves down a level after each search terminates at a
level, the higher levels help "skip" over the need to search earlier elements on lower lists.
Because there are multiple levels of skipping, it becomes less likely that a meagre skip at a
higher level won't be compensated by better skips at lower levels, and Pugh claims O(logN)
performance overall.
Conceptually , is it easier to understand than balancing trees and hence easier to implement
? The development of ideas from binary trees, balanced binary trees, 2-3 trees, red-black
trees, and B-trees make a stronger conceptual network but is progressive in development, so
arguably, once red-black trees are understood, they have more conceptual context to aid
memory , or refresh of memory.
4.6 Treaps
A treap is a two keyed binary tree, that uses a second randomly generated key and the
previously discussed tree operation of parent-child rotation to randomly rotate the tree
so that overall, a balanced tree is produced. Recall that binary trees work by having all
nodes in the left subtree small than a given node, and all nodes in a right subtree greater.
Also recall that node rotation does not break this order ( some people call it an invariant),
but changes the relationship of parent and child, so that if the parent was smaller than a
right child, then the parent becomes the left child of the formerly right child. The idea of a
tree-heap or treap, is that a binary heap relationship is maintained between parents and
child, and that is a parent node has higher priority than its children, which is not the same
as the left , right order of keys in a binary tree, and hence a recently inserted leaf node in a
binary tree which happens to have a high random priority, can be rotated so it is relatively
higher in the tree, having no parent with a lower priority. See the preamble to skip lists
about red-black trees on the details of left rotation2 .
A treap is an alternative to both red-black trees, and skip lists, as a self-balancing sorted
storage structure.
4.7 Derandomization
[TODO: Deterministic algorithms for Quicksort exist that perform as well as quicksort in
the average case and are guaranteed to perform at least that well in all cases. Best of all,
no randomization is needed. Also in the discussion should be some perspective on using
randomization: some randomized algorithms give you better confidence probabilities than
2
http://en.wikibooks.org/wiki/..%2FLeft%20rotation
43
Randomization
the actual hardware itself! (e.g. sunspots can randomly flip bits in hardware, causing failure,
which is a risk we take quite often)]
[Main idea: Look at all blocks of 5 elements, and pick the median (O(1) to pick), put all
medians into an array (O(n)), recursively pick the medians of that array, repeat until you
have < 5 elements in the array. This recursive median constructing of every five elements
takes time T(n)=T(n/5) + O(n), which by the master theorem is O(n). Thus, in O(n) we
can find the right pivot. Need to show that this pivot is sufficiently good so that we're still
O(n log n) no matter what the input is. This version of quicksort doesn't need rand, and it
never performs poorly. Still need to show that element picked out is sufficiently good for a
pivot.]
4.8 Exercises
1. Write a find-min function and run it on several different inputs to demonstrate its
correctness.
44
5 Backtracking
Backtracking is a general algorithmic technique that considers searching every possible
combination in order to solve an optimization problem. Backtracking is also known as
depth-first search or branch and bound. By inserting more knowledge of the problem,
the search tree can be pruned to avoid considering cases that don't look promising. While
backtracking is useful for hard problems to which we do not know more efficient solutions,
it is a poor solution for the everyday problems that other techniques are much better at
solving.
However, dynamic programming and greedy algorithms can be thought of as optimizations
to backtracking, so the general technique behind backtracking is useful for understanding
these more advanced concepts. Learning and understanding backtracking techniques first
provides a good stepping stone to these more advanced techniques because you won't have
to learn several new concepts all at once.
Backtracking Methodology
# View picking a solution as a sequence of choices# For each choice, consider every
option recursively# Return the best solution found
This methodology is generic enough that it can be applied to most problems. However, even
when taking care to improve a backtracking algorithm, it will probably still take exponential time rather than polynomial time. Additionally, exact time analysis of backtracking
algorithms can be extremely difficult: instead, simpler upperbounds that may not be tight
are given.
45
Backtracking
The LCS problem, instead of dealing with lines in text files, is concerned with finding
common items between two different arrays. For example,
let a := array {"The", "great", "square", "has", "no", "corners"}
let b := array {"The", "great", "image", "has", "no", "form"}
We want to find the longest subsequence possible of items that are found in both a and b in
the same order. The LCS of a and b is
"The", "great", "has", "no"
Now consider two more sequences:
let c := array {1, 2, 4, 8, 16, 32}
let d := array {1, 2, 3, 32, 8}
which takes in two arrays as input and outputs the subsequence array.
How do you solve this problem? You could start by noticing that if the two sequences start
with the same word, then the longest common subsequence always contains that word. You
can automatically put that word on your list, and you would have just reduced the problem
to finding the longest common subset of the rest of the two lists. Thus, the problem was
made smaller, which is good because it shows progress was made.
But if the two lists do not begin with the same word, then one, or both, of the first element
in a or the first element in b do not belong in the longest common subsequence. But yet,
one of them might be. How do you determine which one, if any, to add?
46
47
Backtracking
48
6 Dynamic Programming
Dynamic programming can be thought of as an optimization technique for particular
classes of backtracking algorithms where subproblems are repeatedly solved. Note that the
term dynamic in dynamic programming should not be confused with dynamic programming
languages, like Scheme or Lisp. Nor should the term programming be confused with the act
of writing computer programs. In the context of algorithms, dynamic programming always
refers to the technique of filling in a table with values computed from other table values.
(It's dynamic because the values in the table are filled in by the algorithm based on other
values of the table, and it's programming in the sense of setting things in a table, like how
television programming is concerned with when to broadcast what shows.)
F1 = 1
Fn = Fn1 + Fn2
How would one create a good algorithm for finding the nth Fibonacci-number? Let's begin
with the naive algorithm, which codes the mathematical definition:
// fib -- compute Fibonacci(n)
function fib(integer n): integer
assert (n >= 0)
if n == 0: return 0 fi
if n == 1: return 1 fi
49
Dynamic Programming
http://en.wikibooks.org/wiki/Ada_Programming%2FAlgorithms%23Simple_Implementation
Note that this is a toy example because there is already a mathematically closed form for
Fn :
F (n) =
n (1 )n
where:
1+ 5
=
2
This latter equation is known as the Golden Ratio1 . Thus, a program could efficiently
calculate Fn for even very large n. However, it's instructive to understand what's so
inefficient about the current algorithm.
To analyze the running time of fib we should look at a call tree for something even as small
as the sixth Fibonacci number:
Figure 5
Every leaf of the call tree has the value 0 or 1, and the sum of these values is the final result.
So, for any n, the number of leaves in the call tree is actually Fn itself! The closed form
thus tells us that the number of leaves in fib(n) is approximately equal to
!n
1+ 5
n
1.618n = 2lg(1.618 ) = 2n lg(1.618) 20.69n .
2
1
50
http://en.wikipedia.org/wiki/golden_ratio
Fibonacci Numbers
(Note the algebraic manipulation used above to make the base of the exponent the number
2.) This means that there are far too many leaves, particularly considering the repeated
patterns found in the call tree above.
One optimization we can make is to save a result in a table once it's already been computed,
so that the same result needs to be computed only once. The optimization process is called
memoization and conforms to the following methodology:
Memoization Methodology
# Start with a backtracking algorithm# Look up the problem in a table; if there's a
valid entry for it, return that value# Otherwise, compute the problem recursively, and
then store the result in the table before returning the value
Consider the solution presented in the backtracking chapter for the Longest Common
Subsequence problem. In the execution of that algorithm, many common subproblems were
computed repeatedly. As an optimization, we can compute these subproblems once and
then store the result to read back later. A recursive memoization algorithm can be turned
"bottom-up" into an iterative algorithm that fills in a table of solutions to subproblems.
Some of the subproblems solved might not be needed by the end result (and that is where
dynamic programming differs from memoization), but dynamic programming can be very
efficient because the iterative version can better use the cache and have less call overhead.
Asymptotically, dynamic programming and memoization have the same complexity.
So how would a fibonacci program using memoization work? Consider the following program
(f[n] contains the nth Fibonacci-number if has been calculated, -1 otherwise):
function fib(integer n): integer
if n == 0 or n == 1:
return n
else-if f[n] != -1:
return f[n]
else
f[n] = fib(n - 1) + fib(n - 2)
return f[n]
fi
end
http://en.wikibooks.org/wiki/Ada_Programming%2FAlgorithms%23Cached_Implementation
The code should be pretty obvious. If the value of fib(n) already has been calculated it's
stored in f[n] and then returned instead of calculating it again. That means all the copies of
the sub-call trees are removed from the calculation.
51
Dynamic Programming
Figure 6
The values in the blue boxes are values that already have been calculated and the calls can
thus be skipped. It is thus a lot faster than the straight-forward recursive algorithm. Since
every value less than n is calculated once, and only once, the first time you execute it, the
asymptotic running time is O(n). Any other calls to it will take O(1) since the values have
been precalculated (assuming each subsequent call's argument is less than n).
The algorithm does consume a lot of memory. When we calculate fib(n), the values
fib(0) to fib(n) are stored in main memory. Can this be improved? Yes it can, although the O(1) running time of subsequent calls are obviously lost since the values
aren't stored. Since the value of fib(n) only depends on fib(n-1) and fib(n-2) we
can discard the other values by going bottom-up. If we want to calculate fib(n), we
first calculate fib(2) = fib(0) + fib(1). Then we can calculate fib(3) by adding fib(1)
and fib(2). After that, fib(0) and fib(1) can be discarded, since we don't need them
to calculate any more values. From fib(2) and fib(3) we calculate fib(4) and discard
fib(2), then we calculate fib(5) and discard fib(3), etc. etc. The code goes something like this:
function fib(integer n): integer
if n == 0 or n == 1:
return n
fi
let u := 0
let v := 1
for i := 2 to n:
let t := u + v
u := v
v := t
repeat
return v
end
52
http://en.wikibooks.org/wiki/Ada_Programming%2FAlgorithms%23Memory_Optimized_
Implementation
M1 ((M2 M3 )M4 )
(M1 M2 )(M3 M4 )
M1 (M2 (M3 M4 ))
53
Dynamic Programming
Two matrices M1 and M2 can be multiplied if the number of columns in M1 equals the
number of rows in M2 . The number of rows in their product will equal the number rows
in M1 and the number of columns will equal the number of columns in M2 . That is, if the
dimensions of M1 is a b and M2 has dimensions b c their product will have dimensions
a c.
To multiply two matrices with each other we use a function called matrix-multiply that
takes two matrices and returns their product. We will leave implementation of this function
alone for the moment as it is not the focus of this chapter (how to multiply two matrices in
the fastest way has been under intensive study for several years [TODO: propose this topic
for the Advanced book]). The time this function takes to multiply two matrices of size a b
and b c is proportional to the number of scalar multiplications, which is proportional to
abc. Thus, paranthezation matters: Say that we have three matrices M1 , M2 and M3 . M1
has dimensions 5 100, M2 has dimensions 100 100 and M3 has dimensions 100 50. Let's
paranthezise them in the two possible ways and see which way requires the least amount of
multiplications. The two ways are
((M1 M2 )M3 ), and
(M1 (M2 M3 )).
To form the product in the first way requires 75000 scalar multiplications (5*100*100=50000
to form product (M1 M2 ) and another 5*100*50=25000 for the last multiplications.) This
might seem like a lot, but in comparison to the 525000 scalar multiplications required by the
second parenthesization (50*100*100=500000 plus 5*50*100=25000) it is miniscule! You
can see why determining the parenthesization is important: imagine what would happen if
we needed to multiply 50 matrices!
54
(Mk+1 . . . Mn )
That is, just in accordance with the fundamental principle of dynamic programming, the
solution to the problem depends on the solution of smaller sub-problems.
Let's say that it takes c(n) scalar multiplications to multiply matrices Mn and Mn+1 , and
f (m, n) is the number of scalar multiplications to be performed in an optimal parenthesization
of the matrices Mm . . . Mn . The definition of f (m, n) is the first step toward a solution.
When n m = 1, the formulation is trivial; it is just c(m). But what is it when the
distance is larger? Using the observation above, we can derive a formulation. Suppose an optimal solution to the problem divides the matrices at matrices k and k+1
(i.e. (Mm . . . Mk )(Mk+1 . . . Mn )) then the number of scalar multiplications are.
f (m, k) + f (k + 1, n) + c(k)
That is, the amount of time to form the first product, the amount of time it takes to form
the second product, and the amount of time it takes to multiply them together. But what
is this optimal value k? The answer is, of course, the value that makes the above formula
assume its minimum value. We can thus form the complete definition for the function:
(
f (m, n) =
A straight-forward recursive solution to this would look something like this (the language is
Wikicode2 ):
function f(m, n) {
if m == n
return 0
let minCost :=
for k := m to n - 1 {
v := f(m, k) + f(k + 1, n) + c(k)
if v < minCost
minCost := v
}
return minCost
http://en.wikipedia.org/wiki/Wikipedia:Wikicode
55
Dynamic Programming
This rather simple solution is, unfortunately, not a very good one. It spends mountains of
time recomputing data and its running time is exponential.
Using the same adaptation as above we get:
function f(m, n) {
if m == n
return 0
else-if f[m,n] != -1:
return f[m,n]
fi
let minCost :=
for k := m to n - 1 {
v := f(m, k) + f(k + 1, n) + c(k)
if v < minCost
minCost := v
}
f[m,n]=minCost
return minCost
56
7 Greedy Algorithms
In the backtracking algorithms we looked at, we saw algorithms that found decision points
and recursed over all options from that decision point. A greedy algorithm can be thought
of as a backtracking algorithm where at each decision point "the best" option is already
known and thus can be picked without having to recurse over any of the alternative options.
The name "greedy" comes from the fact that the algorithms make decisions based on a single
criterion, instead of a global analysis that would take into account the decision's effect on
further steps. As we will see, such a backtracking analysis will be unnecessary in the case of
greedy algorithms, so it is not greedy in the sense of causing harm for only short-term gain.
Unlike backtracking algorithms, greedy algorithms can't be made for every problem. Not
every problem is "solvable" using greedy algorithms. Viewing the finding solution to an
optimization problem as a hill climbing problem greedy algorithms can be used for only
those hills where at every point taking the steepest step would lead to the peak always.
Greedy algorithms tend to be very efficient and can be implemented in a relatively straightforward fashion. Many a times in O(n) complexity as there would be a single choice at
every point. However, most attempts at creating a correct greedy algorithm fail unless a
precise proof of the algorithm's correctness is first demonstrated. When a greedy strategy
fails to produce optimal results on all inputs, we instead refer to it as a heuristic instead of
an algorithm. Heuristics can be useful when speed is more important than exact results (for
example, when "good enough" results are sufficient).
57
Greedy Algorithms
We first begin with a backtracking solution to the problem:
// event-schedule -- schedule as many non-conflicting events as possible
function event-schedule(events array of s[1..n], j[1..n]): set
if n == 0: return fi
if n == 1: return {events[1]} fi
let event := events[1]
let S1 := union(event-schedule(events - set of conflicting events), event)
let S2 := event-schedule(events - {event})
if S1.size() >= S2.size():
return S1
else
return S2
fi
end
The above algorithm will faithfully find the largest set of non-conflicting events. It brushes
aside details of how the set
events - set of conflicting events
is computed, but it would require O(n) time. Because the algorithm makes two recursive
calls on itself, each with an argument of size n 1, and because removing conflicts takes
linear time, a recurrence for the time this algorithm takes is:
T (n) = 2 T (n 1) + O(n)
which is O(2n ).
But suppose instead of picking just the first element in the array we used some other criterion.
The aim is to just pick the "right" one so that we wouldn't need two recursive calls. First,
let's consider the greedy strategy of picking the shortest events first, until we can add no
more events without conflicts. The idea here is that the shortest events would likely interfere
less than other events.
There are scenarios were picking the shortest event first produces the optimal result. However,
here's a scenario where that strategy is sub-optimal:
Figure 7
58
Figure 8
Above, we can maximize the number of events by picking A, B, C, D, and E. However, the
events with the least conflicts are 6, 2 and 7, 3. But picking one of 6, 2 and one of 7, 3
means that we cannot pick B, C and D, which includes three events instead of just two.
59
Greedy Algorithms
60
http://en.wikipedia.org/wiki/Minimum%20spanning%20tree
8 Hill Climbing
Hill climbing is a technique for certain classes of optimization problems. The idea is to
start with a sub-optimal solution to a problem (i.e., start at the base of a hill) and then
repeatedly improve the solution (walk up the hill) until some condition is maximized (the
top of the hill is reached).
Hill-Climbing Methodology
# Construct a sub-optimal solution that meets the constraints of the problem# Take
the solution and make an improvement upon it# Repeatedly improve the solution
until no more improvements are necessary/possible
One of the most popular hill-climbing problems is the network flow problem. Although
network flow may sound somewhat specific it is important because it has high expressive
power: for example, many algorithmic problems encountered in practice can actually be
considered special cases of network flow. After covering a simple example of the hill-climbing
approach for a numerical problem we cover network flow and then present examples of
applications of network flow.
61
Hill Climbing
http://en.wikipedia.org/wiki/Newton%27s%20method
f (x0 )
f 0 (x0 )
62
f (x0 )
.
f 0 (x0 )
Network Flow
This new solution can be the starting point for applying the same procedure again. Thus, in
general a better approximation can be constructed by repeatedly applying
xn+1 = xn
f (xn )
.
f 0 (xn )
As shown in the illustration, this is nothing else but the construction of the zero from the
tangent at the initial guessing point. In general, Newton's root finding method converges
quadratically, except when the first derivative of the solution f 0 (x) = 0 vanishes at the root.
Coming back to the "Hill climbing" analogy, we could apply Newton's root finding method
not to the function f (x) , but to its first derivative f 0 (x) , that is look for x such that
f 0 (x) = 0 . This would give the extremal positions of the function, its maxima and minima.
Starting Newton's method close enough to a maximum this way, we climb the hill.
Instead of regarding continuous functions, the hill-climbing method can also be applied to
discrete networks.
63
Hill Climbing
Figure 10
We'd like now to imagine that we have some series of inputs arriving at the source that we
want to carry on the edges over to the sink. The number of units we can send on an edge at
a time must be less than or equal to the edge's capacity. You can think of the vertices as
cities and the edges as roads between the cities and we want to send as many cars from the
source city to the destination city as possible. The constraint is that we cannot send more
cars down a road than its capacity can handle.
The goal of network flow is to send as much traffic from s to t as each street can bear.
To organize the traffic routes, we can build a list of different paths from city s to city t.
Each path has a carrying capacity equal to the smallest capacity value for any edge on the
path; for example, consider the following path p:
64
Network Flow
Figure 11
Even though the final edge of p has a capacity of 8, that edge only has one car traveling on it
because the edge before it only has a capacity of 1 (thus, that edge is at full capacity). After
using this path, we can compute the residual graph by subtracting 1 from the capacity of
each edge:
65
Hill Climbing
Figure 12
(We subtracted 1 from the capacity of each edge in p because 1 was the carrying capacity of
p.) We can say that path p has a flow of 1. Formally, a flow is an assignment f (e) of values
to the set of edges in the graph G = (V, E) such that:
1. e E : f (e) R
2. (u, v) E : f ((u, v)) = f ((v, u))
3. u V, u 6= s, t :
vV
f (u, v) = 0
4. e E : f (e) c(e)
Where s is the source node and t is the sink node, and c(e) 0 is the capacity of edge e.
We define the value of a flow f to be:
Value(f ) =
f ((s, v))
vV
The goal of network flow is to find an f such that Value(f ) is maximal. To be maximal
means that there is no other flow assignment that obeys the constraints 1-4 that would have
a higher value. The traffic example can describe what the four flow constraints mean:
1. e E : f (e) R. This rule simply defines a flow to be a function from edges in the
graph to real numbers. The function is defined for every edge in the graph. You could
also consider the "function" to simply be a mapping: Every edge can be an index into
an array and the value of the array at an edge is the value of the flow function at that
edge.
66
67
9 Ada Implementation
9.1 Introduction
Welcome to the Ada implementations of the Algorithms1 Wikibook. For those who are new
to Ada Programming2 a few notes:
All examples are fully functional with all the needed input and output operations.
However, only the code needed to outline the algorithms at hand is copied into the text the full samples are available via the download links. (Note: It can take up to 48 hours
until the cvs is updated).
We seldom use predefined types in the sample code but define special types suitable for
the algorithms at hand.
Ada allows for default function parameters; however, we always fill in and name all
parameters, so the reader can see which options are available.
We seldom use shortcuts - like using the attributes Image or Value for String <=> Integer
conversions.
All these rules make the code more elaborate than perhaps needed. However, we also hope
it makes the code easier to understand
Category:Ada Programming3
9.2.1 To Lower
The Ada example code does not append to the array as the algorithms. Instead we create
an empty array of the desired length and then replace the characters inside.
File:
to_lower_1.adb
1
2
3
4
http://en.wikibooks.org/wiki/Algorithms
http://en.wikibooks.org/wiki/Ada%20Programming
http://en.wikibooks.org/wiki/Category%3AAda%20Programming
Chapter 1.3 on page 4
69
Ada Implementation
Ada.Characters.Handling.To_Lower;
-- tolower - translates all alphabetic, uppercase characters
-- in str to lowercase
function To_Lower (Str : String) return String is
Result : String (Str'Range);
begin
for C in Str'Range loop
Result (C) := To_Lower (Str (C));
end loop;
return Result;
end To_Lower;
Would the append approach be impossible with Ada? No, but it would be significantly more
complex and slower.
70
...
To calculate Fibonacci numbers negative values are not needed so we define an integer type
which starts at 0. With the integer type defined you can calculate up until Fib (87). Fib
(88) will result in an Constraint_Error.
type Integer_Type is range 0 .. 999_999_999_999_999_999;
You might notice that there is not equivalence for the assert (n >= 0) from the original
example. Ada will test the correctness of the parameter before the function is called.
function Fib (n : Integer_Type) return Integer_Type is
begin
if n = 0 then
return 0;
elsif n = 1 then
return 1;
else
return Fib (n - 1) + Fib (n - 2);
end if;
end Fib;
...
Cached Implementation
File: fibonacci_2.adb
...
For this implementation we need a special cache type can also store a -1 as "not calculated"
marker
type Cache_Type is range -1 .. 999_999_999_999_999_999;
The actual type for calculating the fibonacci numbers continues to start at 0. As it is a
subtype of the cache type Ada will automatically convert between the two. (the conversion
is - of course - checked for validity)
subtype Integer_Type is Cache_Type range
0 .. Cache_Type'Last;
71
Ada Implementation
In order to know how large the cache need to be we first read the actual value from the
command line.
Value : constant Integer_Type :=
Integer_Type'Value (Ada.Command_Line.Argument (1));
The Cache array starts with element 2 since Fib (0) and Fib (1) are constants and ends
with the value we want to calculate.
type Cache_Array is
array (Integer_Type range 2 .. Value) of Cache_Type;
The Cache is initialized to the first valid value of the cache type this is -1.
F : Cache_Array := (others => Cache_Type'First);
This implementation is faithful to the original from the Algorithms6 book. However, in Ada
you would normally do it a little different:
File: fibonacci_3.adb
when you use a slightly larger array which also stores the elements 0 and 1 and initializes
them to the correct values
type Cache_Array is
array (Integer_Type range 0 .. Value) of Cache_Type;
F : Cache_Array :=
(0
=> 0,
1
=> 1,
others => Cache_Type'First);
72
http://en.wikibooks.org/wiki/Algorithms
73
Ada Implementation
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
74
No 64 bit integers
Your Ada compiler does not support 64 bit integer numbers? Then you could try to use
decimal numbers7 instead. Using decimal numbers results in a slower program (takes about
three times as long) but the result will be the same.
The following example shows you how to define a suitable decimal type. Do experiment
with the digits and range parameters until you get the optimum out of your Ada compiler.
File:
fibonacci_5.adb
You should know that floating point numbers are unsuitable for the calculation of fibonacci
numbers. They will not report an error condition when the number calculated becomes too
large instead they will lose in precision which makes the result meaningless.
http://en.wikibooks.org/wiki/Ada%20Programming%2FTypes%2Fdelta
75
10 Contributors
Edits
16
3
5
1
1
9
2
2
2
3
1
4
4
1
1
1
11
1
20
1
4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
User
Adrignola1
Andreas Ipp2
Avicennasis3
ChrisMorrisOrg4
Chuckhoffmann5
Codebrain6
DavidCary7
Derek Ross8
Dirk Hnniger9
Dnas10
Elaurier11
Filburli12
Fishpi13
Frikk14
Fry-kun15
Geocachernemesis16
Gkhan17
Guanabot18
Hagindaz19
HorsemansWiki20
Hwwong21
http://en.wikibooks.org/w/index.php?title=User:Adrignola
http://en.wikibooks.org/w/index.php?title=User:Andreas_Ipp
http://en.wikibooks.org/w/index.php?title=User:Avicennasis
http://en.wikibooks.org/w/index.php?title=User:ChrisMorrisOrg
http://en.wikibooks.org/w/index.php?title=User:Chuckhoffmann
http://en.wikibooks.org/w/index.php?title=User:Codebrain
http://en.wikibooks.org/w/index.php?title=User:DavidCary
http://en.wikibooks.org/w/index.php?title=User:Derek_Ross
http://en.wikibooks.org/w/index.php?title=User:Dirk_H%C3%BCnniger
http://en.wikibooks.org/w/index.php?title=User:Dnas
http://en.wikibooks.org/w/index.php?title=User:Elaurier
http://en.wikibooks.org/w/index.php?title=User:Filburli
http://en.wikibooks.org/w/index.php?title=User:Fishpi
http://en.wikibooks.org/w/index.php?title=User:Frikk
http://en.wikibooks.org/w/index.php?title=User:Fry-kun
http://en.wikibooks.org/w/index.php?title=User:Geocachernemesis
http://en.wikibooks.org/w/index.php?title=User:Gkhan
http://en.wikibooks.org/w/index.php?title=User:Guanabot
http://en.wikibooks.org/w/index.php?title=User:Hagindaz
http://en.wikibooks.org/w/index.php?title=User:HorsemansWiki
http://en.wikibooks.org/w/index.php?title=User:Hwwong
77
Contributors
2
2
2
1
2
22
1
1
2
8
1
1
54
1
1
2
1
3
4
2
147
3
1
2
2
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
78
Intgr22
Iwasapenguin23
James Dennett24
JasonWoof25
Jfmantis26
Jguk27
Jleedev28
Jomegat29
JustinWick30
Jyasskin31
K.Rakesh vidya chandra32
Kd8cpk33
Krischik34
Kusti35
Liblamb36
Lynx772537
Mabdul38
Mahanga39
ManuelGR40
Mmartin41
Mshonle42
Nikai43
Panic2k444
QuiteUnusual45
R3m0t46
http://en.wikibooks.org/w/index.php?title=User:Intgr
http://en.wikibooks.org/w/index.php?title=User:Iwasapenguin
http://en.wikibooks.org/w/index.php?title=User:James_Dennett
http://en.wikibooks.org/w/index.php?title=User:JasonWoof
http://en.wikibooks.org/w/index.php?title=User:Jfmantis
http://en.wikibooks.org/w/index.php?title=User:Jguk
http://en.wikibooks.org/w/index.php?title=User:Jleedev
http://en.wikibooks.org/w/index.php?title=User:Jomegat
http://en.wikibooks.org/w/index.php?title=User:JustinWick
http://en.wikibooks.org/w/index.php?title=User:Jyasskin
http://en.wikibooks.org/w/index.php?title=User:K.Rakesh_vidya_chandra
http://en.wikibooks.org/w/index.php?title=User:Kd8cpk
http://en.wikibooks.org/w/index.php?title=User:Krischik
http://en.wikibooks.org/w/index.php?title=User:Kusti
http://en.wikibooks.org/w/index.php?title=User:Liblamb
http://en.wikibooks.org/w/index.php?title=User:Lynx7725
http://en.wikibooks.org/w/index.php?title=User:Mabdul
http://en.wikibooks.org/w/index.php?title=User:Mahanga
http://en.wikibooks.org/w/index.php?title=User:ManuelGR
http://en.wikibooks.org/w/index.php?title=User:Mmartin
http://en.wikibooks.org/w/index.php?title=User:Mshonle
http://en.wikibooks.org/w/index.php?title=User:Nikai
http://en.wikibooks.org/w/index.php?title=User:Panic2k4
http://en.wikibooks.org/w/index.php?title=User:QuiteUnusual
http://en.wikibooks.org/w/index.php?title=User:R3m0t
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
Recent Runes47
Robert Horning48
Sartak49
Sigma 750
Spamduck51
SudarshanP52
Suruena53
Swhalen54
Swift55
Tcsetattr56
Vito Genovese57
WhirlWind58
Yhh59
Ylai60
Ylloh61
Yuyuchan330162
Znetweb63
http://en.wikibooks.org/w/index.php?title=User:Recent_Runes
http://en.wikibooks.org/w/index.php?title=User:Robert_Horning
http://en.wikibooks.org/w/index.php?title=User:Sartak
http://en.wikibooks.org/w/index.php?title=User:Sigma_7
http://en.wikibooks.org/w/index.php?title=User:Spamduck
http://en.wikibooks.org/w/index.php?title=User:SudarshanP
http://en.wikibooks.org/w/index.php?title=User:Suruena
http://en.wikibooks.org/w/index.php?title=User:Swhalen
http://en.wikibooks.org/w/index.php?title=User:Swift
http://en.wikibooks.org/w/index.php?title=User:Tcsetattr
http://en.wikibooks.org/w/index.php?title=User:Vito_Genovese
http://en.wikibooks.org/w/index.php?title=User:WhirlWind
http://en.wikibooks.org/w/index.php?title=User:Yhh
http://en.wikibooks.org/w/index.php?title=User:Ylai
http://en.wikibooks.org/w/index.php?title=User:Ylloh
http://en.wikibooks.org/w/index.php?title=User:Yuyuchan3301
http://en.wikibooks.org/w/index.php?title=User:Znetweb
79
List of Figures
GFDL: Gnu Free Documentation License. http://www.gnu.org/licenses/fdl.html
cc-by-sa-3.0: Creative Commons Attribution ShareAlike 3.0 License.
creativecommons.org/licenses/by-sa/3.0/
http://
http://
http://
http://
81
List of Figures
EPL: Eclipse Public License. http://www.eclipse.org/org/documents/epl-v10.
php
Copies of the GPL, the LGPL as well as a GFDL are included in chapter Licenses64 . Please
note that images in the public domain do not require attribution. You may click on the
image numbers in the following table to open the webpage of the images in your webbrower.
64
82
Chapter 11 on page 85
List of Figures
1
2
3
4
5
6
7
8
9
10
11
12
65
66
GFDL
GFDL
GFDL
GFDL
GFDL
GFDL
GFDL
GFDL
PD
GFDL
GFDL
GFDL
http://en.wikibooks.org/wiki/%3Aen%3AUser%3AOlegalexandrov
http://en.wikipedia.org
83
11 Licenses
11.1 GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright 2007 Free Software Foundation, Inc.
<http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing
it is not allowed. Preamble
The GNU General Public License is a free, copyleft
license for software and other kinds of works.
The licenses for most software and other practical works are designed to take away your freedom
to share and change the works. By contrast, the
GNU General Public License is intended to guarantee your freedom to share and change all versions
of a programto make sure it remains free software
for all its users. We, the Free Software Foundation,
use the GNU General Public License for most of our
software; it applies also to any other work released
this way by its authors. You can apply it to your
programs, too.
When we speak of free software, we are referring
to freedom, not price.
Our General Public Licenses are designed to make sure that you have
the freedom to distribute copies of free software
(and charge for them if you wish), that you receive
source code or can get it if you want it, that you
can change the software or use pieces of it in new
free programs, and that you know you can do these
things.
To protect your rights, we need to prevent others
from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect
the freedom of others.
For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass
on to the recipients the same freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show
them these terms so they know their rights.
Developers that use the GNU GPL protect your
rights with two steps: (1) assert copyright on the
software, and (2) offer you this License giving you
legal permission to copy, distribute and/or modify
it.
For the developers and authors protection, the
GPL clearly explains that there is no warranty for
this free software. For both users and authors
sake, the GPL requires that modified versions be
marked as changed, so that their problems will not
be attributed erroneously to authors of previous
versions.
Some devices are designed to deny users access to
install or run modified versions of the software inside them, although the manufacturer can do so.
This is fundamentally incompatible with the aim
of protecting users freedom to change the software.
The systematic pattern of such abuse occurs in the
area of products for individuals to use, which is
precisely where it is most unacceptable. Therefore,
we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand
ready to extend this provision to those domains in
future versions of the GPL, as needed to protect
the freedom of users.
Finally, every program is threatened constantly by
software patents. States should not allow patents
to restrict development and use of software on
general-purpose computers, but in those that do,
we wish to avoid the special danger that patents
applied to a free program could make it effectively
proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program nonfree.
The precise terms and conditions for copying, distribution and modification follow. TERMS AND
CONDITIONS 0. Definitions.
This License refers to version 3 of the GNU General Public License.
Copyright also means copyright-like laws that apply to other kinds of works, such as semiconductor
masks.
The Program refers to any copyrightable work
licensed under this License. Each licensee is addressed as you. Licensees and recipients may
be individuals or organizations.
To modify a work means to copy from or adapt
all or part of the work in a fashion requiring copyright permission, other than the making of an exact
copy. The resulting work is called a modified version of the earlier work or a work based on the
earlier work.
A covered work means either the unmodified Program or a work based on the Program.
To propagate a work means to do anything with it
that, without permission, would make you directly
or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in
some countries other activities as well.
To convey a work means any kind of propagation
that enables other parties to make or receive copies.
Mere interaction with a user through a computer
A User Product is either (1) a consumer product, which means any tangible personal property
which is normally used for personal, family, or
household purposes, or (2) anything designed or
sold for incorporation into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, normally used refers to a typical or
common use of that class of product, regardless of
the status of the particular user or of the way in
which the particular user actually uses, or expects
or is expected to use, the product. A product is a
consumer product regardless of whether the product has substantial commercial, industrial or nonconsumer uses, unless such uses represent the only
significant mode of use of the product.
If you convey an object code work under this section in, or with, or specifically for use in, a User
Product, and the conveying occurs as part of a
transaction in which the right of possession and
use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section
must be accompanied by the Installation Information. But this requirement does not apply if neither
you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has been installed in ROM).
The requirement to provide Installation Information does not include a requirement to continue to
provide support service, warranty, or updates for a
work that has been modified or installed by the recipient, or for the User Product in which it has been
modified or installed. Access to a network may be
denied when the modification itself materially and
adversely affects the operation of the network or
violates the rules and protocols for communication
across the network.
Corresponding Source conveyed, and Installation
Information provided, in accord with this section
must be in a format that is publicly documented
(and with an implementation available to the public
in source code form), and must require no special
password or key for unpacking, reading or copying.
7. Additional Terms.
Additional permissions are terms that supplement
the terms of this License by making exceptions from
one or more of its conditions. Additional permissions that are applicable to the entire Program
shall be treated as though they were included in
this License, to the extent that they are valid under applicable law. If additional permissions apply
only to part of the Program, that part may be used
separately under those permissions, but the entire
Program remains governed by this License without
regard to the additional permissions.
When you convey a copy of a covered work, you may
at your option remove any additional permissions
from that copy, or from any part of it. (Additional
permissions may be written to require their own removal in certain cases when you modify the work.)
You may place additional permissions on material,
added by you to a covered work, for which you have
or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you add to a covered work, you
may (if authorized by the copyright holders of that
material) supplement the terms of this License with
terms:
* a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this
License; or * b) Requiring preservation of specified
reasonable legal notices or author attributions in
that material or in the Appropriate Legal Notices
displayed by works containing it; or * c) Prohibiting misrepresentation of the origin of that material,
or requiring that modified versions of such material
be marked in reasonable ways as different from the
original version; or * d) Limiting the use for publicity purposes of names of licensors or authors of
the material; or * e) Declining to grant rights under
trademark law for use of some trade names, trademarks, or service marks; or * f) Requiring indemnification of licensors and authors of that material
by anyone who conveys the material (or modified
versions of it) with contractual assumptions of liability to the recipient, for any liability that these
contractual assumptions directly impose on those
licensors and authors.
All other non-permissive additional terms are considered further restrictions within the meaning of
section 10. If the Program as you received it, or any
part of it, contains a notice stating that it is governed by this License along with a term that is a
further restriction, you may remove that term. If a
license document contains a further restriction but
permits relicensing or conveying under this License,
you may add to a covered work material governed
by the terms of that license document, provided
that the further restriction does not survive such
relicensing or conveying.
If you add terms to a covered work in accord with
this section, you must place, in the relevant source
files, a statement of the additional terms that apply to those files, or a notice indicating where to
find the applicable terms.
Additional terms, permissive or non-permissive,
may be stated in the form of a separately written
license, or stated as exceptions; the above requirements apply either way. 8. Termination.
You may not propagate or modify a covered work
except as expressly provided under this License.
Any attempt otherwise to propagate or modify it is
void, and will automatically terminate your rights
under this License (including any patent licenses
granted under the third paragraph of section 11).
However, if you cease all violation of this License,
then your license from a particular copyright holder
is reinstated (a) provisionally, unless and until the
copyright holder explicitly and finally terminates
your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some
reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright
holder is reinstated permanently if the copyright
holder notifies you of the violation by some reasonable means, this is the first time you have received
notice of violation of this License (for any work)
from that copyright holder, and you cure the violation prior to 30 days after your receipt of the
notice.
Termination of your rights under this section does
not terminate the licenses of parties who have received copies or rights from you under this License.
If your rights have been terminated and not permanently reinstated, you do not qualify to receive new
licenses for the same material under section 10. 9.
Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely
as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However, nothing other than this License
grants you permission to propagate or modify any
covered work. These actions infringe copyright if
you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate
your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient
automatically receives a license from the original
licensors, to run, modify and propagate that work,
subject to this License. You are not responsible
for enforcing compliance by third parties with this
License.
An entity transaction is a transaction transferring control of an organization, or substantially all
assets of one, or subdividing an organization, or
merging organizations. If propagation of a covered work results from an entity transaction, each
party to that transaction who receives a copy of the
work also receives whatever licenses to the work the
partys predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from
the predecessor in interest, if the predecessor has it
or can get it with reasonable efforts.
You may not impose any further restrictions on the
exercise of the rights granted or affirmed under this
License. For example, you may not impose a license
fee, royalty, or other charge for exercise of rights
granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim
is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A contributor is a copyright holder who authorizes use under this License of the Program or a
work on which the Program is based. The work
thus licensed is called the contributors contributor version.
A contributors essential patent claims are all
patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired,
that would be infringed by some manner, permitted by this License, of making, using, or selling its
contributor version, but do not include claims that
would be infringed only as a consequence of further
modification of the contributor version. For purposes of this definition, control includes the right
to grant patent sublicenses in a manner consistent
with the requirements of this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributors essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a patent license is any express agreement or commitment,
however denominated, not to enforce a patent (such
as an express permission to practice a patent or
covenant not to sue for patent infringement). To
grant such a patent license to a party means to
make such an agreement or commitment not to enforce a patent against the party.
If you convey a covered work, knowingly relying
on a patent license, and the Corresponding Source
of the work is not available for anyone to copy,
free of charge and under the terms of this License,
through a publicly available network server or other
readily accessible means, then you must either (1)
cause the Corresponding Source to be so available,
or (2) arrange to deprive yourself of the benefit
of the patent license for this particular work, or
(3) arrange, in a manner consistent with the requirements of this License, to extend the patent
license to downstream recipients. Knowingly relying means you have actual knowledge that, but
for the patent license, your conveying the covered
work in a country, or your recipients use of the covered work in a country, would infringe one or more
identifiable patents in that country that you have
reason to believe are valid.
If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate
by procuring conveyance of, a covered work, and
grant a patent license to some of the parties receiving the covered work authorizing them to use,
propagate, modify or convey a specific copy of the
covered work, then the patent license you grant is
automatically extended to all recipients of the covered work and works based on it.
A patent license is discriminatory if it does not include within the scope of its coverage, prohibits the
exercise of, or is conditioned on the non-exercise
of one or more of the rights that are specifically
granted under this License. You may not convey a
covered work if you are a party to an arrangement
with a third party that is in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying the work, and under which the
third party grants, to any of the parties who would
receive the covered work from you, a discriminatory patent license (a) in connection with copies
of the covered work conveyed by you (or copies
made from those copies), or (b) primarily for and in
connection with specific products or compilations
that contain the covered work, unless you entered
into that arrangement, or that patent license was
granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No
Surrender of Others Freedom.
If conditions are imposed on you (whether by court
order, agreement or otherwise) that contradict the
conditions of this License, they do not excuse you
from the conditions of this License. If you cannot
convey a covered work so as to satisfy simultaneously your obligations under this License and any
other pertinent obligations, then as a consequence
you may not convey it at all. For example, if you
agree to terms that obligate you to collect a royalty for further conveying from those to whom you
convey the Program, the only way you could satisfy
both those terms and this License would be to refrain entirely from conveying the Program. 13. Use
with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have permission to link or combine any
covered work with a work licensed under version
3 of the GNU Affero General Public License into
a single combined work, and to convey the resulting work. The terms of this License will continue
to apply to the part which is the covered work, but
the special requirements of the GNU Affero General
Public License, section 13, concerning interaction
through a network will apply to the combination
as such. 14. Revised Versions of this License.
The Free Software Foundation may publish revised
and/or new versions of the GNU General Public License from time to time. Such new versions will be
similar in spirit to the present version, but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License
or any later version applies to it, you have the
option of following the terms and conditions either
of that numbered version or of any later version
published by the Free Software Foundation. If the
Program does not specify a version number of the
GNU General Public License, you may choose any
version ever published by the Free Software Foundation.
If the Program specifies that a proxy can decide
which future versions of the GNU General Public
License can be used, that proxys public statement
of acceptance of a version permanently authorizes
you to choose that version for the Program.
Later license versions may give you additional or
different permissions. However, no additional obligations are imposed on any author or copyright
holder as a result of your choosing to follow a later
version. 15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE
PROGRAM AS IS WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE.
THE ENTIRE RISK AS TO THE QUALITY AND
PERFORMANCE OF THE PROGRAM IS WITH
YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY
OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE,
BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE
THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED
BY YOU OR THIRD PARTIES OR A FAILURE
OF THE PROGRAM TO OPERATE WITH ANY
OTHER PROGRAMS), EVEN IF SUCH HOLDER
OR OTHER PARTY HAS BEEN ADVISED OF
THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided above cannot be given local legal ef-
this License can be used, that proxys public statement of acceptance of a version permanently authorizes you to choose that version for the Document.
11. RELICENSING
"Massive Multiauthor Collaboration Site" (or
"MMC Site") means any World Wide Web server
that publishes copyrightable works and also provides prominent facilities for anybody to edit those
works. A public wiki that anybody can edit is
an example of such a server. A "Massive Multiauthor Collaboration" (or "MMC") contained in the
site means any set of copyrightable works thus published on the MMC site.
"CC-BY-SA"
means
the
Creative
Commons
Attribution-Share Alike 3.0 license published by
Creative Commons Corporation, a not-for-profit
corporation with a principal place of business in
San Francisco, California, as well as future copyleft
versions of that license published by that same
organization.
"Incorporate" means to publish or republish a Document, in whole or in part, as part of another Document.
An MMC is "eligible for relicensing" if it is licensed
under this License, and if all works that were first
published under this License somewhere other than
this MMC, and subsequently incorporated in whole
or in part into the MMC, (1) had no cover texts or
invariant sections, and (2) were thus incorporated
prior to November 1, 2008.
The operator of an MMC Site may republish an
MMC contained in the site under CC-BY-SA on the
same site at any time before August 1, 2009, provided the MMC is eligible for relicensing. ADDENDUM: How to use this License for your documents
To use this License in a document you have written,
include a copy of the License in the document and
put the following copyright and license notices just
after the title page:
Copyright (C) YEAR YOUR NAME. Permission is
granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no
Invariant Sections, no Front-Cover Texts, and no
Back-Cover Texts. A copy of the license is included
in the section entitled "GNU Free Documentation
License".
If you have Invariant Sections, Front-Cover Texts
and Back-Cover Texts, replace the "with . . .
Texts." line with this:
with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover Texts being LIST, and
with the Back-Cover Texts being LIST.
If you have Invariant Sections without Cover Texts,
or some other combination of the three, merge
those two alternatives to suit the situation.
If your document contains nontrivial examples of
program code, we recommend releasing these examples in parallel under your choice of free software
license, such as the GNU General Public License,
to permit their use in free software.
The Corresponding Application Code for a Combined Work means the object code and/or source
code for the Application, including any data and
utility programs needed for reproducing the Combined Work from the Application, but excluding the
System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3
and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified
Versions.
If you modify a copy of the Library, and, in your
modifications, a facility refers to a function or data
to be supplied by an Application that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of
the modified version:
* a) under this License, provided that you make a
good faith effort to ensure that, in the event an Application does not supply the function or data, the
facility still operates, and performs whatever part
of its purpose remains meaningful, or * b) under
the GNU GPL, with none of the additional permissions of this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from a header file that is part of
the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or
small macros, inline functions and templates (ten
or fewer lines in length), you do both of the following:
* a) Give prominent notice with each copy of the
object code that the Library is used in it and that
the Library and its use are covered by this License.
* b) Accompany the object code with a copy of the
GNU GPL and this license document.
4. Combined Works.
You may convey a Combined Work under terms of
your choice that, taken together, effectively do not
restrict modification of the portions of the Library
contained in the Combined Work and reverse engineering for debugging such modifications, if you
also do each of the following:
* a) Give prominent notice with each copy of the
Combined Work that the Library is used in it and
that the Library and its use are covered by this License. * b) Accompany the Combined Work with a
copy of the GNU GPL and this license document. *
c) For a Combined Work that displays copyright notices during execution, include the copyright notice
for the Library among these notices, as well as a reference directing the user to the copies of the GNU
GPL and this license document. * d) Do one of the
following: o 0) Convey the Minimal Corresponding
Source under the terms of this License, and the Corresponding Application Code in a form suitable for,
and under terms that permit, the user to recombine
or relink the Application with a modified version
of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of
the GNU GPL for conveying Corresponding Source.
o 1) Use a suitable shared library mechanism for
linking with the Library. A suitable mechanism
is one that (a) uses at run time a copy of the Library already present on the users computer system, and (b) will operate properly with a modified
version of the Library that is interface-compatible
with the Linked Version. * e) Provide Installation
Information, but only if you would otherwise be required to provide such information under section 6
of the GNU GPL, and only to the extent that such
information is necessary to install and execute a
modified version of the Combined Work produced
by recombining or relinking the Application with
a modified version of the Linked Version. (If you
use option 4d0, the Installation Information must
accompany the Minimal Corresponding Source and
Corresponding Application Code. If you use option
4d1, you must provide the Installation Information
in the manner specified by section 6 of the GNU
GPL for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work
based on the Library side by side in a single library
together with other library facilities that are not
Applications and are not covered by this License,
and convey such a combined library under terms of
your choice, if you do both of the following:
* a) Accompany the combined library with a copy
of the same work based on the Library, uncombined
with any other library facilities, conveyed under
the terms of this License. * b) Give prominent notice with the combined library that part of it is a
work based on the Library, and explaining where
to find the accompanying uncombined form of the
same work.
6. Revised Versions of the GNU Lesser General
Public License.
The Free Software Foundation may publish revised
and/or new versions of the GNU Lesser General
Public License from time to time. Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or
concerns.
Each version is given a distinguishing version number. If the Library as you received it specifies that
a certain numbered version of the GNU Lesser General Public License or any later version applies to
it, you have the option of following the terms and
conditions either of that published version or of any
later version published by the Free Software Foundation. If the Library as you received it does not
specify a version number of the GNU Lesser General Public License, you may choose any version of
the GNU Lesser General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a
proxy can decide whether future versions of the
GNU Lesser General Public License shall apply,
that proxys public statement of acceptance of
any version is permanent authorization for you to
choose that version for the Library.