Académique Documents
Professionnel Documents
Culture Documents
Programming
William F. Klostermeyer
School of Computing
University of North Florida
Jacksonville, FL 32224
E-mail: klostermeyer@hotmail.com
2
Contents
0 Preface 7
0.1 Course Outlines . . . . . . . . . . . . . . . . . . . . . . . . . . 8
0.2 Java Notes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1 Introduction 13
1.1 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.2 Programming Assignments . . . . . . . . . . . . . . . . . . . 19
1.3 Chapter Notes . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2 Algorithmic Thinking 21
2.1 Programming Assignments . . . . . . . . . . . . . . . . . . . 27
3 Growth of Functions 29
3.1 Big Oh et al. . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.2 Some Example Code Segments . . . . . . . . . . . . . . . . . 39
3.3 Recursive Algorithms . . . . . . . . . . . . . . . . . . . . . . . 42
3.3.1 Recurrence relations . . . . . . . . . . . . . . . . . . . 47
3.3.2 Master Theorem . . . . . . . . . . . . . . . . . . . . . 51
3.4 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
3.5 Programming Assignments . . . . . . . . . . . . . . . . . . . 59
3
4 CONTENTS
Preface
These notes are designed to be accessible: for students to read with the goal
of teaching them to understand and solve algorithmic problems. Numerous
programming assignments are given so that students can see the impact of
algorithm design, choice of data structure, etc. on the actual running time
of real programs.
Emphasis is given to both the design and analysis of algorithms and
although a few proofs of correctness and other mathematical proofs are given
and assigned that is not the primary emphasis of this text. Implementation
details are also considered an important part of the learning experience and,
accordingly, programming assignments are numerous and compilable Java
code is used for all code in the book: most of these programs have exercises
associated with them to encourage the students to run them on interesting
inputs. Students are expected to have had one semester courses in calculus,
data structures, and ideally, discrete mathematics, though a tour of the
Mathematical Preliminaries chapter should provide a sufficient background
for those who have not had a discrete math course. Likewise, those students
who have not had a full semester of data structures, could spend a few weeks
on the data structures chapter before proceeding with the rest of the text
(such might be the model for a course that many schools offer called Data
Structures and Algorithms.)
It is not my goal in these notes to avoid mathematical rigor, rather,
I have found that a course in algorithms is challenging for most students,
as they have not yet developed a lot of experience in algorithmic problem
solving. Thus I believe development of these skills is essential for many
students, before they can be exposed to a hard-core course in algorithmics.
Most of the Algorithms books that I have read are either aimed at the
7
8 CHAPTER 0. PREFACE
5.2.2 Brute-Force
5.3 Path Problems
5.3.2 Dijkstras Algorithm
5.3.4 All-Pairs Shortest Path
5.3.5 Maximum Flow
5.4 Spanning Trees
5.4.2 Prims Algorithm
5.4.4 MST with 0-1 Edge Weights
System.out.flush();
}
//--------------------------------------------------------------
public static String getString() throws IOException
{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String s = br.readLine();
return s;
}
//-------------------------------------------------------------
public static char getChar() throws IOException
{
String s = getString();
return s.charAt(0);
}
//-------------------------------------------------------------
public static int getInt() throws IOException
{
String s = getString();
return Integer.parseInt(s);
}
Chapter 1
Introduction
Anyone who has ever written a program is hopefully aware of the time re-
quired for the program to execute. Some programs may run so fast you
dont notice, while others may run for seconds or minutes or even more.
As an example, try implementing the following (in your favorite language),
which inputs an integer and determines whether or not it is prime (recall
that an integer n is prime if it is evenly divisible only by 1 and n, so for
example, 12 is not prime since 12 = 223, and 17 is prime. In other words,
17 mod i = 0 for all i such that 2 i 16):
n=getInt();
// test if n is prime
prime=true;
for (i=2; i < n; i++) {
if (n % i == 0) prime=false;
}
13
14 CHAPTER 1. INTRODUCTION
Exercise Run this program for a large value of n, say, 32,567. How long
did it take? What about for n = 1, 000, 003? How long did it take?
The clever reader will realize that we can improve the program as fol-
lows, since if n has a non-trivial divisor, it must have one whose value is at
most n:
n : long_integer;
prime : boolean;
input(n);
-- test if n is prime --
prime:=true;
for i in 2..sqrt(n)
if n mod i = 0 then
prime:=false;
end;
Exercise How long did this program take for n = 32, 567, for n =
1, 000, 003? How long does it take for n = 232 1?
Now we have seen several programs to solve the same problem (testing
for primality), where one runs quite slowly and another runs more quickly
(though, as you should observe, still fairly slow for super large values of
n). 1 . The fact that factoring very large integers is computationally time-
consuming is the basis for many encryption schemes such as the RSA algo-
rithm [11]!
Algorithmic problems come in many varieties: numerical problems such
as primality testing or finding the roots of a polynomial, matrix problems,
algebraic problems, logic problems (is a given Boolean formula a tautology),
graph problems (find the largest independent set in a graph), text problems
(find the first occurrence of some pattern in a large text), game problems
(is a given chess position winning for white), and many others. But for
each problem, we shall be concerned with whether it is solvable, whether it
is efficiently solvable, and how to design and analyze algorithms for these
problems.
Every year it seems, faster (and cheaper) computers become available
and the computer companies entice buyers with how fast their programs will
run. However, equally important as the speed of the computer or processor
is the speed of the program (or algorithm) itself. That is, a computer that
is twice as fast as an old computer can only speed up our programs by a
factor of two; whereas a faster algorithm might speed up a program by a
hundred-fold. Compare the above two programs: run the slower one on a
fast computer and the faster one on a slow computer for some large inputs.
What can you conclude?
Our goal in this text is to understand how the choice of algorithm (or
data structure) affects the speed with which a program runs and be able to
effectively solve problems by providing efficient algorithmic solutions, if an
efficient solution is in fact possible.
The ACM (Association for Computing Machinery, the main professional
organization for computing professionals) published a definition of computer
science that begins
1
Faster, though more complex, algorithms for primality testing exist [28, 25, 1, 2]
16 CHAPTER 1. INTRODUCTION
(1) Analysis: given a program (or an algorithm), how fast does it run
(or how much space does it use) and how can we improve or optimize its
performance.
(2) Design: given a problem, how can we solve it efficiently (if in fact, it
is solvable or tractable). A problem is tractable if it admits an efficient
algorithm. We shall see that there are problems (color the vertices of a
graph with as few colors as possible so that adjacent vertices have different
colors) that, although solvable, are not tractable. By solvable, we mean
there exists an algorithm to solve every instance in finite time. The Halting
Problem is an example of an unsolvable problem. In essence, the Halting
Problem asks you to write a program to input the source code of an arbi-
trary C program and output whether or not the program halts on some
particular input (let alone all possible inputs!).
put). We usually denote the input length by n for example, the size of an
array we must process. Then by t(n) we represent the maximum number
of steps taken by our program over all possible inputs of length n. In this
way, we will be able to estimate the actual running time (in minutes and
seconds) as our inputs become larger. For example, if we can sort n integers
in n2 steps and we know that we can sort 10 integers in 1 second; then we
can estimate that sorting 100 integers will take 100 seconds and sorting 1000
integers will take 10,000 seconds (which is almost three hours!). Some of
the programming assignments, on the other hand, will ask you to evaluate
the actual time taken by programs on certain inputs and to compare these
times with the theoretical running times.
Let us give four simple examples. Consider the following code segments.
Input n
Repeat
if n is even then n:=n/2
else n:=n+1
until n <= 1
Input n
Repeat
if n is even then n:=n/2
else n:=2n-2
until n <=1
Input n
Repeat
if n is even then n:=n/2
else n:=3n+1
until n<=1
18 CHAPTER 1. INTRODUCTION
Input n
k:=0
for i:=1 to n
for j:=1 to n
k:=k+1;
end;
end;
print(k);
How many iterations (in terms of the input n) does each segment perform
in the worst case? Try some examples (n = 128, n = 11, n = 13). If
you cannot see a pattern emerging, write a program for each and run with
several different inputs. Even if you see the pattern, write the programs
and compare the actual running times of each (in minutes and seconds) for
various n, say n = 20, 100, 1000.
In the first case, if we pair consecutive iterations, we can see that we
will roughly reduce the number by half until we reach 1. Hence we can say
that about 2 log2 n iterations are needed in the worst case. In the second
segment, we can see that consecutive pairs of iterations will always reduce
the number by at least 1. Hence at most 2n iterations are needed. The third
case is the famous Collatz Problem: no one knows if it terminates on all
inputs or not! The fourth program takes n2 steps (it simply computes n2
from the input n in a very silly way!)
The following table shows the impact the complexity (i.e. this function
describing the running time) has on the actual running time of a program.
Suppose we have a computer that performs 10,000 operations per second.
Let us see how long it will take to run programs with varying complexities
and various small input sizes.
1.1 Exercises
1. For what values of n is 2n less than n3 ?
2. Search the Internet for the word algorithm and list a few important
applications and uses of algorithms. You may wish to refine your search to
say, encryption algorithms, or graph algorithms.
Algorithmic Thinking
Note that if a problem concerns an array A[n], the program to solve the
problem should initially input an integer n, then create an array A with n
elements, then input n values into A, then solve the specified problem.
input(A[1..n])
for i=1 to n
count[i]=0;
end for;
for i=1 to n
for j = 1 to n
if A[i]=A[j] count[i]++;
21
22 CHAPTER 2. ALGORITHMIC THINKING
end for
end for
for i=1 to n
if count[i] > n/2 then begin
print(A[i], is a majority element.);
break;
end
end for;
Exercise: Can you improve upon this solution if we require that each ele-
ment of A be an integer between 1 and n?
input(A[1..n]);
count=1;
for i = 1 to n-1
if A[i]=A[i+1] then count++
else count=1;
if count > n/2 then
print(A[i], is a majority element.);
end for;
This algorithm uses fewer than 2n log n operations, which is a big im-
provement over our previous solution when n is large. The reason for this
is that HeapSort can sort the array using fewer than 2n log n comparisons.
As stated above, we can reduce this to something on the order of n op-
erations using a technique from Chapter 4.
main program:
input(A[1..n])
maj = majority(A);
end main;
Using the analysis methods from Chapter 3, we will be able to show that
this algorithm requires about n log n operations.
26 CHAPTER 2. ALGORITHMIC THINKING
Problem 3. Write a O(n) time algorithm to find the slice (i.e., contiguous
sub-array) of an array whose sum is maximum over all slices. Hint: the
empty slice is defined to have sum zero.
Bonus: Write an efficient algorithm to find the slice whose product is max-
imum. (Hint: you might first want to consider the cases when (1) the array
has no negatives or (2) the array contains only integers).
input A[1..n]
max=0
for i=1 to n
for j=i to n
sum=0
for k = i to j
sum=sum+A[k]
end
if sum > max
max=sum
end
end print(max)
Growth of Functions
29
30 CHAPTER 3. GROWTH OF FUNCTIONS
And since the average value of the terms in this summations is n(n1)
2 ,
2
we see the number of comparisons is about n2 .
In some cases, analysts have determined how many steps certain al-
gorithms perform on average. Bubble sort is known to require (n2 )
comparisons, on average. HeapSort and QuickSort are two such algorithms,
which shall be discussed later. In these case, the average-case means that
one assumes that all inputs of size n are equally likely. The expected num-
ber of comparisons performed by QuickSort is about 1.39n log n [33] and for
HeapSort it is about 2n log n, see for example [9], though this depends on
the implementation.
In general we will be interested in the worst-case running time of an
algorithm: what is the maximum number of steps it will perform, over all
possible inputs. We shall express this running time as a function of the
input size, being interested in how the running time increases as the input
size increases and becomes very large. In Figure 3 we see that the slope of
the curve is not too steep, perhaps reflecting an algorithm whose running
time is linear in its input size, though of course we cannot make judgment
3.1. BIG OH ET AL. 31
about all possible input sizes from just the small sample shown in the Figure.
.....
Time (sec)
,
(,
((
,
,
,
,
Input Size
Notation: We will usually use n to denote the size of the input of an algo-
rithm.
k
C
g(n)
C
C
(
(
CCW ((((((
( ( ( f(n)
(((
n
Figure 2. Function Growth
In Figure 2, we can see that for all n k g(n) is above (is an upper
bound for) f (n). As another example, we can show that log2 n O( n).
Recall that log2 n= (log n)2 . This does not seem obvious since, for example,
log2 64 = 36 and 64 = 8. However, in the limit, that is, as n one can
show that log2 n < O( n, for all sufficiently large n. Compute the values of
these two functions for n = 1, 000, 000.
If we consider f (n) = n and g(n) = n2 , we can see that f (n) > g(n) for
all n. However, n O( n2 ) since we can choose c 2 in Definition 1 to ensure
that n c n2 . Likewise we can observe that
1
Assuming this second program does not in fact run in time O(f (n)) for some f (n) <<
n2 log n.
34 CHAPTER 3. GROWTH OF FUNCTIONS
g(n)
f(n)
n
Figure 3. A Strange Function
Input n
Repeat
if n is even then n:=n/2
else n:=3n+1
until n<=1
We can easily see that the number of steps required by this program
is (log n) as well as 0 (log n). Furthermore, we say that the number of
steps is 0 (log(3n + 1)), since there are infinitely many inputs that are even
and infinitely many that are odd. And certainly you could describe larger
functions than 3n + 1 to use in 0 . But no one knows any finite function
that provides an upper-bound on the running time of this program! Other
such examples exist, such as Millers algorithm to test if an integer is prime
or not. This algorithm runs in polynomial time (in the number of bits of
the integer) if the Extended Riemann Hypothesis is true (which remains an
unsolved problem) [25]. Thus we may not have an accurate knowledge of
the big-Oh running time of this primality testing algorithm if and until the
Extended Riemann Hypothesis is resolved. The resolution of this problem
however, is not likely imminent. 2
We also sometimes use and 0 to describe lower bounds on problems.
That is, we say that the problem of sorting using comparisons (i.e. less
than, greater than comparisons, such as in bubble-sort) is an (n log n)
problem. By this we mean that any comparison based sorting algorithm
will perform at least n log n comparisons for some inputs of length n (for all
n). As another example, consider the problem of searching a sorted array
for a particular element. We may sometimes get lucky and find the desired
element in the first or second cell of the array we look at, but in general,
any algorithm will require (log n) (and thus 0 (log n)) steps binary search
being the algorithm that actually achieves this, taking O(log n) steps:
2
Primality test can be solved in polynomial time using the algorithm of AgrawalKa-
yalSaxena which was discovered in 2002 [2]. However, the Miller-Rabin test is generally
used in practice as it is much faster, though it does have a small chance of error.
3.1. BIG OH ET AL. 37
In other words, we think of g(n) being much faster growing that f (n):
a loose upper bound,
As an example, note that 2n << n! (can you prove this), even though
for 1 3 2n > n!.
Any positive exponential function (i.e., one with a positive base and ex-
ponent) grows faster asymptotically than any polynomial function. And any
polynomial function grows faster than any logarithmic function.
In most cases we can use simple algebra to see which function is asymp-
totically larger than the other. However, this is not always the case. To
determine which of two functions is greater, recall that we are dealing with
the limit as n . Therefore, we say f (n) >> g(n) if and only if the
limit as n of fg(n)
(n)
approaches . Thus it may be necessary to use de
lHopitals rule from calculus:
3.2. SOME EXAMPLE CODE SEGMENTS 39
f (n) f 0 (n)
lim n = lim n 0
g(n) g (n)
where f 0 (n) denotes the derivative of f (n). But this only makes sense
we are interested in the growth rate of a function as n , which
is nothing 3
more than its slope2 . Consider the following two functions:
f (n) = 2n and g(n) = log10 n . From elementary calculus, we know that
f 0 (n) = 12n and g 0 (n) = n2 . Inspection reveals that 12n > n2 for all n > 8.
0
Furthermore, we can see that 12n >> n2 , since the ratio fg0 (n)
(n)
= 2n2n , which
approaches infinity as n approaches infinity. Thus we can see that in general
logc n o( k n) for any positive c, k.
However, simple algebra and common sense usually suffice. For exam-
ple, to show logn n >> n log2 n we do the following. Recall that log2 n =
(log n)2 . Putting logn n over n log2 n we get lognn which is clearly infinite
as n since any polynomial in n is asymptotically greater than any
polylogarithmic function, i.e. logx n, of n, even a polynomial with positive
exponent less than one (as we just showed above). That is, 4 n >> log3 n.
i : integer;
i:=1
while i < n/2 loop
i:=i+1;
end while;
3
We may iterate de lHopitals rule if necessary to the second derivative, and so on
40 CHAPTER 3. GROWTH OF FUNCTIONS
return a[i];
end foo;
i : integer;
i:=3;
a[1]:=1;
a[2]:=1;
while i < n loop
end while;
return i;
end bar;
the other iterations. Thus the running time is O(log n), because if we pair
consecutive iterations of the while, the net effect is that i:=2i-2 in the
worst case. Thus after 2 log n iterations (i.e. log n pairs of iterations) we
have that i 2log n1 + 14 . Clearly, after O(1) more iterations it will be the
case that i n and thus the function will terminate.
Note that it somewhat strange to have a program whose running time is
less than O(n), that is, sub-linear. In such a program, we did not even read
all the input! One could argue that in the above function bar, the running
time depends upon the language implementation. That is, how much time
does computer actually spend passing the parameter a: array[1..n]. If the
array was passed by reference (i.e. only an address was passed) we can safely
say that the array was not actually read by bar at run-time and that the
running time is truly O(log n) (though we can expect the the array was read
somewhere in the main program. In general though, one must approach
sub-linear running times with a bit of caution, to ensure that one does not
actually read the entire input.
p : integer;
k : integer :=0;
input p;
for i:=1 to p loop
if p mod i = 1 then
print(i)
k:=k+i;
end if;
end loop
It is easy to see that this program takes O(p) steps, however we would
not say the running time is O(n). Remember, we generally use n to also
denote the size of the input. In this case, the size of the input is O(log p).
Since we store the integer p as a binary number, its length is on the order
of log p bits. If p is small, we may use a built-in data type such as int
or longint or double depending on the language. But if p is very large,
say 1045634 , we would probably need a user-defined type to accommodate
this and again we would require O(log p) bits to represent n. Since the
number of iterations of the for loop is O(p) and 2log p = p, we have that the
4
Exercise 5 asks you to prove this
42 CHAPTER 3. GROWTH OF FUNCTIONS
main:
input(s);
print(palindrome(s));
end;
end fib;
main:
input(n);
print(fib(n));
end;
We let T (n) denote the time taken to compute the solution on input n.
We can then write T (n) = T (n 1) + T (n 2), and we let T (1) = 1 and
T (2) = 1 (since the time to compute F1 or F2 is O(1) steps).
We can try to solve this by hand, to see if a pattern emerges:
n T (n)
1 1
2 1
3 2
4 3
5 5
6 8
Aha! Its just the Fibonacci numbers, and this should come as no sur-
prise. But this does not tell us our ultimate goal, which is a big-Oh or
big-Omega description of the running time of our program. We do this as
follows.
Let = 1+2 5 . Using some algebra, we can see that is the positive
root of the polynomial x2 x 1; that is, 2 1 = 0.
44 CHAPTER 3. GROWTH OF FUNCTIONS
Theorem 6 T (n) n .
Proof: By induction on n.
has the solution T (n) = 2Fn 1, which is almost twice that of our original
recurrence! To see this, look at how many times the 1 is added as the
recurrence unfolds. For example, let us compute T (5):
3.3. RECURSIVE ALGORITHMS 45
class recbinsearch
// Note that this version does not have the binary search
// method tied to an object.
{
public static void main(String[] args)
{
int i;
int maxSize = 100; // array size
int [] arr = new int[maxSize]; // create the array
} // end main()
public static boolean binsearch(int [] a, int lo, int hi, int key)
{
if (lo == hi)
return (a[lo] == key);
else {
int mid = (lo + hi)/2;
3.3. RECURSIVE ALGORITHMS 47
} // end
We can express the running time of the recursive binary search (assum-
ing negligible cost for passing the array as a parameter) as
C: array[1..p+q];
if i <= p then
for m:=i to p loop
C[k]:=A[m];
k:=k+1;
end for
else
for m:=j to q loop
C[k]:=B[m];
k:=k+1;
end for
end if
return C;
end merge;
end MergeSort;
Show T (n + 1) = 2n+2 (n + 1) 2.
= 2n+2 2n 4 + n + 1
= 2n+2 (n + 1) 2, as desired.
Then
O(n) if a < b
T (n) = O(n log n) ifa = b
O(nlogb a ) if a > b
Then
Case 1. If f (n) = O(nlogb a ) for some constant > 0 then T (n) =
(nlogb a ).
Case 3. If f (n) = (nlogb a+ ) for some constant > 0 and if af ( nb ) df (n)
for some constant d < 1 and for all sufficiently large n then T (n) = (f (n)).
Suppose
n
T (n) = 9T ( ) + n2 .
3
Case 1 does not apply because f (n) = n2 / O(nlog3 9 ) for any > 0. For
example, if we choose = 0.01, we have that n2 / O(nlog3 90.01 ) = O(n1.99 ).
2 log 9 2
But we see that f (n) = n = O(n 3 ) = O(n ) and so Case 2 applies. Thus
T (n) = O(n2 log n).
We have already seen recurrences such as T (n) = T (n c) + f (n) for
some c (c may be a constant or a function of n) to which the master theorem
obviously cannot apply. One way to get a loose bound on recurrences of
this form is to determine the depth of the recursion and multiply that by
f (n). So for example, we can bound T (n) = T (n 1) + n by O(n2 ) since
the depth of the recursion is O(n). This happens to be a precise bound, i.e.
T (n) = (n2 ), but this may not always be the case.
Other recurrences such as T (n) = 2T (n 1) + 1 cannot be bounded so
easily and need to be attacked in other ways (see Exercise 6). Yet many
recurrences, even those of the proper divide-and-conquer format, i.e. T (n) =
aT ( nb ) + f (n), cannot be solved using the master theorem. For example,
suppose we have a divide-and-conquer type recurrence
n
T (n) = 4T ( ) + n2 log n .
2
then f (n) = n2 log n and a = 4, b = 2, so nlogb a = n2 . Since n2 log n >> n2 ,
Case 1 and Case 2 cannot apply. Checking case 3, we test whether n2 log n =
(n2+ ) for some constant > 0. For example, is n2 log n = (n2.001 ).
The answer is no, because n2+ >> n2 log n for any positive epsilon: any
polynomial function with positive exponent dominates any polylogarithmic
function. Thus the master theorem does not apply. How might we solve
such a recurrence? We could use more advanced methods such as discussed
in [34] or settle for a loose bound. That is, we can see that
n n
T (n) = 4T ( ) + n2 log n << 4T 0 ( ) + n2.1
2 2
Using the master theorem, Case 3 applies, since f (n) = n2.1 (n2 ) and
n
4( )2 0.75n2.1 , for all positive n.
4
Hence we may conclude T 0 (n) = (n2.1 ) and T (n) = O(n2.1 )
Consider the following program for finding the maximum element in an
array. 5
5
The reader may wish to implement this and compare its running time with the iterative
method that uses a for loop.
54 CHAPTER 3. GROWTH OF FUNCTIONS
end maximum;
main:
input(A[1..n]);
max:=maximum(A);
end main;
The running time of this maximum-finding algorithm is described by
n
T (n) = 2T ( ) + 1
2
since we divide the array in half and recursively process each half. The
time to compare the two maximums returned from the recursive calls is
O(1). Since f (n) = O(1) O(nlog2 2 ) for = 0.5 (i.e., O(1) O( n))
Case 1 of the master theorem applies and we conclude the running time is
O(nlog2 2 ) = O(n).
We shall see more recursive programs in subsequent chapters and use
the Master Theorem to analyze many of them.
The Master Theorem cannot be used to give precise bounds on all divide-
and-conquer type recurrences. Consider T (n) = 2T (n/2) + logn n . None of
the three cases apply, as logn n is not polynomially smaller than n, though it
is smaller. We can however, say that in this case T (n) < 2T (n/2) + n and
conclude that the recurrence is O(n log n).
3.4 Exercises
1. Find the smallest positive n such that 4
n > log3 n. (Hint: use trial and
error).
3.4. EXERCISES 55
2log n nn n3 20n2 log log2 n nlog 5 n log n 22n 75 n3
input(k);
i:=3;
for j:= 1 to k loop
i:=2i - 2;
end loop;
print(k);
6 0
T (n) is the recurrence for the algorithm for the famous Towers of Hanoi problem
[26].
56 CHAPTER 3. GROWTH OF FUNCTIONS
a) T (n) = T (n 1) + log n
b) T (n) = T ( n3 ) + n
c) T (n) = T (n 2) + n
d) T (n) = T ( 3n
4 ) + 1. (Solve this using the Master theorem and try solving
it by analyzing the depth of the recursion).
e) T (n) = T (n) + n2
Bonus T (n) = T (n 1) + T ( n2 ) + n.
Bonus T (n) = 2T (n 1) + n2
[11] Bonus T (n) = T ( n) + 1.
9. Solve the following recurrences using the Master Theorem (give a big-Oh
or Theta solution). If none of the cases apply, provide a loose bound by
substituting another function for f (n). Assume T (1) = 1 in all cases.
a) T (n) = T ( n3 ) + log n
b) T (n) = 6T ( n3 ) + n log n
c) T (n) = 8T ( 3n
2 )+n
2
d) T (n) = 3T ( n2 ) + n2
10. Analyze the running times of the following programs (first find a recur-
rence describing the running time of the program then solve the recurrence.)
3.4. EXERCISES 57
a)
return foo(a[2..n]);
end;
main:
input(a[1..n]);
x:=foo(a);
end;
a:=concatenate(foo(a[n/2+1..n]), foo(a[1..n/2]));
end foo;
main:
input(a[1..n]);
58 CHAPTER 3. GROWTH OF FUNCTIONS
a:=foo(a);
end;
c) [Bonus]
end foo;
main:
input(a[1..n]);
x:=foo(a);
end;
13. Consider the recurrence T (n) = nT (n/2) + 1. Draw a recursion tree and
Qi=log n n
show that this sum is greater than or equal to i=0 2i
. Then show that
this product is equal to nlog n /2(log n)(log n+1)/2 and then show this is equal
log n 1
to n 2 n 2 .
17. Describe an algorithm that inputs two n-digit numbers and uses the
algorithm you learned in grammar school to compute their product. What
is the running time of this algorithm?
Bonus: Show that a solution for the recurrence: T (n) = T ( n) + 1, is
(log log n). Hint: Show that log log n is an upper bound on the depth of
the recursion [38].
Bonus: Euclids algorithm for finding the greatest common divisor (gcd) of
two positive integers is one of the oldest numerical algorithms. It is naturally
recursive in nature, very fast, and based on the following idea. If a > b, then
gcd(a, b)=gcd(b, a mod b). Write pseudo-code for this algorithm (you need
to determine the termination condition for the recursion) and analyze the
running time.
4.1 Searching
Let A be an array with n elements and key a value we wish to search for.
That is, we want to know whether or not any element of A is equal to key.
The simplest way to perform this this is to sequentially compare each of the
n elements of A to key. This takes O(n) steps in the worst case the worst
case being when key = A[n] or if key is not in the array. Of course, if key is
in the array, we would expect, on average, that n2 comparisons would suffice.
Note that sequential search does not require that A be sorted in any order.
If A is sorted, we can achieve much faster search results, as described below.
In the previous chapter we showed the classic iterative binary search
procedure. Given an array of length n and a key value; the binary search
procedure makes at most O(log n) comparisons to either find the key or de-
termine that the key is not in the array. Let us now recall the recursive
version of binary search:
61
62 CHAPTER 4. SORTING, SELECTION, AND ARRAY PROBLEMS
end binary_search;
main:
input(A[1..n]);
input(key);
found:=binary_search(A, key);
end main;
Proof: The base case is trivial. Assume for the inductive hypothesis that
T ( n2 ) c log n2 for some constant c. Now substituting we have that
n n
T (n) = T ( ) + O(1) c log + d
2 2
where d = O(1). Simplifying, we get
T (n) = c log n c + d
4.1. SEARCHING 63
which implies
T (n) = c log n
since we can choose c d.
Note that we could have used the Master Theorem to arrive at the same
conclusion.
Suppose we wish to search an array with n elements for both the max-
imum and minimum elements. Obviously, two sequential searches can ac-
complish this with no more than 2n 2 comparisons (since, for example, the
maximum can be found with n 1 comparisons by initializing a variable to
the first element in the array and then doing comparisons with elements 2
through n).
However, we can accomplish this task with only 3n 2 comparisons as fol-
lows. Rather than considering elements from the array one at a time, we
consider then two at a time. We first find the smaller of the two elements
and compare that with min (the current candidate for the minimum ele-
ment from the array) and then compare the larger of the two with max (the
current candidate for the minimum element from the array).
The algorithm in detail:
procedure max_and_min
a : array[1..n] of integer;
max : integer;
min : integer;
m1, m2 : integer;
max:=a[1];
min:=a[1];
end if;
if n mod 2 = 0 then
if a[n] > max then
max:= a[n];
else
if a[n] < min then
min := a[n];
end if;
end if;
The for loop iterates no more than n2 times and each iteration contains
three comparisons, hence the total number of comparisons is at most 3n 2 .
4.2 Sorting
We have already seen MergeSort and BubbleSort; the former requiring
O(n log n) steps in the worst case and the latter O(n2 ) steps in the worst
case to sort an array with n elements.
4.2.1 QuickSort
QuickSort is one of the most widely used sorting algorithms in practice,
for example the UNIX program qsort is an implementation of QuickSort.
Though a worst-case O(n2 ) time algorithm, as we mentioned above, Quick-
Sort runs on average in O(n log n) steps. That is, if we average the number
of steps taken over all possible inputs (permutations) of length n, the aver-
4.2. SORTING 65
pivot : integer;
k : integer;
begin
return b;
end;
4.2. SORTING 67
begin
lo:=0;
hi:=m;
repeat
lo:=lo + 1; -- find element in lower part of array greater than pivot
until b[lo] >= pivot
temp:=b[lo]
b[lo]:=b[hi];
b[hi]:=temp
end if;
end loop;
if pivot=b[lo] then k:=lo else k:=hi; --- locate where the pivot is at the end of shuf
b[k] := pivot; --- Move pivot to "middle"
end;
68 CHAPTER 4. SORTING, SELECTION, AND ARRAY PROBLEMS
main:
input a;
a:=quicksort(a);
if min != i then {
swap(A[i], A[min]) // exchange values at two indices
Heapify(A, min)
}
// Work from the leaf level up. At each node, push node i down using
// Heapify to the correct location. After processing node i,
// subtree rooted at i will be a heap
} // for
4.2. SORTING 71
n++;
i = n;
while i > 1 and A[i/2] > key { // percolate up
A[i] = A[i/2];
i = i/2;
}
A[i] = key;
A[i] = new_key; // assuming this is less than old key value while
i > 1 and A[i/2] > new_key // percolate up
72 CHAPTER 4. SORTING, SELECTION, AND ARRAY PROBLEMS
swap(A[i], A[i/2]);
i=i/2;
}
Since the height of a heap is O(log n), insert and deletemax operations
can be done in O(log n) time. It is trivial to see that a heap can be built
in O(n log n) time (consider n insert operations, each taking O(log n) time),
but a more sophisticated analysis of the Makeheap() procedure above shows
one can be built in O(n) time.
Numerous other implementations of priority queues are known, such as
Fibonacci heaps, with different performance characteristics. Other priority
queues with useful properties include binomial heaps, leftist heaps, pairing
heaps, thin heaps, and Brodal queues.
BONUS: summarize the performance of all the priority queues mentioned
above.
4.2.3 HeapSort
Heapsort is an algorithm that runs in O(n log n) in the worst-case, based on
the heap data structure. A heap is a binary tree such that each nodes
value is greater than or equal to the value of its children. Heapsort is
sometimes faster in practice than MergeSort, since it does not incur the
overhead associated with recursion. MergeSort, however, often uses the
cache more efficiently than heapsort. Heapsort requires O(1) extra space, in
addition to the array to be sorted, whereas MergeSort requires O(n) extra
space.
Various analyses of heapsort have been done. Assuming we use Floyds
O(n) algorithm to build a heap (which takes 2n comparisons in the worst-
case and about 1.88n on average [9], the heapsort algorithm takes about
2n log n comparisons in the worst case (minus some low order terms) and
about the same on average [30]. Slightly better ways to build a heap are
known [9].
The variant known as bottom-up heapsort due to McDiarmid and Reed
requires at most 1.5n log n 0.4n comparisons, cf. [14]. A heapsort variant
due to Carlsson uses only n log n+n log log n0.82n comparisons in the worst
case and another variant of his uses n log n + 0.67n + O(log n) comparisons
on average [9]. Dutton introduced the weak-heap sort (which relaxes the
4.2. SORTING 73
requirement of the data structure being in heap order) and creates a sorting
algorithm using (n 1) log n + 0.086013n comparisons in the worst case [14].
The weak-heap data structure does require n additional bits of data to store
n keys.
Implementations of the basic heapsort algorithm tend to be about twice
as slow as quicksort in practice [30]. MergeSort generally is more efficient
in terms of cache performance than heapsort (unless the cache is very small
or very large), though MergeSort requires more memory than heapsort, but
MergeSort is stable. Whether MergeSort or heapsort is better in practice
would depend on a number of application-specific factors such as this. Some
variations of weak-heap sort seem competitive with quicksort in practice.
The basic heapsort algorithm is as follows and was adapted from http://www.inf.fh-
flensburg.de/lang/algorithmen/sortieren/heap/heapen.htm
74 CHAPTER 4. SORTING, SELECTION, AND ARRAY PROBLEMS
}
}
c : constant;
a : array[1..n] of integer range 1..c;
count : array[1..c];
i, j : integer;
Note that if c was not a fixed constant, then steps (*) and (**) would
require time dependent on n. But since c is a constant, we can see that the
number of steps taken by the algorithm is at most 2c + 2n = O(n), since
step (**) is executed n times. There may be some is such that count[i] = 0,
so we must allow that the cost of the final for loop is as much as n + c.
A stable counting sort is described in [11]. A sorting algorithm is stable
if records with equal valued keys appear in the sorted list in the same order
as in the original list. This is useful when, for example, we first sort an array
of records on a secondary key (such as age) and then sort on the primary
key (such as name). In this example then, the people named John Smith
would appear in the sorted list in increasing order of age.
Radix sort is designed to sort n numbers, each with d digits (left padded
with zeroes if necessary) or equivalently, strings having at most d characters
each. The idea is to make d passes over the data, examining the ith least
4.3. FINDING THE MEDIAN 77
significant digit on pass i. For example, suppose we have the numbers 26,
31, 46, and 17. We have maintain 10 lists (since there are 10 possible values
for a digit). We insert items into these lists (imagine each list is a queue or
a linked list) based on the ith least significant digit of each. So after pass
1, we have 26 and 46 in the 6 list, 31 in the 1 list and 17 in the 7
list. Re-arrange these by outputting the items in the lists (processing the
lists from 0 to 9) and we have the list 31, 26, 46, 17. Now clear the lists and
pass over the second least significant digit and we put 31 into the 3 list,
26 into the 2 list, 46 into the 4 list and 17 into the 1 list. Then we
output 17, 26, 31, 46.
Choosing pivot:
select(L,k)
{
for (i = 1 to n/5) do
x[i] = median(S[i]) // sorting S[i] is one way to do this
if (k <= length(L1))
4.4. MORE ARRAY PROBLEMS 79
return select(L1,k)
else if (k > length(L1)+length(L2))
return select(L3,k-length(L1)-length(L2))
else return M
Then clearly T (n) O(n) + T (n/5) + T (3n/4). The O(n) terms from
choosing the medians of each group and partitioning the array around M.
One can prove by induction that this is O(n), mainly because 0.2 + 0.75 < 1.
A randomized version of this chooses M randomly, rather than via re-
cursive call: this will be fast in practice, though with the chance of running
slowly if you are unlucky.
input x (length n)
input y (length l)
match = -1; // set to start
index of matching substring if we find it found = true
i 0 1 2 3 4 5 6 7
f(i) 0 0 1 0 0 1 2 3
For instance, f(6)=2 since the first 2 characters of y are the same as
characters 5 and 6. That is, as is the longest proper prefix of aabbaa that
is a suffix of aabbaa.
We use the failure function as follows to find y in x. We scan x, compar-
ing the characters of x with those of the pattern y. When a match occurs,
we look at the next character of x and the next character of y. When a
mismatch occurs, rather than back up in x, which would lead to a non-
linear time algorithm, we simply use the failure function to determine which
character of the pattern to compare with the next character of the text.
For example, suppose y = aabbaab and x = abaabaabbaab, the algorithm
described would behave as follows. Since the pattern has 7 characters, there
are eight states of the algorithm, corresponding to the number of matched
characters in the pattern, 0-7.
Input: a b a a b a a b b a a b
State 0 1 0 1 2 3 1 2 3 4 5 6 7
aabbaab
aabbaaaaabbaab
That has a mismatch on the third character of y (still the seventh char-
acter of x), so we make the following alignment:
We now look at the computation of the failure function, which will take
O(l) time.
Of course, f(0)=0 and f(1)=0.
The rest of the table is built incrementally.
i 0 1 2 3 4 5 6 7
f(i) 0 0 1 0 0 1 2 3
To see that this is O(l), only the while loop needs to be considered, as
each other statement in the for loop is O(1) time. The cost of the while loop
is proportional to the number of times i is decremented by the i :=f (i) line
inside the while loop. The only way i is incremented is by the f (j) :=i + 1
84 CHAPTER 4. SORTING, SELECTION, AND ARRAY PROBLEMS
statement and the corresponding j:=j + 1 (in the for loop) and i:=f (j 1)
statements. Since i = 0 initially and since is incremented at most l1 times,
we can conclude that the total number of iterations of the while loop (over
all iterations of the for loop) is O(l).
Sum = 0;
For a = 1 to n
Sum = Sum + A[i, a] * B[a, j]
end
Product[i, j] = Sum
Since there are O(n2 ) entries in Product, the running time is O(n3 ).
4.6. ANOTHER RECURSIVE ALGORITHM 85
If the initial matrix M is such that n is not a power of 2, embed into the
upper left corner of an n0 n0 matrix where 2n > n0 > n and n0 is a power
of 2; fill the rest of this array with 0s.
Asymptotically faster, albeit more complex, recursive matrix multiplica-
tion algorithms have been discovered since Strassens, though they are likely
not faster in most practical situations. In fact, Strassens algorithm is faster
than the naive method only when n gets fairly large (perhaps n > 100 or
n > 1000, depending on hardware).
86 CHAPTER 4. SORTING, SELECTION, AND ARRAY PROBLEMS
4.7 Exercises
1. Execute procedure shuffle from QuickSort by hand on the following array
< 7, 3, 1, 2, 5, 8, 11, 14, 0, 3, 16, 19, 23, 6 > with pivot element 8.
Problem a: You have exactly one pumpkin. Explain why binary search
doesnt work.
tually be solved in O(n) time, see Frederickson and Johnson, SIAM Journal
on Computing, 1984]
9(b). Now suppose A contains n positive integers. Find the longest sub-
sequence of elements such that each element less than the next element. For
example, of A = 20, 3, 7, 12, 8, 10, 15, 9, 21, the longest such subsequence is
3, 7, 8, 10, 15, 21. Your algorithm should run in O(n2 ) time or better.
10. Given an array A[1 . . . n] of any type values. Print out the values
in the order they appear in A, removing any duplicates. Can you find an
algorithm that runs in o(n2 ) time?
Example:
A: 7 3 4 6 7 3 5 6 8
output 7 3 4 6 5 8
11. Give a fast algorithm to find the mode (most frequently occurring
element) in an array.
12. Given an array A[1..n], give an O(n) time algorithm to find the
substring of A (a substring of consecutive elements) whose sum is maximum.
Example:
A: 1 4 6 8 -23 4 6 7
answer: A[1..4] whose sum is 19
88 CHAPTER 4. SORTING, SELECTION, AND ARRAY PROBLEMS
14. Input two separate lists of n pairs each. Each pair is of the form
(a, b) where each a, b is an integer between 1 and n. No pair appears more
than once in a list. In linear time, output the pairs that occur in both lists.
15. An inversion is a pair of elements in an array A, say A[i] and A[j],
such that i < j and A[i] > A[j]. What is the is maximum number of
inversions that an array with n elements can have? What is the relationship
between a swap in the execution of Bubble Sort and an inversion?
5.1 Storage
A graph will be denoted by G = (V, E) where V is the vertex set (usu-
ally |V | = n) and E is the edge set (usually |E| = m). In this class we
will consider undirected and directed graphs (digraphs) and weighted and
unweighted graphs (weights will usually be edge weights). Graphs are com-
monly used to model many structures such as maps, computer networks
and relationships (e.g. in a database or a family tree or any type of flow or
organizational chart). A graph is simple if it has no multi-edges. We assume
simple graphs unless otherwise noted. A self-loop is an edge from a vertex
to itself.
89
90 CHAPTER 5. GRAPH ALGORITHMS AND PROBLEMS
3 4
2 2 1
1
4
1 2 3
5
Figure 5.1: Graph G
0 2 0 0 0
1 0 3 0 2
A=
0 0 0 0 0
0 0 4 0 0
0 0 0 3 0
the 0-1 entries and null used as an entry when there is no edge.
3) Adjacency List. A linked list is maintained for each vertex v that contains
all vertices u adjacent to v (v u edges in the case of digraphs). It costs
an extra pointer for each edge, but if G is sparse, this will be require less
space than a matrix. Some algorithms run more efficiently with adjacency
lists, as we shall see later. Figure 5.3 is the adjacency list for the digraph
in Figure 5.1. If G is weighted, an extra field can be added to each node in
the linked list.
This requires O(n + m) space but can require O(n) time to determine of
two vertices are adjacent, compared with O(1) time in a matrix. Also note
that the headers (the left hand box in Figure 5.3) are stored in an array for
easy indexing.
1 2
2 1 3 5
4 3 2
5 4
never re-evaluated). For some problems, greedy algorithms can lead to fast
algorithms that always yield optimal solutions to the problem (see Dijsktras
algorithm below for an example). For others, greedy algorithms to not
always yield optimal solutions (see the section on NP-completeness or the
greedy dominating set algorithm discussed below). Yet in such cases, they
may still be useful due to their speed.
5.2.2 Brute-Force
Brute-force, also known as exhaustive search, is a technique whereby all pos-
sibilities are examined in turn. For some problems this technique may prove
reasonably efficient and for some problems it may not (e.g., when there are
exponentially many possibilities to consider). Trial division for primality
testing is an example of this that we have already seen.
Exercises:
Q = empty queue;
color all vertices white;
end BFS;
S: those whose shortest path from the source has already been deter-
mined and
V-S: those whose shortest path is thus far unknown. A vertex in V-S
is added to S by selecting the vertex which will have the minimum path
lengthclearly this vertex is a neighbor of at least one vertex in S.
1. for each v in V
dist[v]=infinity
pred[v]=nil -- keep track of predecessor in shortest path
2. dist[source]=0
3. S:={}
4. construct Q -- priority queue based on dist values of vertices in V-S
5. while Q != {} do begin
6. u:=extract_min(Q);
7. S:=S + {u}
8. for each v adjacent to u do
9. if dist[v] > dist[u] + length(u, v) then -- found shorter
-- path to v
10. dist[v]:=dist[u] + length(u, v)
11. pred[v]:=u
12. end
|V | times. In addition, each edge is examined exactly once at line 8 for O(E)
time. Thus we have O(V 2 + E) = O(V 2 ).
Implementing the priority queue using a heap, we can achieve a running
time of O((V+E)logV)), which is O(E log V) if all nodes are reachable from
the source (connected to the source). This is because building the heap takes
O(V) time. Each subsequent extract min takes O(logV) time, of which there
are O(V). Each iteration of the for loop that updates the path lengths takes
O(logV) time to heapify, and there are O(E) of those.
In practice, elements with infinite distance values should be inserted onto
the heap as they are encountered for the first time, rather than as part of
the initialization phase. This will reduce the size of the heap for many of
the computations performed.
If the graph is sparse, (E < O(n2 / log n)) this is faster than the naive
priority queue method.
The problem with the heap implementation is the update phase. How-
ever, using Fibonacci heaps [16] we can improve this phase and achieve a
running time of O(V log V + E) time. Experimental results typically find
that using Fibonacci Heaps is somewhat slower in practice than heaps or
other data structures (e.g., buckets).
Notes 1. Single source shortest paths can be found in DAGs in O(V +E)
time by using topological sort (see exercise 4.7).
3 4
8
1 3
4
5
2
7
5 4
would be O(|V |3 + |V ||E| log |V |) using heaps and O(|V |3 ) using Fibonacci
heaps. If negative weight edges exist, run Bellman-Ford |V | times (yielding
an O(|V |2 |E|) running time). The dynamic programming approach will take
O(|V |3 ) time (and have small hidden constants!).
We shall assume an adjacency matrix representation of the graph and
use zero for the diagonal entries, integers for edge weights, and infinity for
the weights of non-existent edges.
Figure 5.4 contains a digraph we shall use in our example (again, it is
from [11]).
We give two algorithms, the second a refinement of the first. Both are
dynamic programming but they use different criteria.
Let D0 be the initial adjacency matrix. Both algorithms will construct
additional matrices, with the final matrix holding the solution. Let n = |V |.
Figure 5.5 is D0 (we will use it in both algorithms), which is just the adja-
cency matrix of Figure 5.4.
Algorithm 1. Idea: Dijm (that entry in the table) is the shortest path be-
tween vi and vj having at most m edges (I shall explain below why I use
quotes here). We build up paths having more and more edges as we move
down the table.
0 3 8 4
0 1 7
A=
4 0
2 5 0
6 0
input D1 = D0
for m:=2 to n-1 do begin /* since path can be up to length n-1 */
for i:=1 to n do begin /* n2 entries in D */
for j:=1 to n do begin
m := MIN (D m1 , D m1 + D 1 , D 1 + D m1 ),
Dij ij ip pj ip pj
over all p, 1 p n
end;
end;
end;
input D1 = D0
for m:=2 to n-1 do begin /* since path can be up to length n-1 */
for i:=1 to n do begin /* n2 entries in D */
for j:=1 to n do begin
m := MIN (D m1 , D m1 + D m1 ), over all p, 1 p n
Dij ij ip pj
end;
end;
end;
At the MIN step, we search for a better path with m edges that passes
through each other vertex possible intermediate vertex p. Running time is
O(n4 ). The execution on the input of Figure 5.4 can be seen in Figures
5.6-5.8.
To see how this algorithm works, consider D3,5 4 = 3. We computed this
3 3 3
when we saw that D3,1 + D1,5 = 3 < 11 = D3,5 . Also note that since all the
entries in D4 are finite, G is strongly connected.
Note that in the MIN step, we may build a path that is of length
98 CHAPTER 5. GRAPH ALGORITHMS AND PROBLEMS
0 3 8 2 4
3 0 4 1 7
A=
4 0 5 11
2 1 5 0 2
8 1 6 0
Figure 5.6: D2
0 3 3 2 4
3 0 4 1 1
A=
7 4 0 5 11
2 1 5 0 2
8 5 1 6 0
Figure 5.7: D3
0 1 3 2 4
3 0 4 1 1
A=
7 4 0 5 3
2 1 5 0 2
8 5 1 6 0
Figure 5.8: D4
5.3. PATH PROBLEMS 99
0 3 8 4
0 1 7
A=
4 0
2 5 0
6 0
Algorithm 2. Floyd-Warshall.
k is the shortest path between v and v which passes through no
Idea: Dij i j
vertex numbered higher than k. By through, we mean the path enters
k makes sense even when j > k.
and leaves the vertex. So, for example, Dij
input D0
for k:=1 to n do begin /* paths passing thru verts numbered n */
for i:=1 to n do begin /* n2 entries in D */
for j:=1 to n do begin
k := MIN (D k1 , D k1 + D k1 )
Dij ij ik kj
end;
end;
end;
This runs in time O(n3 ). Now let us show, in Figures 5.9-5.14, the
1 . The
matrices generated by Floyd-Warshall. In this example, consider D4,2
matrix indices indicates we are looking for the shortest path from v4 to v2
that passes through no vertex numbered higher than 1. Since there is no
0 = . But since there is an edge from v to v (of
edge from v4 to v2 , D4,2 4 1
0 ) and from v to v (of weight 3, as shown in D 0 )
weight 2, as shown in D4,1 1 2 1,2
1 to 5 (so we might say that v is an intermediate vertex on this
we set D4,2 1
path i.e. we do this computation when k = 1 in the algorithm).
EXERCISE: Can these algorithms be easily modified to find the all-pairs
longest paths (such paths must still be simple paths)? Justify your answer.
100 CHAPTER 5. GRAPH ALGORITHMS AND PROBLEMS
0 3 8 4
0 1 7
A=
4 0
2 5 5 0 2
6 0
Figure 5.10: D1
0 3 8 4 4
0 1 7
A=
4 0 5 11
2 5 5 0 2
6 0
Figure 5.11: D2
0 3 8 4 4
0 1 7
A=
4 0 5 11
2 1 5 0 2
6 0
Figure 5.12: D3
0 3 1 4 4
3 0 4 1 1
A=
7 4 0 5 3
2 1 5 0 2
8 5 1 6 0
Figure 5.13: D4
0 1 3 2 4
3 0 4 1 1
A=
7 4 0 5 3
2 1 5 0 2
8 5 1 6 0
Figure 5.14: D5
5.3. PATH PROBLEMS 101
Min Cut
The dual problem of finding a maximum flow is that of finding the minimum
cost cut in a network. A cut of a network is a set of edges whose deletion
partitions the network into two (or more) distinct components such that
there is no path between vertices in different components, the source in one
and the sink in the other. The actual cost of cut set are those edges
between the two partitions. Thus we want to cut the flow from the source
to the sink by disabling the minimum total weight of edges.
A famous theorem, the Max-flow/Min-Cut theorem shows the relation-
ship between these two properties:
5.4. STABLE MARRIAGE PROBLEM 103
That is, the sum of the capacities of the edges in a minimum-weight cut
equals the value of the maximum flow.
Bipartite Matching
Another problem related to that of a maximum flow us that of finding a
bipartite matching. We may think of the bipartite graph G = (V, E), V =
L R as follows. Let L be a set of boys and R a set of girls. We want
to marry the boys to the girls so that the maximum number of marriages
occur with the constraint that a couple may marry if and only if there is a
compatibility edge between them indicating they are willing to marry.
Modify G by adding a new node s and connecting s to each vertex in L.
Add a new vertex t and connect it to each vertex in R. Now let each edge in
the graph have capacity one. Now it is clear that a maximum flow from s to
t can use each vertex in L only once (i.e., at most one unit flow can flow into
and out of such a vertex) and each vertex in R at most once Hence the edges
between L and R used in the maximum flow must be independent, that is,
have no vertices common. And these edges therefore constitute a maximum
matching. So we can use the Ford-Fulkerson algorithm using BFS to find
the maximum bipartite matching in O(V E) time.
In general graphs, not necessarily bipartite, the maximum matching
problem can be solved in O(V E) time, due to Edmonds. This problem
asks us to find the maximum size set of edges such that no two share a
vertex. Edmonds algorithm for this (from the 1960s) is quite-complicated
and clever.
1. for each v in V
dist[v]=infinity
parent[v]=nil -- keep track of parent in spanning tree
2. dist[source]=0
3. S:={}
4. construct Q -- priority queue based on dist values of vertices in V-S
5. while Q != {} do begin
6. u:=extract_min(Q);
7. S:=S + {u}
8. for each v adjacent to u such that v is not in S do
9. if dist[v] > length(u, v) then -- found shorter
-- edge to v
10. dist[v]:=length(u, v)
11. parent[v]:=u
12. end
Using heaps, the running time is easily seen to be O(E log V ), since we
do at most E heap update operations. If we use Fibonacci heaps, we get an
O(V log V + E) running time.
1. T := {}
2. let each vertex be a singleton set in the UNION-FIND data structure
3. Sort edges in increasing order of weight
4. for each edge uv (considered in increasing order of weight)
5. if FIND(v) != FIND(u)
6. add uv to tree
7. UNION(u, v)
The running time is O(E log E) = O(E log V ), since the UNION-FIND
operations take only O(E(E, V )) time. Note that if the edge weights can
5.5. SPANNING TREES 107
be sorted quickly, e.g., with counting sort, then this is a very good algorithm.
Use BFS to find a spanning tree T of G (all weights will be weight 1).
Find the edges in G corresponding to the edges of T. Adding these edges
to F forms a MST of G.
To form G:
Form one vertex for each tree of F. Label each vertex of G with tree of
F that it is in (takes O(V+E) time). For each edge of G, add corresponding
edge to G (may contain redundant edges, but this still only takes O(V+E)
time and is easier than trying to ensure G is a simple graph). We can attach
to each edge of G a field describing the endvertices of the corresponding edge
of G.
108 CHAPTER 5. GRAPH ALGORITHMS AND PROBLEMS
5.6 Exercises
1. Describe (at a high level) an algorithm to find the largest and second
largest elements in an array using at most n + log n comparisons. That is,
we only care about the number of comparisons between values in the array.
Hint: Assume n is a power of 2 and think about having a tournament to
determine the largest element.
2. Let G = (V, E) be a digraph. Our goal is to minimize the maximum
outdegree over all vertices in the graph by orienting edges that is we can
reverse the direction of individual edges in order to achieve this goal. We
want a polynomial time algorithm to do this. Hint: transfer an outgoing
arc from a vertex with large outdegree to a vertex of small outdegree by
finding a path between the two vertices. Repeat this process.
3. (Bonus) Study the minimum spanning algorithm which runs in O(E(E, V ))
time and is due to Chazelle. It is based on the soft-heap priority queue, which
is a heap-like structure.
4. Modify Dijkstras algorithm to find a path between u and v that minimizes
the maximum edges weight on the path (this type of problem is known as a
bottleneck problem).
5. Determine if graph G contains a uv path, all of whose edge weights are
equal.
6. Show (by an example) that Dijkstras algorithm (when you change MIN
to MAX and < to >) fails to compute longest paths.
7. An independent set in a graph is a subset of the vertices, no two of which
are adjacent. The maximum independent set is the largest such subset.
A Maximal Independent Set of a graph G = (V, E) is an independent set
M such that the addition to M of any vertex in V M creates a non-
independent set. Write an efficient algorithm to find a maximal independent
set in a graph and analyze its running time. Demonstrate a graph on which
your algorithms maximal independent set is not the maximum independent
set.
8. Give a polynomial time algorithm the following: Does G have a dom-
inating set of size at most 3? [A dominating set D is a subset of vertices
such that every vertex in G is either in D or has at least one neighbor in D].
9. Let G be a weighted graph. Find a second shortest path from vertex u
to vertex v (note that 2nd shortest path may possibly have same weight as
shortest path).
5.7. PROGRAMMING ASSIGNMENTS 109
DFS-VISIT(u)
1. color[u]=gray
2. d[u]:=time
3. time:=time+1
111
112 CHAPTER 6. ADVANCED ALGORITHMS AND PROBLEMS
u v w
T
1/8 2/7 9/12
F T C T
B
B
DFS starts from some arbitrary vertex (execution obviously will be dif-
ferent for different start vertices, though not in any significant way). All
vertices are initially white. A gray vertex is active it has been visited
but the recursion that started at it has not terminated. A vertex is black
when that recursion terminates. d[u] is the time a vertex is discovered (vis-
ited for the first time) and f[u] is the time when the recursion started at u
terminates. pred[u] gives the vertex from which we discovered u.
Consider the digraph in Figure 4 (DFS is more interesting in digraphs).
The edges in Figure 4 are labelled according to the type of edge they are
with respect to the DFS (note that if a different start vertex were chosen, or
the adjacency lists re-ordered, this labelling may be different). Tree edges
form the spanning forest of the graph (tree edges are formed when a vertex
is discovered); back edges (those edges connecting a vertex to an ancestor
of it, which has previously been discovered) in the DFS tree; forward edges
6.2. STRONGLY CONNECTED COMPONENTS 113
Exercise: How many strong components does the graph in Figure 1 have?
114 CHAPTER 6. ADVANCED ALGORITHMS AND PROBLEMS
You could find the strong components by running O(n) DFSs, one from
each vertex, in order to determine which vertices are mutually reachable (i.e.
have a path in each direction between one another). But this is fairly slow
and we can do better using only two DFSs, though in a seemingly strange
way!
First we need to define GT , the transpose of a digraph G. GT is obtained
from G by reversing the direction of all the edges of G. Note that this can
be done in linear time (if we use adjacency lists).
Exercise Prove that G and GT have the same strongly connected compo-
nents.
6.2. STRONGLY CONNECTED COMPONENTS 115
a b c d
e f h
g
Figure 6.2: Digraph for SCC
This algorithm is easily seen to take only O(|V | + |E|) time. An example
is in order. Consider the digraph in Figure 5, which has been labelled with
start and finish times from a DFS started at c.
GT is given in Figure 6 and the component graph in Figure 7. In the
component graph, each strong component of the original digraph is a single
vertex, with edges directed from component a to b if there is a vertex in
component a with an edge to a vertex in component b.
a b c d
e f h
g
Figure 6.3: GT of Figure 5
abe cd
fg h
Now lets see why it works. Note that if u has a path to v in G then v
has a path to u in GT . To prove this, simply look at a uv path in G: all
edges on the path are reversed in GT .
Theorem 11 If u and v are output in the same tree in step 3, then u and
v are in the same SCC of G.
Proof: By assumption, u and v are output as being in the same DFS tree
of GT . Let us assume v has a later finishing time than u at step 1. Thus v
will be processed prior to u at step 3. First assume that v is an ancestor of
u in the DFS tree of GT (that is, there is a path from v to u in GT which
implies there is a path from u to v in G).
Suppose by way of contradiction that there is no path from v to u in G.
There are two cases.
Case 1) Suppose DFS of G initiated at v prior to u (v was gray before
u was gray at step 1). Since there is no path from v to u in G, it must be
that u finished later than v, a contradiction.
Case 2) Suppose DFS of G initiated at u prior to v (u was gray before v
was gray at step 1). Since we know there is a path from u to v, we know u
had a later finishing time than v, again a contradiction.
Proof: Let Fi denote the number of edges bounding the ith face of (a planar
representation of) G. Obviously, Fi 3, since V 3. Thus
F
X
Fi 3F
i=1
Since each edge borders exactly two faces, we have
F
X
Fi = 2E
i=1
.
Thus 2E 3F , and using the previous theorem we get, 2E 3(E V +
2), and simplifying this gives 2E 3E 3v + 6, which implies 3V 6 E.
.
Also note that each subgraph of a planar graph is planar. We can now
present the algorithm (in the form of an inductive proof that planar graphs
can be five colored).
(recursively!). Now let us add v back to the graph (with its edges). If the
neighbors of v use only 4 different colors, we may safely color v with color 5.
So assume the neighbors of v (of which there are at most 5) are colored with
5 different colors. Assume the neighbors of v, v1 , v2 , . . . , v5 are arranged in
a clockwise order around v (see Figure 25). Define H(i.j), i, j = 1, . . . 5 to
be the subgraph induced in G v by the vertices of G v colored i and j.
There are two cases.
Case 2. Suppose v1 and v3 lie in the same (connected) component of H(1, 3).
Then there is a path from v1 to v3 in H(1, 3). Together with edges vv1 and
v3 v this path forms a cycle, C, in G. Because of the clockwise arrangement
of the neighbors of v, one of v2 , v4 must like inside this cycle and one must lie
outside the cycle, else G is not planar (see Figures 27 - 29 for an example):
thus v2 and v4 cannot lie in the same component of H(2, 4), since any path
in H(2, 4) from v2 to v4 would have to either pass through a vertex in C v
(which cannot be since the vertices of C v have colors 1 and 3) or cross an
edge on the cycle (which violates the planarity of G). Thus we may apply
Case 1 to H(2, 4).
Exercise: Analyze the running time of this 5-coloring algorithm.
v3
v2 v4
v1
v5
v3 H3(1, 3)
1
3
H1(1, 3)
3
1
v1
1
C
v2 3 v3
v4
v1
1
C
3 v3
v2 v4
v1
Clockwise order is violated
1
C
3 v3
v4
v2
v1
Note: The PCP Theorem [Arora, Sudan, et al., 1992] implies that no poly-
nomial time approximation algorithm for Dominating Set have have a per-
formance ratio that is less that a function of n (so, in particular, no constant
performance ratio is possible) unless P = N P .
D:=D + {v}
V:=V - {v} - {w | w adjacent to v}
E:= E - {(w, u) | w adjacent to v}
end;
In other words, the algorithm chooses at each step the vertex that has
the largest degree which thus dominates the most other vertices. This
vertex and its neighbors are then removed from the graph and the process
continues until no more vertices remain.
We can see that line 1 takes O(E) time; line 3 takes O(V ) time per
iteration; line 7 takes O(E) time in total (i.e. over the life of the algorithm);
and line 8 takes O(E) time in total. Summing the costs shows the running
time to be O(V 2 ) in the worst case.
128 CHAPTER 6. ADVANCED ALGORITHMS AND PROBLEMS
3. repeat
4. remove max element from heap
5. heapify;
6. for each v adjacent to max do
7. remove v from heap
8. remove v from G
9. for each w adjacent to v do
10. degree(w) = degree(w) - 1
11. update heap accordingly
12. remove edge (v, w)
13. end for
14. end for
15. remove max from G
until G is empty
Step 5 of course take O(log V ) time each time that step is performed.
6.4. ANALYSIS OF A GREEDY DOMINATING SET ALGORITHM 129
Step 7 requires O(V log V ) time in total and Step 11 can be seen to take
O(E log V ) time over the course of the algorithm.
type list_element is
count : integer :=0;
deleted : boolean:=false;
list : ptr to linked list
of vertices;
end;
adj_list : array[1..V]
of list_element;
Q : queue;
In terms of the individual steps, line 4 iterates O(V ) times; line 5 requires
O(log V ) time for each remove; lines 6-7 require O(V ) time in total; line 8
takes O(log V ) per remove; line 9 takes O(E) time in total and line 10 takes
O(1) time for each add. Likewise, lines 12-17 require O(V + E) time in
total. Line 18 iterates O(V ) times and line 19 iterates O(E) times in total.
Finally, line (*) requires O(log V ) time per decrease key.
It can be shown that this algorithm runs in O(V log V + E) time. The
idea is to use combinatorial analysis to show that the optimization trick
employed actually sped up the algorithm. In particular, we claim that only
O(V + E/ log V ) in total of then statements indicated by (*) above, are
satisfied. This implies only E/ log V decrease key operations are performed,
thereby giving the desired running time. The proof of this claim is non-trivial
and the interested reader is referred to [21]. Students with an interest in
132 CHAPTER 6. ADVANCED ALGORITHMS AND PROBLEMS
graph theory may wish to find their own proof of this claim.
In this final algorithm, steps 1-2 take O(V + E) time. The for loop at
line 6 iterates O(V ) time in total and the removed at line 7 takes O(1)
time per remove. The for loop at line 8 iterates O(E) times in total and
steps 9-10 require O(1) time per iteration. The while loop on line 13 takes
O(V ) time.
columns(A)
X
Cij = Aik Bkj
k=1
M1 * M2 * M3 * M4
[10 X 20] [20 X 50] [50 X 1] [1 X 100]
Lets see where these entries come from! Consider m1,4 (looking for a
product subsequence of matrices numbered 1 through 4). According to the
algorithm (the MIN step) this entry is the minimum of
Thus the optimal is 2200, which recursively, we can see is the order (M1
* M2 * M3) * M4 = (M1 * (M2 * M3)) * M4. Why is this so why did we
multiply (M1 * M2 * M3) in this order?
them in a way that best expresses their similarities. For example, if one
string is ATTACG and another is TTAG, we may select the alignment
AATTACG
--TTAG-
AATTACG
--TTA-G
Note that this alignment has inserted a space inside one of the strings,
which is allowable. We need to define the score of an aligment, which repre-
sent how good the aligment is. For example, we would like to express that
the following alignment is probably not very good:
AATTACG
TTA-G--
Let the strings be s and t. One possible scoring scheme is to give p(i, j)
points if s[i] is aligned with t[j] and the s[i] = t[j], g points if s[i] is aligned
with a space inserted into position t[j] and p(i, j) points if s[i] is aligned
with t[j] and the s[i] 6= t[j]. We do not allow two spaces to be aligned
together. For instance, if p(i, j) = 1, g = 2, then the three alignments above
have scores of -4, -2, and -10, so we would say the second alignment is the
best of these three. Our goal is to compute the best alignment. We do so
using dynamic programming.
The algorithm will compute optimal ways to align prefixes of s and t.
Our description of the algorithm follows that of [35]. To align s[1..i] with
t[1..j] there are three possibilities: align s[1..i] with t[1..j 1] and match a
space with t[j], align s[1..i 1] with t[1..j] and match a space with s[i], or
align s[1..i 1] with t[1..j 1] and (mis)match s[i] and t[j].
6.5. MORE DYNAMIC PROGRAMMING EXAMPLES 137
Input: s and t
m = |s|
n = |t|
for i = 0 to m
A[i, 0] = i * g
for j = 0 to n
A[0, j] = j * g
for i = 1 to m
for j = 1 to n
A[i, j] = MAX {A[i-1, j] + g,
A[i-1, j-1] + p(i, j) if s[i]=t[j],
A[i-1, j-1] - p(i, j) if s[i]!=t[j],
A[i, j-1] + g}
end for
end for
Output(A[m ,n])
138 CHAPTER 6. ADVANCED ALGORITHMS AND PROBLEMS
0 -2 -4 -6
-2 1 -1 -3
-4 -1 0 -2
-6 -3 -2 -1
-8 -5 -4 -1
So the optimal alignment of these two strings has score -1. Note that
this comes from optimally alignment AAA with AG (scorfe -2) and then
matching C with C (score +1)
Key Point: The number of bits needed to store an integer (what we usually
call the size of the input) is logarithmic in the magnitude of the integer
and we measure the running time of algorithms in terms of the input size.
So if the size of the input is log n and the computation time is n steps
then the running time is exponential in the size of the input.
0 1 2 3 4 5 6 7 8 9 10 11 12 13
T T F F F F F F F F F F F F
T T F F F F F F F T T F F F
T T F F F T T F F T T F F F
T T F T T T T F T T T F T T
T T F T T T T F T T T T T T
In this example, the sum of all the elements in 26 and the answer is
Yes (if we let A1 = {a1 , a2 , a4 }). To see how the table works, observe that
T [4, 13] = True because a4 = 3 and T [3, 10] = True.
Hint: Do you see why the running time is exponential in the input size?
Of course, in practice, we may be fortunate and all of the integers in the
input are small (say, 1000 or less) in which case this algorithm is fast and
practical. But if there are large integers in the input (say, on the order of
1010 ), the algorithm will be VERY slow.
140 CHAPTER 6. ADVANCED ALGORITHMS AND PROBLEMS
6.5.4 Knapsack
In the Knapsack problem, we are given a list of n items, each item has a
size and a profit. The goal is to find a subset of the items whose cumulative
size is at most C and which maximizes the sum of the profits of the items.
Here is a dynamic programming solution.
for i = 1 to n
for j = 1 to n
X[i, j] = infinity
end
end
for all j != p1
T[1, j] = False
end
X[1, p1] = s1
for i = 2 to n
for j = 1 to nP
if T[i-1, j] = TRUE then
T[i, j] = TRUE
X[i, j] = X[i-1, j]
6.5. MORE DYNAMIC PROGRAMMING EXAMPLES 141
end if
if X[i-1, j - pi] + si <= C then
T[i, j] = TRUE
X[i, j] = min{X[i, j], X[i-1, j-pi] + si
end if
end
end
3. let T[0, (v1 , v2 )] = 0 and T[0, (i, j)] = , for (i, j) 6= (v1 , v2 )
T[i, j] will be the minimum cost of serving the first i requests and
finishing (finishing the first i requests) in configuration j
4. for i:=1 to n
for j:=1 to V 2 do
T[i, j]:=min{T[i-1, k] + cost(k, j)} over all configurations k
where cost(k, j) is cost of moving from
configuration k to configuration j
That is, if k=(u, v) and j=(w, x) then
cost(k, j) = distance(u, w) + distance(v, x)
and distance is from the APSP matrix computed in step 1.
Furthermore, if j=(u, v) then at least one of u, v must equal ri else
T[i, j] = .
That is, we can safely ignore candidate configurations that do not
include the requested vertex.
end;
end;
The minimum cost to serve the sequence is then the minimum value on
row n. (If we actually want to know what that sequence is, we can store
some additional information in the table (i.e. where each optimal value in
6.5. MORE DYNAMIC PROGRAMMING EXAMPLES 143
each cell came from, that is the sequence of server movements, i.e. which
server served all the preceding requests).
A simple analysis gives a running time of step 4 is O(n V 2 V 2 ) which
is O(nV 4 ). Note that this algorithm works for the k-server problem in gen-
eral, though the running time is exponential in k. Faster algorithms exist
based on maximum flow techniques [10], for example Chrobak et al. give
one which runs in time O(nV 2 ) note that this is independent of k.
6.5.6 Trees
Suppose we want to find the maximum-sized independent set S in a tree T
(an independent set is a set of vertices such that no two of them are adja-
cent). One way to do it is as follows.
Start with S empty. Add all leaves to S (unless T is a tree with one edge,
in which case add just one of the leaves to S). Delete all vertices added to
S as well as all vertices adjacent to those added to S. The results graph is
a forest. Recurse on each tree in the forest.
Exercise: Prove the above algorithm is correct. Analyze its running time
(hint: it is polynomial time).
However, suppose we add weights to each vertex of T and wish to find
the independent set S such that the sum of the weights in S is as large as
possible. Then the above algorithm is useless. A kind of dynamic program-
ming algorithm can solve the problem. Working from the leaf level up on
larger and larger subtrees until we get to the root, we can solve this prob-
lem by storing some information about the optimal solutions in the subtrees
rooted at each vertex. The details are left as an important exercise.
6.7 Exercises
1. The following problem is from [8]:
Let L be a set of strings of characters and let Q be a program that runs
in polynomial time that decides whether or not a given string is in L or not.
Write a polynomial time algorithm (using dynamic programming) to decide
if a given string is in L , where L consists of strings that are formed by
concatenating one or more strings from L.
6.7. EXERCISES 145
Note: Be aware that the empty string, , may or may not be a member of
L.
b. Modify your solution to a) in the case that vertices are weighted and
we are searching for a minimum weight dominating set (the sum of all the
weights of vertices in the dominating set is minimum).
Complexity: An Introduction
7.1 Introduction
7.1.1 Easy Problems, Efficient Algorithms
We say an algorithm is efficient if it runs in polynomial time. Of course, if
a linear running-time algorithm for a problem is available, wed hardly call
an O(n40 ) running-time algorithm for the same problem efficient, except
in this technical sense. Weve considered some problems now for which we
found no efficient algorithms:
* partition
The reasons why it can prove difficult to find efficient algorithms for solving
particular problems, such as the above, will be a main topic for the rest of
the semester. Another main objective in the remainder of the course is to
learn to recognize intractable problems when we come across them.
We should be careful to distinguish algorithms from problems. A prob-
lem is easy if it can be solved by an efficient algorithms (more on algorithms
as solutions to problems below). Here is an easy problem:
Sorting. We know many efficient algorithms that solve this problem. But
consider the following algorithm for sorting:
147
148 CHAPTER 7. COMPLEXITY: AN INTRODUCTION
Stupid Sort
begin
input A
for i in 2..length of A loop
end loop;
end loop;
A(i) := alpha(i);
end loop;
end Stupid Sort;
In the program for the Internet Worm (1987) the programmer used a
sequential search to search a sorted array, rather than a binary search, thus
requiring so much computation time as to bring the Internet to a halt.
m1
X
d(c(1) , c(m) ) + d(c(i) , c(i+1) )
i=1
TSP asks us to find, for any given complete weighted graph, a tour of mini-
mum length in that graph (a tour is a cycle that visits every vertex exactly
once apart from the starting/end point). Intuitively, think of the set C as
a set of cities, with the function, d, giving the distance between any pair of
cities in C. What we want is to find a way to visit every city other than the
starting point exactly once, minimizing the distance weve travelled along
the way. The Figure illustrates a particular instance of TSP:
1
2
3
4 5 1
1
4
1
Figure 7.1: TSP Example
instance.
For the time being, we confine our attention to decision problems. This may
seem limiting, but actually any optimization problem can be turned into a
7.1. INTRODUCTION 151
Here, k is the dummy parameter. For any choice of k we have converted the
optimization problem into a decision problem.
The graph coloring problem affords another example of how an optimiza-
tion problem can be turned into a decision problem by employing a dummy
parameter in the statement of the problem:
It turns out that this question is easy if k = 2 (Its equivalent to the deci-
sion problem: Is G bipartite?, which we can answer in linear time using a
simple graph traversal, such as a modified depth-first search), and its easy
as well for k n. But for many choices of k strictly between 2 and n, the
problem is hard (we note that graph coloring remains easy solved in cases
such as k = n 1 or when k (G) 1, where (G) is the maximum ver-
tex degree in G). This illustrates the fact that a problem can be subdivided
into cases classes of instancessome of which are hard, while others are easy.
Exercise: Find a graph where the greedy coloring algorithm fails to use the
minimum number of colors. Find a graph where either greedy dominating
set algorithm fails to find a smallest dominating set.
Exercise: Prove that, for every graph G, there exists a permutation of the
vertex set of G, such that when the vertices are considered by the greedy
coloring algorithm in that order, an optimal coloring is produced. (Hint:
Consider an optimal coloring and order/permute the vertices from lowest
color to highest).
end;
Then if we could invoke a non-deterministic, omniscient guesser, we could
have the following polynomial-time algorithm to actually k-color a graph:
program color(G, k)
1. Guess a coloring, C;
2. Test if C is a k-coloring of G;
end;
If a k-coloring exists, our guesser would always guess one at step 1. and
the verifying at step 2. would be what takes polynomial time. If theres
no coloring, no guess would work. There are O(4n ) different colorings. You
could also employ 4n different machines, each checking its own coloring, to
check all the possible colorings.
The Partition Problem is another another example of a problem in the class
N P . For any particular partition of a set of integers S into two non-empty
subsets, it takes only linear time to determine whether the sum of the ele-
ments of one subset equals the sum of the elements of the other. However,
in such a case, where n is the size of S, there are 2n possible partitions of S.
P is a subset of N P . Any instance of a problem that can be solved in
polynomial can be verified in polynomial time. This suggests the following
diagram:
We note that there exist very hard problems beyond NP (e.g. PSPACE:
class of problems decidable in polynomial space, EXPTIME and EXPSPACE
154 CHAPTER 7. COMPLEXITY: AN INTRODUCTION
PSPACE
NP-complete
Harder Classes
P NP
Problems get more difficult the futher you get from the origin
Figure 7.2: Complexity Classes
7.4. NP-COMPLETENESS AND COMPLEXITY CLASSES 155
P = NP?
Instance of L1
x Yes
No
A Language: the set of instances having yes answers for some decision
problem .
a a 1 b
c d 2
1 2
1
b
HC Instance
c 1 d
TSP Instance
P is the base of this partial order, since for all L1, L2 which are
elements of P , it is easy to see that L1 and L2 are polynomially equivalent (a
reduction f between them can simply solve the initial problem in polynomial
time).
Also note that m is transitive. Therefore, if L1 m L2 and L2 m L3,
then L1 m L3. This is like a composition of two functions (and in fact the
f that reduces L1 to L3 is just the composition of the reduction that takes
L1 to L2 and the reduction that takes L2 to L3).
0 m
0 m SAT 0 N P.
M F
M F
NP-Complete
x x
x x
NP
x ? ? x
x ? x P x ? x
x ? x
?
?
x
x
x x
x
Figure 7.8: Are there problems in NP that are not in P and not in NP-
complete?
Partition Problem.
a b a b
c c d c
Graph Isomorphism seems very hard, but is not NP-complete unless the
polynomial time hierarchy collapses to a very low level (which is thought
highly unlikely [5]).
In conclusion, we also note that problems are very sensitive to parameters
and specific graphs. For instance, Does G have a vertex cover of size at
most k? is easy when k is small (i.e. a constant). In such cases we can
solve the problem in polynomial time by enumerating all possible solutions
of size k or less (but this is not feasible if k is, say, logn n ).
Hard instances
Instances
Instances with with NO
NO negative positive
literals literals
(a b c) (a b d) (b c d) (a b c)
Once can see that this instance is satisfiable: if a is True, b is True, and
c is False.
(a b c) (b c d) (a b e) (a b d)
Replace each clause C E which does not have 3 literals with a few
clauses with 3 literals.
Let C = (x1 , x2 . . . xk ) where k 4, or there are at least 4 literals in
C.
Now introduce new variables y1 , y2 , . . . yk3 to transform C in to group
of clauses with 3 literals as shown below
C = (x1 x2 x3 x4 x5 )
In this case we can set all the yi s in C such that all clauses in C are
satisfied. As an example let us suppose x3 = True. Then setting y1 = True
7.4. NP-COMPLETENESS AND COMPLEXITY CLASSES 165
C = (x1 x2 )
C = (x1 x2 z) (x1 x2 z)
C = (x1 )
C = (x1 y z) (x1 y z) (x1 y z) (x1 y z)
3 SAT-Instances
SAT Instances (E)
Satisfiable
Satisfiable
Hitting Set
INSTANCE: A collection C of subsets of a set S, positive integer k.
QUESTION: Does S contain a hitting set for C of size k or less.
A hitting set is a subset S of S , |S 0 | k such that S contains at least one
element from each subset in C.
For example:
S = { 1,2,3,4,5}
C = { {1,2,3}, {1,5}, {3,2,4} }
k = 2. Then S = {1,2} is a Hitting set for C.
7.4. NP-COMPLETENESS AND COMPLEXITY CLASSES 167
NP-C Q
problem
Figure 7.12:
Subgraph Isomorphism
INSTANCE: Two graphs G and H.
QUESTION: Does G contain a subgraph isomorphic to H?
Multiprocessor Scheduling
INSTANCE: Finite set A of jobs. Each job has a positive integer length l(a)
and there are a finite number, m, of independent identical processors and
some deadline d 1.
QUESTION: Can we schedule the jobs so that every job is finished by the
deadline.
Example:
A = {1,2,4,5,9,2} (these are the lengths of the jobs, l(a))
3 processors, d = 9.
Solution: Yes we can meet the deadline: Processor 1 runs jobs of length
1, 2, 2; processor 2 runs jobs of length 5 and 4; and processor 3 runs a job
of length 9.
Claim: If we restrict m = 2 and set d equal to the sum of the length of
all the jobs divided by 2, then this is the partition problem.
To see this in a more
P general setting, consider the following question.
aA l(a)
What happens if d < m ? Then it is not possible to meet the deadline.
Why is that true?
Vertex Cover
C = {c1 , c2 , c3 , . . . , cm }
a2[2]
a2[1]
Any Vertex Cover of this graph should have one of each of the following
pairs of vertices:
u1 and u1 ,
u2 and u2 ,
u3 and u3 ,
u4 and u4 .
Any Vertex Cover should have at least two of the vertices in each triangle.
That is, at least 2 of a1 [1], a2 [1], a3 [1], and at least 2 of a1 [2], a2 [2], a3 [2].
Therefore we need at least n vertices in the literals to take care of the Vertex
Cover ( to cover all literal edges ) and we need at least 2m vertices from the
triangles to take care of Vertex Cover (to cover all the triangle edges edges).
Of course, there are additional edges between the literals and triangles!
We now show F is satisfiable if and only if G has a Vertex Cover of size
less than or equal to k.
k. Since V 0 must contain at least one vertex from each edge corresponding
to a pair of literals and two vertices from each triangle corresponding to a
clause, we must have |V 0 | n + 2m = k.
Thus by setting ui = True , if ui belongs to V 0 and ui = False, if ui
belongs to V 0 we have a truth assignment.
Now we have to show that this truth assignment is satisfying truth as-
signment. This truth assignment satisfies F because one of the the inter-
component edges (between literals and triangles) is covered by a ui or a ui
for each clause, and two by ai [j] vertices. And this ui or ui vertex is a wit-
ness for the clause corresponding to ai [j]. that is every clause has a witness.
By this we mean what is shown in Figure 7.14.
The Vertex Cover V 0 includes one vertex for each pair of literal vertices (the
literal whose value is true ui if it is assigned true and ui if ui is assigned
false) and two vertices from each triangle (so as to cover the inter-component
edges (edges between literals and triangles) as shown in Figure 7.14). So
consider the vertex cover in our example consists of the shaded vertices (and
we see that u1 and u2 are assigned true).
. Since F is satisfiable , each clause has a witness. Construct a vertex
cover, V 0 , as before (if ui is assigned true then place ui in the cover, else
place ui in the cover. Finally, place two vertices from each triangle in the
cover in such a way as to cover the inter-component edges not covered by
the witness for the clause. Hence |V 0 | is less than or equal to k .
Clique
Let F = F1 F2 F3 . . . Fm ( m clauses ).
We associate four vertices with this clause Fi one for each literal in the
clause. Each one of these clauses will have a column of vertices having a
number of vertices equal to the number of literal in the clause.
To connect the vertices: vertices from same column are not connected (so
we get an m-partite graph). Vertices from different columns are connected
unless they represent the same variable in the negated form.
Here is an example.
F = (x y z) (x y z) (y z).
Since these vertices must have edge between them (thee are in different
columns and cannot represent a literal and its negation) and thus form a
Clique of size k.
Dominating Set
_
x
x
y _
y
_
z
_
z
z
G G
Figure 7.16:
from each (u, uv) type edge, hence D will cover all the (u, v) type edges
(since each (u, uv) type edge corresponds to a (u, v) type edge). Hence D is
a vertex cover of size at most k for G.
1) Hope we are given easy instances of the problem (such as 2-SAT, domi-
nating set in a tree, vertex cover in a grid, etc.). It is an active research area
to find the threshold at which particular problems become N P -complete.
For example, Vertex Cover remains N P -complete in graphs having maxi-
mum degree three. Certain classes of graphs such as trees, series-parallel,
bounded-treewidth, grids are so structured that many N P -complete prob-
lems become easy when restricted to those cases.
2) Run an exponential time algorithm and hope we stumble upon the correct
solution quickly.
Edge Coloring We wish to color the edges of a graph so that every pair of
edges incident upon a common vertex has a different color. To decide if k
colors suffice to edge color a graph is an N P -complete problem. However,
it is known by Vizings theorem that the number of colors needed is either
or + 1 where is the maximum degree of any vertex in the graph. This
theorem leads to a polynomial time algorithm to edge color a graph with at
most + 1 colors (though the edge coloring number of such a graph might
178 CHAPTER 7. COMPLEXITY: AN INTRODUCTION
Note that in the last two problems, it was easy to get an absolute per-
formance guarantee because we had a concrete upper bound on the value of
the optimal solution. This is not the case with the next problem.
Exercise: Construct such a proof for Clique showing that there cannot exist
a polynomial time algorithm with absolute performance guarantee. (Hint:
Make c + 1 copies of the graph; but disjoint copies wont suffice).
7.5. APPROXIMATION ALGORITHMS 179
R1
R2 R3 R4 R5 R6 R7 R8
x(v) 0 v V (7.4)
Now solve this linear program optimally in polynomial time and let x
denote the optimal solution (consisting of values assigned to each vertex).
We round x to an integer solution xi (since it makes no sense to have half
a vertex in a vertex cover!) as follows: for each v V set xi (v) = 1 if
x (v) 0.5 and xi (v) = 0 otherwise.
Observe that xi is indeed a vertex cover for G. Constraint (2) for each
edge (u, v) ensures that at least one of x (v) and x (v) is at least 0.5, hence at
least one of them will be rounded up and included in the cover. Furthermore,
the rounding has the property that xi (v) 2x (v) for each v V so we
obtain the fact that the weight of the vertex cover corresponding to xi is
at most twice the weight of the cover corresponding to x (which wasnt
really a cover, since it had fractional vertices!). But it is clearly the case
that any vertex cover must have weight at least that of x , so we have that
the weight of the approximate cover is at most twice that of the optimal.
Note how in both approximation algorithms for Vertex Cover, we found
some way of giving a lower bound of the value of the optimal solution,
whether os was the cardinality of a maximal matching or the value of an
optimal (though unreal) fractional cover. Then once we can give an upper
bound of the value of our approximate solution, we can derive a performance
ratio.
This technique of using linear programming as a basis for approxima-
tion algorithms is often a fruitful one. It was generalized by Goemans and
Williamson [19], who devised a method called semi-definite programming
that yields, for example, an algorithm for MAX-CUT with performance ra-
tio 1.14 and one with performance ratio 1.07 for MAX-2SAT.
7.5. APPROXIMATION ALGORITHMS 183
Euler Tours
Recall than an Euler tour of a graph is a closed walk that traverses each edge
of the graph exactly once (some vertices may be visited more than once, and
of course we finish the walk where we start). It is well-known that G has an
Euler Tour if and only if it is connected and each vertex has even degree.
If every vertex has even degree, we can find an Euler tour in polynomial
time as follows. Start at a vertex v. Go to a neighbor u of v (and delete
edge uv) provided deleting edge uv does not disconnect G into non-trivial
components. That is, if we delete an edge a leave a vertex isolated, that is ok,
since we have used up all that vertices edges. But if we disconnect the graph
so that more than two components are non-trivial (a trivial component is
an isolated vertex) we choose another neighbor of v with which to proceed.
It is easy to see that we can implement this algorithm in polynomial
time. An example can be seen in Figure 7.18.
To prove that it works, let G be a graph with every vertex having even
degree. Note that G cannot have any bridges (edges whose deletion discon-
nect the graph) since G has an Euler tour. That is, if there were a bride,
once crossed, there would be no way to get back where we started.
Suppose the algorithm fails to find an Euler tour in G. That is, at some
point the algorithm halts because the only edges incident to v are bridges
between nontrivial components in the current graph G0 = G minus the set
of edges that have been deleted thus far. Consider the edges deleted so
far. Let C be those edges deleted so far that form closed walks and cycles.
Let G00 = G C. Clearly G00 has every vertex with even degree, but is
has a bridge incident to v and thus cannot have an Euler tour. This is a
contradiction, proving our algorithm is correct.
184 CHAPTER 7. COMPLEXITY: AN INTRODUCTION
Let G = (V, E) be the weighted graph that is our instance of TSP; G satis-
fies the triangle inequality. The algorithm is as follows:
2 5
6
3
7
4
Eulerian graph
Solid Line is TSP tour. Dashed line is Euler Tour:
1, 2, 3, 4, 5, 1, 6, 7, 1
Figure 7.18: Approximating TSP
186 CHAPTER 7. COMPLEXITY: AN INTRODUCTION
1
We show that List Scheduling has a performance ratio of 2 m .
Assume that after all the jobs have been scheduled that machine M1 has
the highest load (= latest finishing time). Let L denote the total load on M1
and let Jj be the last job assigned to M1 . Note that every machine (after all
jobs have been scheduled) has load at least L pj , else Jj would not have
been assigned to M1 M1 had load exactly L pj when the Jj was assigned
to it. Hence, summing up all the job lengths, we see that
n
X
pi m(L pj ) + pj
i=1
m(L pj ) + pj
OP T (I)
m
and simplifying, we have that
pj
OP T (I) (L pj ) +
m
1
OP T (I) L (1 )pj
m
.
Now dividing both sides by OP T (I) and re-arranging shows
1
(1 m )pj L
1+
OP T (I) OP T (I)
and since OP T (I) pj (some machine must run that job!) we get
1 L
1+1
m OP T (I)
which means
1 L
2
m OP T (I)
188 CHAPTER 7. COMPLEXITY: AN INTRODUCTION
7.5.5 Bin-Packing
Bin Packing is a similar problem to scheduling. We have n items to pack
into as few unit sized bins as possible. Each item has a size, 0 < si 1.
The First Fit algorithm is just like List Scheduling: we consider the items
one by one, packing each into the lowest indexed bin into which it will fit.
We show that FirstP Fit has a performance ratio of 2. The optimal clearly
must
P use at least d si e bins. We will show that First Fit uses no more than
d2 si e bins.
First note that when First Fit is done, there is at most one bin that is
half-empty (or more than half-empty). If this were not the case, First First
would have combined those two bins into one.
If all thePbins are at least half full, then obviously First Fit has used
at most d2 si e bins. So suppose one bin, Bq , is less than half full, i.e.
Bq = 0.5 , > 0.
Let us assume without loss of generality that q is the highest numbered
bin. Then for all Bi 6= Bq , Bi > 0.5 + , else Bi + Bq 1 and First Fit
would have used only one bin. So if q 2, First First must be optimal and
our performance guarantee is achieved.
So assume q > 2. Then
7.5. APPROXIMATION ALGORITHMS 189
= 0.5q 2 + q
P which from our comments above about the Bi s is less than or equal to
d si e which we know is a lower bound on OP T (I). That is
= 0.5q 2 + q OP T (I)
P
We claim that F irstF it(I) = q 2OP T (I). First notice that 2d
P si e
d2 si e.
So we want to show that
where q is the number of bins used by First Fit. Where the right hand
side is twice a lower bound on the OPT(I). Simplifying, we want to show
that
q < dq 4 + 2qe
q < q 4 + 2q
0 < 2q 4
0 < q 2
TSP
Previously, we saw that TSP could be effectively approximated if the dis-
tances satisfied the triangle inequality. Now we show that the general TSP
problem is hard to approximate. Suppose we had an approximation algo-
rithm for TSP with performance ratio c (c is some constant). Let G be
an instance of the N P -complete Hamiltonian cycle problem with n vertices.
Construct an instance of TSP, G0 (a complete, weighted graph on n vertices)
by setting weight(u, v)=1 if (u, v) is an edge in G and weight(u, v)=cn oth-
erwise. Now we input this graph to our approximation algorithm for TSP.
If G has a Hamiltonian cycle, then G0 will have a TSP tour of weight n.
Otherwise, the shortest TSP tour in G0 must be of weight greater than cn
(since such a tour must contain at least three vertices and one edge of weight
cn). Thus, because of the performance ratio of our algorithm, if G has a
Hamiltonian cycle, we will find a TSP tour of weight exactly n (since we
are guaranteed to find a tour of weight at most c times the optimal). And
if G does not have a Hamiltonian cycle, we will find a TSP tour of weight
greater than cn. Thus we could decide the Hamiltonian Cycle problem in
polynomial time. So we see that TSP is in fact N P -hard to approximate.
as being partitioned into addresses of length log p(n) for the proof of length
p(n)) and y, a and b contain the same answer to the query. Since at most
log n random bits are generated, G has p(n) vertices. It is easy to see that
G has a clique of size 2r if F is in fact satisfiable, since the verifier must
declare the proof to be true no matter what its random string is. Thus for
each of the 2r choices of random string x there is a vertex corresponding to
this bit string and all 2r such vertices must be consistent in order to meet
the definition of a PCP. On the other hand, suppose F is not satisfiable and
we have a (unbeknownst to us) bogus proof of this. Then we claim G has
r
a clique of size at most 24 . Suppose there exists a clique V 0 in G of size
r
greater than or equal to 24 . Construct a proof for F as follows. For each
vertex v =< x, y > V 0 , assign to the bits in the proof at c |V 0 | addresses
given by x the bits in y (each x corresponds to c = O(1) distinct addresses).
Since all vertices in V 0 are consistent, this will assign each of these addresses
r
exactly one value. To each of the other 2r c 24 addresses, assign arbitrary
values. But now we have a proof for F which the verifier will accept with
r
probability at least 24 /2r 14 which is a contrary to the definition of a PCP.
If there were a polynomial-time approximation algorithm A for Clique
with a performance ratio of two, we could run A on G and determine with
certainty whether of not G has a clique of size 2r or not: since if G does, A
r
would return a clique of size at least 22 , which is only the case if G has a
clique of size at least 2r , by construction.
Thus it is N P -hard to approximate Clique to within a factor 2. This
result has been strengthened to there being no approximation algorithm for
1
Clique with performance ratio less than n 3 unless P = N P .
7.6 Exercises
On-line Algorithms
193
194 CHAPTER 8. ON-LINE ALGORITHMS
c times the cost incurred by any other algorithm B for for any request
sequence , even if B knows in advance. Hence we are comparing the cost
incurred by A with that of an optimal offline algorithm B and doing so over
all possible request sequences. Analysis of on-line algorithms is typically
done in an amortized fashion (costs are averaged over the entire request
sequence so that high costs for single requests may be offset by low costs for
other requests) and worst-case sequences are constructed using adversarial
arguments.
It is not difficult to see that the k-server problem is a generalization
of the paging problem (consider the k-server problem on an complete, un-
weighted graph: in this case the servers are marking the pages stored in
fast memory, whereas the vertex set if the universe of all possible pages).
It is not hard to prove that k is a lower bound on the competitive ratio
for the k-server problem, even in the case of complete, unweighted graphs
(though it is a bit more difficult to prove that this lower bound applies to
any metric space with at least k + 1 points). Many algorithms are know to
be 2-competitive in general (see for example [24]). In [22], the simple work-
function algorithm is shown to be 2k 1 competitive in general, and it is
conjectured that the work-function algorithm is in fact k-competitive, which
would be optimal, given the lower bound of k on the competitive ratio for
the k-server problem. Sleator and Tarjan [36] discuss the competitiveness
of some familiar algorithms for paging such as LRU. Load balancing in
parallel and distributed computing systems and other scheduling problems
are obvious application areas for on-line algorithms.
Another well-known on-line problem is the dictionary problem for data
structures in which a data structure maintains a set of keys and searches,
inserts, and deletes are performed. The goal is to minimize the time spent
traversing the data structure. The move-to-front-list is shown below to be
2-competitive for list (one-dimensional) data structures [36] and the splay
tree is known to be O(log n) competitive for binary trees (of course, a static
height-balanced binary tree is also O(log n) competitive, though not as ef-
fective for certain request sequences as a splay tree [6]).
In an amortized analysis, the time required to perform a sequence of
operations is averaged over all the operations. A total time to perform n
operations of T (n) is shownyielding an average of T (n)
n time per operation,
although some individual operations may require more or less than the av-
erage time. Amortized analysis differs from average-case analysis in that an
amortized bound is a guaranteed worst-case bound. In average-case anal-
ysis, some probability distribution of the inputs is typically assumed (for
example, we might assume that the searches are drawn uniformly and ran-
8.2. ON-LINE ALGORITHMS 195
overall goal of the move-to-front rule is to have the working set congregate
near the front of the list.
One important use of amortized analysis is called competitive analysis.
We shall examine how optimal this MTF strategy is. Competitive analysis
is used to analyze on-line algorithms: that is, algorithms that must make
decisions without knowledge of future requests.
Recall that we said an algorithm is c-competitive if CA () cCB ()+a
for some constant a for any and every algorithm B; where CA () is the cost of
our algorithm,A, on a sequence of requests and CB () is the cost incurred
by B on the same sequence of requests . By cost we may mean comparisons
made, in the case of list access strategies.
To avoid the arduous(!) task of comparing our algorithm with every
other one, we simply compare it with the optimal one! Of course, since we
dont know the optimal one, we loosen the rules and compare our algorithm
with the optimal algorithm that knows the future (i.e. knows in advance)
since the optimal on-line algorithm is certainly no better than the optimal
off-line algorithm. The optimal offline algorithm is said to be controlled by
an adversary, who is allowed to construct . The adversary constructs
in order to maximize the ratio of CA to CB . We may think of the adversary
as knowing how algorithm A will behave in the future and we will refer to
the optimal, offline algorithm B as the adversarys algorithm.
We now prove that MTF is 2-competitive, that is, MTF makes no more
than twice as many comparisons as any omnipotent algorithm. This result
is due to Sleator and Tarjan [36].
We use the concept of a potential function. Recall from physics that an
object hanging above the ground has potential energy. A potential function
captures the goodness of the configuration of our algorithm (in this case, a
low potential value is good from our algorithms perspective and conversely,
a large potential value is good from the adversarys point of view). For
instance, we may be willing to pay a lot for one move now if the result is
an improved potential (which will result in future moves costing less). By
configuration, we mean the order of the keys in our list.
In general, a potential function is a non-negative function used to cap-
ture the goodness of an online algorithms configuration relative to the ad-
versarys.
Specifically, a dictionary operation takes time ti for the ith operation and
changes the potential from to 0 , then the sum ti + 0 is the amortized
cost of the ith operation. Thus a large ti (high actual cost for operation)
is balanced by a large decrease in potential (good for us!!), meaning we
will spend less in the future. Thus we are reducing the cost we just paid,
8.2. ON-LINE ALGORITHMS 197
ti , to ai , because we know that in the future we will spend less for some
operation(s). Likewise, we will pay an inexpensive actual cost with some
extra cost in order to pay for future expensive operations. This is what we
mean by amortize.
Then for all operations in (which is possibly an infinite sequence) we
have
X X
ti = 0 f + ai
where 0 is the initial value of the potential function (often taken to be
zero in many applications), f is the final value of the potential function
and ai is the amortized cost of the ith operation. Note that the terms have
dropped out of the summation because they form a telescoping summation,
hence only the first and last terms have cancelled out!
Re-arranging terms, we have that
ai = ti + 0
which we may write as
ai = ti + ()
where () means the change in potential during the ith operation.
To show the MTF list is 2-competitive we define the potential to be the
number of inversions in the MTF list as compared to the adversarys list.
An inversion is any pair of keys i, j such that i occurs before j in one list
and after j in the other list. Thus if the MTF list were the same order as the
optimal list, there will be no inversions note that this is the case initially
since both A and B start with their lists in the same order. So the number
of inversions measures how far our list is from the optimal list. We note the
in general, potential functions must be non-negative functions, and ours is
in this case as well.
Now consider an access of key i on the ith operation. Let k be is position
in MTFs list and let xi be the number of items that precede i in MTFs list
but follow i in the adversarys list.
Then the number of (common) keys preceding i in BOTH lists (each list)
is k 1 xi .
Moving i to the front of the MTF list creates k 1 xi additional
inversions and destroys xi inversions (since i now precedes these xi values).
Therefore the amortized time for the ith operation, ai is
ai = ti + 0 =
k + ()
Since xi inversions are destroyed (these items now follow i in both lists,
after i is moved to the front) and (k 1 xi ) new inversions created, we
have
k + (k 1 xi ) xi = 2(k xi ) 1.
An analog to the MTF list for binary search trees is thesplay tree, in-
troduced by Sleator and Tarjan in [37]. An accessed key in a splay tree is
rotated to the root position. A splay tree wit n nodes maintains O(log n)
worst-case access time for any key, but has the advantage of moving re-
cently accessed keys nearer the root. The dynamic optimality conjecture
claims that splay trees are O(1) competitive, but this has yet to be proven.
A different binary tree was shown to be O(log log n) competitive in [12].
8.2. ON-LINE ALGORITHMS 199
Recall the paging problem from operating systems: n pages of fast memory
are available to a computer system, e.g. cache memory. Memory accesses
to these memory locations are considerably faster than accesses to RAM or
disk, hence we would like to store those pages that we will be accessing often
in the near future in this fast memory (a single page may store a few bytes or
a few thousand bytes, depending on the system and particular type of fast
access memory we are dealing with). The paging problem then is to decide
which page to eject from the n fast pages when a page is requested that is
not currently stored in the fast page set. As a rule, we must always store
the most recently accessed page amongst the n fast pages. The objective is
to minimize the number of page faults, i.e., the number of requests which
require us to retrieve a page from slow memory and eject a page from fast
memory. Having few page faults then equates with fast performance of our
computer system.
Given that we do not know the future sequence of page requests, this
is an on-line problem. Several well-known strategies exist for the paging
problem:
(1) LRU: eject the page, from amongst the n pages in fast memory, that has
not requested for the longest period of time (Least Recently Used).
(2) FIFO: eject the page that was brought into fast memory the longest time
ago (First-in First-out).
(3) LFU: eject the page the page that has been used least frequently from
among all the pages in fast memory.
Proof: Si ends when LRU faults n times during Si . Consider a page q that is
in LRUs fast memory at the beginning of Si or that is brought into LRUs
fast memory during Si . It may be that q is evicted at some point during
Si . However, we claim that q cannot be evicted twice during Si . If true,
then it is easy to see that the Lemma must also be true (think about the
extreme situation when none of the pages that are in LRUs fast memory
at the beginning of Si are accessed during Si . That is, each request during
Si causes LRU to fault). To see the claim is true, suppose to the contrary
that a page q is evicted twice during Si . That means q was evicted, then
brought into main memory later in Si when q was again requested. But
after this request, q is inserted at the front of the move-to-front list we use
to implement LRU. Page q will be evicted a second time only when it is
at the end of the move-to-front list. A page can only move one position
towards the end of the list per request during Si . And a page moves one
position only if a page x that follows it is requested (in which case x will
then precede q in the list) or a page from external memory is brought into
fast memory. Hence n pages other than q must be accessed before q works
its way to the end of the list. The Lemma then follows.
Thus we know that during Si , n pages other than p are accessed. Now
consider MIN. MIN has n pages of fast memory, thus cannot keep both p and
n pages other than p in its fast memory at one time during Si . Therefore,
MIN makes at least one page faulty during Si .
Since LRU makes n page faults during each Si and MIN makes at least
one page fault during each Si , the ratio of page faults made by LRU during
S to the number of page faults made by MIN over S is at most n.
We now show that no on-line algorithm can have competitive ratio less
than n. We define a sequence S of page requests in which on-line algorithm
8.2. ON-LINE ALGORITHMS 201
A makes n page faults and MIN makes only 1 fault, and this sequence can
be repeated indefinitely. Assume that A and MIN initially have the same
set of n pages.
(1) The first request is to a page in neither A nor MINs fast memory, hence
each fault once.
(2) Let X be the set of pages either in MINs fast memory initially or brought
in on the first request, so |X| = n + 1.
Hence during this sequence, A faults n times and MIN faults only once.
At the end of this sequence, both A and MIN have the same set of n pages
in fast memory and thus the process can be repeated.
Example. Suppose n = 3 and let A and MIN both hold pages 1, 2, 3
initially. On request for page 4, supposed A ejects 3, in which case MIN
chooses to eject 1. The next request is for 3, and A ejects 2. The next
request is for 2 and A ejects 1. At this point both A and MIN hold pages
2, 3, 4 and A has made 3 faults versus 1 for MIN.
Thus LRU is n-competitive and an optimal on-line paging algorithm.
It turns out that FIFO is also n-competitive (which can be proved using a
similar argument as above), though in practice, LRU performs much better
that FIFO.
Note that this lower bound also gives a lower bound of k on the k-server
problem (since the paging problem is simply the special case of the k-server
problem when it is played on a complete graph: the vertices are pages and
the servers locations represent pages that are in fast memory).
k-Server Problem
Recall that we gave a dynamic programming algorithm previously to solve
the offline 2-server problem. The k-server problem is as follows:
(1) Show that when the adversary moves, that does not increase by
more than c times the cost incurred by the adversary.
(2) Show that when the on-line algorithm moves, that decreases by at
least the cost incurred by the online algorithm.
EXERCISE: Show that if the on-line algorithm always serves the request
with the closest server, then the algorithm is not c-competitive, for any con-
stant c.
a are not). If y serves the request, then Mmin becomes zero, a reduction of
one. Thus the expected change in potential is 3((1/30)+(2/31)) = 2.
and
= s2 s1
where we re-label the servers as necessary so that s1 s2 and x1 x2 .
Assume that initially s1 = x1 and s2 = x2 . Note that when the adver-
sary moves, only can change, and if the adversary incurs a cost of d, then
8.2. ON-LINE ALGORITHMS 205
finding efficient priority queues for certain applications, such as for Prims
MST algorithm and for Dijkstras algorithm. Fibonacci heaps, introduced
in [16] are among those data structures that theoretically provide the best
performance for these algorithms.
Lots of of network problems (e.g., routing packets in networks, file and
process migration) and numerous scheduling problems are important on-line
problems.
8.4 Exercises
Mathematical Preliminaries
9.1 Functions
A function f is a mapping from A (domain) to B (co-domain) such that
f (a) = b, that is, each element in the domain is mapped to exactly one
element of the co-domain.
207
208 CHAPTER 9. MATHEMATICAL PRELIMINARIES
logc a
logc (ab) = logc a + logc b, logb an = n logb a, logb a = logc b ,
logb (1/a) = logb a, logb a = log1 b .
a
Furthermore, log n (the iterated log) is used to denote the smallest value
i such that log log log log n 1, where the log operation is done i times.
Examples:
log 64 = 6 since 2 2 2 2 2 2 = 64
Other definitions:
A(1, j) = 2j , for j 1
n F(n)
0 1
1 2
2 4
3 16
4 65,536
5 2^{65536}
Note that
n
X n n
log i log
2 2
i=n/2
n n n
= log n log 2 = log n n = (n log n)
2 2 2
Certain series, called telescoping series, are particularly easy to manip-
ulate. In general, these are of the form
n
X
(ak ak1 ) = an a0
k=1
As an example:
n1 n1
X 1 X 1 1 1
= ( )=1
k(k + 1) k k+1 n
k=1 k=1
To see why, note that the series is equal to (1 - 1/2) + (1/2 - 1/3) +
(1/3 - 1/4) ... (1/(n-1) - 1/n), which is the mirror image of the general form
shown above.
Splitting the terms. Sometimes we can split a sum into two or more
parts that are simpler to analyze.
Consider
X
k 2 /2k
k=0
2
X k2 X k2
+
2k 2k
k=0 k=3
2
X k2 X 9 8 k
+ ( )
2k 8 9
k=0 k=3
X 9 8 k
O(1) + ( ) =
8 9
k=0
9X 8 k
O(1) + ( )
8 9
k=0
Since the second term is a geometric series with x < 1, it too is O(1).
Hence the sum is O(1).
(k + 1)/3k+1 1k+1 2
k
=
k/3 3 k 3
Hence
X X k X1 2 k 1X 2 k
( ) ( )
3k 3 3 3 3
k=1 k=1 k=1 k=0
The latter sum is geometric, and has the value 1/3 1/(1 2/3) which
is equal to 1.
If we split the sum into log n pieces, we can easily bound it by O(log n).
This is done by using a double summation.
n dlog ne 2 1 i
X 1 X X 1
n 2i + j
k=1 i=0 j=0
dlog ne 2i 1
dlog ne
X X 1 X
2 = O(log n)
2i
i=0 j=0 i=0
9.3. INDUCTION AND PROOFS 213
The first term in the double summation breaks in into log n pieces. The
second term is the 1/k where k = 2i + j. We can then sweep the j term
under the rug. What remains is simply 1/2i c 2i , for c < 1, which is no
more than 2. That is, each term in the summation is at most 2. Hence the
O(log n) bound.
Example: Assertion:
n
X n(n + 1)
i=
2
i=1
Base case. n = 1. The sum has only one term, 1, and thus the value of
the summation is 1. And we have that 1 (1 + 1)/2 = 1 2/2 = 1.
214 CHAPTER 9. MATHEMATICAL PRELIMINARIES
Inductive step: Show that it holds for n + 1. That is, we want to show
(n+1)(n+2)
that n+1
P
i=1 i = 2 .
The idea is to find some way to use the inductive hypothesis to our
advantage. In this case, we do this by taking the sum above and separating
out the last term:
n+1
X Xn
i=( i) + (n + 1)
i=1 i=1
which is equal to
Show that
n
X
3k = O(3n ) c 3n
k=0
n+1 n
X X 1 1
3k = ( 3k ) + 3n+1 c3n + 3n+1 = ( + )c3n+1 c3n+1
3 c
k=0 k=0
This holds as long as 1/3 + 1/c 1 (or c 3/2, which is ok given our
base case), otherwise we are changing our constant in midstream, which is
not what a constant does!!
n2 = (2k + 1)2
= 4k 2 + 4k + 1
= 2(2k 2 + 2k) + 1, which is clearly odd, contradicting our assumption that
n is odd. Hence we conclude that n is even.
9.4 Graphs
Let G = (V, E) be a graph with vertex set V and edge set E. Usually, we
let |V | = n, |E| = m.
An outerplanar graph is one that can be drawn in the plane with all
vertices bordering the exterior face.
A (vertex) coloring of a graph is an assignment of integers (colors) to
the vertices so that any two adjacent vertices have different colors.
Theorem. Any outerplanar graph can be colored using at most three colors.
tive hypothesis, each of these G0i are 3-colorable, and we may assume that
v receives color 1 in each of these (else the colorings may be easily modified
so this is the case). These colorings then induce a coloring of G.
9.5 Exercises
Data structures
10.1 Lists
219
220 CHAPTER 10. DATA STRUCTURES
Bibliography
[3] A. Aho, J. Hopcroft, and J. Ullman (1974), The Design and Analysis
of Computer Algorithms, Addison-Wesley, Reading, Mass.
221
222 BIBLIOGRAPHY
[12] Erik Demaine, Dion Harmon, John Iacono, and Mihai Patrascu (2007),
Dynamic optimalityalmost, SIAM J. Comput., vol. 37, pp. 240251.
[14] R. Dutton (1993), Weak-heap sort, BIT, vol. 33, pp. 372-381
[16] M. Fredman and R. Tarjan (1987), Fibonacci Heaps and their uses
in Improved Network Optimization, Journal of the ACM vol. 34, pp.
596-615
[31] Shih Wei-Kuan and Hsu Wen-Lian (1999), A new planarity test, The-
oretical Computer Science vol. 223, no. 1-2, pp. 179-191
[39] R. Tarjan (1975), Efficiency of a good but not Linear Set Union Algo-
rithm, J. ACM, vol. 22, pp. 215-225