data structures brett bernstein 1 lecture 1: introduction ...brettb/dssum2016/allnotes.pdf · data...

207

Upload: others

Post on 21-Sep-2020

3 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

Data Structures � Brett Bernstein

1 Lecture 1: Introduction and a Review/Enrichment of

CS 101

1.1 Introduction

1.1.1 Class Information

The title of this course is Data Structures, but in reality the name only tells half of the story.Part of the course covers the standard data structures theory along with an introduction toalgorithms:

1. Basic Runtime and Space Asymptotic Analysis

2. Stacks, Queues and Deques

3. Linked Lists

4. Recursion

5. Trees, Tree Traversals, Balanced Trees

6. Binary Heaps and Priority Queues

7. Sorting and Searching Algorithms

8. Maps (hash and tree-based) and Sets

9. Graphs (time permitting)

10. Applying the above structures to solve problems

Although the above topics are important, data structures is also largely a Java programmingcourse. Even more importantly, it is the second Java programming course in the major. Thispositions you (the student) to really improve your coding skills. Throughout the semesteryou will spend a lot of time programming. As such, part of the lectures will be devoted toimproving your knowledge of the Java programming language. In class we will �rst quicklyreview and enrich what was covered last semester (in your Introduction to CS in Java course).For the remainder of the semester we will learn how to properly design, implement, and testour implementations in Java, and cover whatever language features are required. Theseinclude:

1. Using object-oriented techniques to design reusable data structures

2. Generics, Iterators, Exceptions

1

Page 2: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

3. Using the Java API data structure implementations

4. Writing tests

We probably won't have time to cover the functional programming features added in Java8.

1.1.2 Class Logistics

Most of the grade will come from the homeworks and labs. There will be 1-2 written examsthat will cover the material from class, and I will provide materials to help you prepare.Most if not all of the homeworks will be programming assignments. My lecture style is veryinteractive with a lot of student collaboration. In class computer usage will be restricted tothe labs, which will take 45-75 minutes of each week. The course will be based primarilyon the lecture notes. The listed textbook can be used as a learning aid to supplement thelectures.

1.2 Review/Enrichment of CS 101

1.2.1 Initial Exercises

1. FizzBuzz, a famous interview question: Write a Java program that prints out thenumbers from 1 to 100 but, for multiples of 3 instead print Fizz, for multiples of 5instead print Buzz, and for multiples of 3 and 5 instead print FizzBuzz.

2. Write a function factorial that takes an int as argument and outputs an int that is thefactorial of the input. As a reminder, the factorial of 5 is 5! = 5 · 4 · 3 · 2 · 1 and ingeneral

n! = n · (n− 1) · (n− 2) · · · 2 · 1.

3. Write a function isPermutation that takes an array of ints and returns a boolean. Let ndenote the length of the array. Your function should return true if each number between0 and n−1, inclusive, occurs exactly once in the array. Otherwise return false. [In otherwords, we are checking if the array contains a reordering of the numbers 0, 1, . . . , n−1.]For example,

{0,1,2,4,3} => True{2,3,1,0} => True{0,1,2,4} => False{−9} => False{1,1,1,1} => False

2

Page 3: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

1.2.2 Solutions

1. This exercise let's us warm up our Java skills. Here are two correct implementations:

FizzBuzz.java

public class FizzBuzz{

public static void main(String[] args){

for (int i = 1; i <= 100; ++i){

if (i % 15 == 0) System.out.println("FizzBuzz");else if (i % 3 == 0) System.out.println("Fizz");else if (i % 5 == 0) System.out.println("Buzz");else System.out.println(i);

}}

}

FizzBuzz2.java

public class FizzBuzz2{

public static void main(String[] args){

for (int i = 1; i <= 100; ++i){

if (i % 3 == 0) System.out.print("Fizz");if (i % 5 == 0) System.out.print("Buzz");if (i % 3 != 0 && i % 5 != 0) System.out.print(i);System.out.println();

}}

}

Here is a typical incorrect solution:

FizzBuzzWrong.java

public class FizzBuzzWrong{

public static void main(String[] args){

for (int i = 1; i <= 100; ++i){

if (i % 3 == 0) System.out.println("Fizz");else if (i % 5 == 0) System.out.println("Buzz");else if (i % 15 == 0) System.out.println("FizzBuzz"); //Never happens

3

Page 4: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

else System.out.println(i);}

}}

Note that FizzBuzz is never printed in this one.

2. In this problem we remember how to write functions in Java, and think about thelimits of our data types. Below is a correct implementation (we included a main fortesting):

Factorial.java

public class Factorial{

public static int factorial(int n){

int v = 1;for (int i = 2; i <= n; ++i) v *= i;return v;

}public static void main(String[] args){

System.out.printf("factorial(%d) = %d\n",3,factorial(3));System.out.printf("factorial(%d) = %d\n",10,factorial(10));System.out.printf("factorial(%d) = %d\n",12,factorial(12));System.out.printf("factorial(%d) = %d\n",13,factorial(13));System.out.printf("factorial(%d) = %d\n",50,factorial(50));

}}

Here is the output:

factorial(3) = 6factorial(10) = 3628800factorial(12) = 479001600factorial(13) = 1932053504factorial(50) = 0

It is clear from the above output that our function doesn't work for an input of 50. Italso doesn't work for 13. To see why, we must understand what an int is. The primitiveintegral data types in Java are byte, char, short, int, and long. The most used of theseis int, and it uses 32 bits (or 4 bytes). Each bit takes the value 0 or 1, so there are 232

possible di�erent values that an int can take. By standard convention, an int uses halfof the values for non-negative numbers (0, . . . , 231 − 1), and half for negative numbers(−231, . . . ,−1). If we recall that 210 = 1024 ≈ 1000 we see that

231 = 2 · 210 · 210 · 210 ≈ 2 · 1000 · 1000 · 1000 = 2, 000, 000, 000.

4

Page 5: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

Thus 231 is a bit larger than 2 billion. As a rule of thumb, an integer can hold all9-digit numbers. The value 13! is the �rst factorial that is larger than 231 and thus weget an incorrect value. To get the correct value of 13! = 6227020800 we could use thelong data type which uses 64-bits and goes up to 263 − 1. The rule of thumb is thata long can represent any 18-digit number. As an aside, let's try to understand whywe get zero for factorial(50). The answer is that when we add or multiply non-negativeintegers, the resulting int will always be the lowest order 32-bits of the result. Since232 divides 50! the lowest order 32-bits are all zero.

3. Our �nal question on isPermutation will allow us to think more about functions, andwill get us started thinking about runtime analysis. Below we have 3 correct solutions:

public class IsPermutation{

public static boolean isPermutation(int[] arr){

int n = arr.length;int[] counts = new int[n];for (int i = 0; i < n; ++i){

int v = arr[i];if (v < 0 || v > n−1) return false;counts[v]++;

}for (int i = 0; i < n; ++i){

if (counts[i] != 1) return false;}return true;

}}

A few comments. First note that both for loops use the variable i. This is legal sincethe scope of each variable is their respective for loops. In other words, once each forloop ends the variable i ceases to exist. Here is another solution that uses sorting:

import java.util.Arrays;public class IsPermutation2{

public static boolean isPermutation(int[] arr){

//arr = Arrays.copyOf(arr, arr.length);Arrays.sort(arr);for (int i = 0; i < arr.length; ++i)

if (arr[i] != i) return false;return true;

5

Page 6: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

}}

One thing to notice about this implementation is that input array is modi�ed in thefunction which may be undesirable. We can avoid this by instead doing

arr = Arrays.copyOf(arr,arr.length);Arrays.sort(arr);//rest of code

Make sure you understand why this newly modi�ed code is correct. Finally, let's lookat a third implementation:

public class IsPermutation3{

public static int countOcc(int[] arr, int val){

int count = 0;for (int i = 0; i < arr.length; ++i){

if (arr[i] == val) count++;}return count;

}

public static boolean isPermutation(int[] arr){

for (int i = 0; i < arr.length; ++i)if (countOcc(arr,i) != 1) return false;

return true;}

public static void main(String[] args){

int[] arr = {5,4,3,2,1,0};System.out.println(isPermutation(arr));arr[0] = 1;System.out.println(isPermutation(arr));arr[0] = 9;System.out.println(isPermutation(arr));

}}

Here we included main to illustrate the following point. The function main has avariable named arr. The functions countOcc and isPermutation both have parameters

6

Page 7: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

called arr. How can all of these coexist without con�icting? To understand how thisworks we will review the notion of scope (also called name visibility).

Each variable has a name that is used to access it. For instance, when you write intv = 4; you are creating a variable named v and giving it the value 4. The scope of avariable is where that name can be used once it is declared. All of the local variables(variables declared in functions) and parameters of a function can only be accessed inthe function they were declared in. Thus there is no name con�ict between the variousvariables named arr in IsPermutation3 since they live in di�erent scopes.

1.2.3 Runtime and Space Utilization

There are many criteria we can use to evaluate a piece of code. These include:

1. Correctness

2. Memory Usage

3. Running Time

4. Reusability/Fragility: measure of how hard it is to add/change the functionality

5. Quality of Tests

For now we will focus on memory usage and running time. Memory usage describes the totalamount of memory used by all of our variables, and all of the objects we refer to. In otherwords, all of the space we use to store our values. Running time refers to the number ofoperations/instructions we perform when solving a particular task. In both cases, a preciseanalysis can be very di�cult to perform. For this reason we will use a rougher measure thatignores constant factors. Instead of going into an abstract de�nition, let's try to evaluateour 3 implementations of isPermutation above.

1. We say the �rst version of isPermutation requires Θ(n) steps. The Θ(n) means thatwe are ignoring constants, and only care about how the number of steps grows withn, the size of the input. (This is a form of asymptotic analysis.) If we were beingmore precise we could look at each loop and ask how many operations it performs periteration. But even this has its limitations, since each instruction may turn into manymachine instructions once the program is compiled. Modern compilers are incrediblysophisticated, so trying to �gure out what they will output is not always feasible.

You could also argue that sometimes isPermutation takes far fewer than Θ(n) steps,like on the input {−9,1,2,3,4,...} since we immediately return false. This brings usto another convention. By default we will focus on worst-case runtime and spaceutilization analyses. By this we mean that for each n we look at the largest runtimeand largest space utilization over any possible length n input.

Analogously we say that isPermutation uses Θ(n) space. This is determined by lookingat all of the memory we allocate within our function. In addition to a few integervariables we allocate an array counts of size n that represents our main memory usage.

7

Page 8: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

2. In the second implementation we use only a constant amount of space (i.e., it doesn'tgrow with n) if we don't copy the input array. We write Θ(1) to denote a constantthat doesn't depend on n. If we do then we use Θ(n) space. The runtime has twoparts: the time required to sort, and the time required to loop over the array. If wealso copy the input, that is an additional time consideration. Suppose we assume the(quicksort in this case) sort requires (worst-case) Θ(n2) steps. Then the total runtimeis Θ(n2) + Θ(n). We simplify this to Θ(n2) since we only care about the term thatgrows quickest. We will say more about this in a moment.

3. In the third implementation we use Θ(1) space and have a runtime of Θ(n2). This isbecause we call countOcc n times, and each call to countOcc requires Θ(n) operations.

There is a bit of necessary intuition that goes along with the analysis we performed above.Firstly, logarithms grow slowly. We use lg n to denote the base-2 logarithm of n. In otherwords, lg n is the value v such that 2v = n. If n = 1024 then lg n = 10. If n is a billion thenlg n is roughly 30. Secondly, powers (like n1/2, n, n2, n3) grow much faster than logarithms.Furthermore, if you increase the power by 1 (going from n to n2 or n2 to n3) it greatlyincreases the growth. Here is a table that illustrates these points:

n lg n n n lg n n2 n3

2 1 2 2 4 810 3.32 10 33.2 100 1000

1000 9.96578 1000 9965.78 106 109

106 19.9315 106 19.9 · 106 1012 1018

On large inputs, having a runtime of Θ(n2) or Θ(n3) instead of Θ(n log n) can make theprogram take an unreasonable amount of time. Another way to look at these growth functionsis by changing the input length. Suppose we have a program with Θ(n2) runtime that takesroughly 10 seconds to process our input. How long do we expect it to take if we quadruplethe input length? Roughly 160 seconds, or 16 times longer. If we had the same setup buthad a Θ(n) runtime, the quadrupled input would only require 4 times longer.

You may have heard of �Big-Oh� notation before and noticed its similarity with the Θnotation we use here. They are actually slightly di�erent. When you say the runtime isΘ(n2), you are making the statement that it grows like n2 up to constants. When you makethe statement �the runtime is O(n2)� you are saying it grows at most like n2 up to constants.That is, Big-Oh denotes an upper bound while the Big-Theta denotes what is called a tightbound (i.e., is more precise). As an example, suppose a function requires n steps. We cansay it has a Θ(n) runtime. We can also say it has a O(n) or O(n2) or O(n1000) runtime, sinceall of these are upper bounds.

Technical aside (can be ignored) : We will assume a simpli�ed model where our indicesand references take constant size. This isn't technically true, since for an enormous array ofsize n (way larger than can �t on the machine say), simply holding an index to a positionrequires lg n space. Alternatively, you can think of our space as omitting an implicit lg nfactor when dealing with indices/references.

8

Page 9: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

1.2.4 Runtime, Space, and Procedural Design Exercises

1. What is the runtime and space utilization of the following function in terms of theinput value n? [Don't try to determine what the program is doing.]

public static int sumfunc(int n){int sum = 0;for (int i = 1; i <= n; i += 2) sum += i;return sum;

}

2. What is the runtime and space utilization of the following function in terms of theinput value n? [Don't try to determine what the program is doing.]

public static int func(int n){int acc = 0;for (int i = 1; i < n; i *= 2) acc += i*i;return acc;

}

3. What is the runtime and space utilization of the following function in terms of thelength of the input (which we denote as n)? [Don't try to determine what the programis doing.]

1 public static int[] myFunction(int[] arr)2 {3 int n = arr.length;4 int[] tmp = new int[n];5 for (int i = 0; i < n; ++i) tmp[i] = 2*arr[i];6 int[] tmp2 = tmp;7 int[] sums = new int[n];8 for (int i = 0; i < n; ++i)9 {10 for (int j = 1; j < n; ++j) sums[j] += sums[j−1]+tmp[j];11 }12 return sums;13 }14

4. (??) Given the following code determine the value of f(5). What are the runtime andspace utilization of f in terms of n, the input parameter?

1 public static int f(int n)

9

Page 10: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

2 {3 if (n == 0) return 0;4 int val = f(n−1);5 return val + n;6 }7

1.2.5 Solutions

1. The runtime is Θ(n) and the space usage is Θ(1).

2. The runtime is Θ(lg n) and the space usage is Θ(1).

3. The runtime is Θ(n2) and the memory usage is Θ(n). The runtime is dominated bythe operations performed in the inner most for-loop on line 10. The space usage isdominated by the allocations on lines 4 and 7. Also note that line 6 only requiresa constant amount of memory since it only points tmp2 at tmp and doesn't allocateanything. One last detail. Even though we didn't mention it above, allocating memorydoes take time proportional to the size of the memory (since Java zeros out any objectsor arrays you allocate). Thus lines 4 and 7 also contribute Θ(n) to the runtime, butthis is dominated by the work on line 10.

4. The value of f(5) is 0 + 1 + 2 + 3 + 4 + 5 = 15. The runtime is Θ(n) and the spaceutilization is Θ(n). We will explain this in the next section on recursion.

1.2.6 Introduction to Recursion

There are three main things one must learn to understand how recursive functions work:

1. (Implementation) How the program stack and activation records work.

2. (Theoretical) How to break a problem into subproblems of the same type.

3. (Coding) The structure that recursive functions usually have.

As this is only an introduction we will review the �rst and third parts (implementation &coding) and brie�y discuss the second (theory). Later on we will spend more time on thetheory when discussing trees and divide-and-conquer algorithms.

Each time you call a function, memory is allocated for the local variables and argumentsin addition to other things (like where to go once the function returns). This memory iscalled the activation record for your function call. The activation records are organized as astack (we will learn more about stacks later). To illustrate this, we will depict what happenswhen we call f(1):

1. An activation record is allocated for f(1) with the argument variable �lled in:

10

Page 11: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

f(1) n

1

val

2. The function is executed until we reach line 4 where we call f again with argument 0.Another activation record is created for f(0) on top of the activation record for f(1).We say the activation record for f(0) is pushed onto the program stack.

f(0) n

0

val

f(1) n

1

val

3. The call to f(0) returns on line 3 with the value 0. The activation record for f(0) isremoved (popped) from the program stack. We return to line 4 in the call to f(1).

11

Page 12: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

f(1) n

1

val

0

4. The call to f(1) reaches line 5 where the value 1 is returned. The activation record forf(1) is popped o� the stack. What remains on top of the stack is the activation recordfor whichever function called f(1), which we have omitted.

The important take away from the above example is that each call to a function gets itsown copies of its argument and local variables. You may have actually seen evidence ofthe program stack before if you ever looked at one of your runtime exceptions. Here is anexample of such a stack trace for a program called IndexError:

1 import java.util.ArrayList;2 public class IndexError3 {4 public static void main(String[] args)5 {6 ArrayList<Integer> al = new ArrayList<>();7 al.get(4);8 }9 }

Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 4, Size: 0at java.util.ArrayList.rangeCheck(ArrayList.java:653)at java.util.ArrayList.get(ArrayList.java:429)at IndexError.main(IndexError.java:7)

As you can see, main is at the bottom of the stack and on line 7 it calls the get method ofArrayList. On line 429 of the ArrayList implementation the method rangeCheck is called,and then an exception is thrown. We will learn more about exceptions later and how theyinteract with the program stack.

Secondly, we show how to compute the runtime and memory usage of f , and then inves-tigate what it computes. When we call f(n) it will in turn call f(n− 1), which in turn callsf(n − 2), etc. This will go on until we get to f(0) for a total of n + 1 calls to f . Then allof the calls will return. Each call to f will have a runtime of Θ(1) (excluding the recursivecall), so the total runtime is Θ(n). Furthermore, when we call f(0) the program stack willhave Θ(n) activation records on it giving us the Θ(n) memory usage. To understand what

12

Page 13: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

f is doing, consider the task of adding the �rst numbers n positive integers:

1 + 2 + · · ·+ (n− 1) + n

and compare it to the task of adding the �rst n− 1 positive integers:

1 + 2 + · · ·+ (n− 1).

In pseudocode we could write:

int SumIntsUpTo(N)If N is 0, return 0Else

let s = SumIntsUpTo(N−1)return s + N

The key idea here is that the problem of adding the �rst n − 1 positive integers can bethought of as a subproblem for adding the �rst n positive integers. This is why f adds the�rst n positive integers.

Finally, note that our recursive function f has the form

Check Stopping Condition (also called base case)Do work and perform recursive callReturn

You will see this structure again and again when looking at recursive function implementa-tions.

1.2.7 Program Arguments and the Terminal

In the past you may have run all of your Java programs by clicking on run in Eclipse. Eclipseis actually doing a bunch of tasks behind the scenes. Suppose we were writing a Java programcalled Hmm.java with a public class Hmm and used a plain text editor instead of Eclipse.Once we �nished editting Hmm.java we could go to the terminal and type

javac Hmm.java

This will compile Hmm.java into a binary �le Hmm.class that is optimized for running.Furthermore, javac will let us know all of the errors the compiler found in the code. Eclipseis constantly compiling our Java �les in the background and letting us know (with redunderlines) whenever we make an error it can catch. The next step is to run our compiledclass �le. To do this we write

java Hmm

This runs a program called the Java Virtual Machine (JVM) and gives it our Hmm.class �leto execute. If instead we write

java Hmm wow blah

13

Page 14: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

then it will again run our class on the JVM but the main function will get a String[] args =

{"wow","blah"} as an argument. One last bit about the terminal. You have probably writtenprograms using Scanner that take input from the user on the console (using nextInt andsuch). The terminal allows us to use the syntax

java Hmm < input.txt

This will run the class Hmm and will use input.txt to supply all of the console input. Thisway we can test programs that need console input without having to repeatedly type in ourinput. You can also type

java Hmm < input.txt > output.txt

so that all of the input comes from input.txt and all of the output (all of your System.outstu�) goes to output.txt. Using input.txt and output.txt in this way is called I/O redirection.In the newest version of Eclipse we can get the same behavior by using an appropriate runcon�guration.

14

Page 15: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

2 Lecture 2: More Recursion then OOP

2.0.8 Recursion Exercises

1. What does the function p below output when you call p(3)?

static void p(int n){if (n == 0) return;System.out.printf("Before: %d\n",n);p(n−1);System.out.printf("After: %d\n",n);

}

2. Write a recursive implementation of factorial. (?) What will your implementation dofor a negative value?

3. High/Low: Your friend picks a number between 1 and n, inclusive. You keep guessingthe number. For each guess he gives one of the three following answers: correct, toohigh, too low.

(a) How many guesses will you need (at most) when n = 3, 7, 15?

(b) In Θ-notation, how many guesses will you need for arbitrary n?

(c) Suppose you have a sorted array of n ints. How long will it take to �nd whethera value v is in the array?

4. (? ? ?) What does the function h below compute? Also gives its runtime and memoryusage.

1 static int hHelper(int[] arr, int left, int right)2 {3 if (left == right) return arr[left];4 int mid = (left + right)/2;5 int L = hHelper(arr,left,mid), R = hHelper(arr,mid+1,right);6 return L + R;7 }8 static int h(int[] arr)9 {10 return hHelper(arr,0,arr.length−1);11 }

2.0.9 Solutions

1. We obtain the output

15

Page 16: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

Before: 3Before: 2Before: 1After: 1After: 2After: 3

2. Code is listed below:

static int factorial(int n){if (n == 0) return 1;return n*factorial(n−1);

}

We could also use a tertiary condition and write

static int factorial(int n) { return n == 0 ? 1 : n*factorial(n−1); }

If we attempt to call factorial(−1) then the program will keep recursing until we runout of stack space and the program exits (called a stack over�ow).

3. (a) 2, 3, 4, respectively by always choosing the middle value. More generally we cando n = 2k+1 − 1 in k steps.

(b) Each time we roughly halve the number of values we must consider. At worst wemust reduce the number of possible values to a single number. This will give ourΘ(lg n) runtime.

(c) Θ(lg n). Play the High/Low game with the indices of the array. This is calledbinary search. Here is a recursive and an iterative implementation:

BinarySearch.java

public class BinarySearch{

public static int binarySearch(int[] arr, int L, int R, int v){

if (L > R) return −1;int M = (L+R)/2;if (v == arr[M]) return M;if (v < arr[M]) return binarySearch(arr,L,M−1,v);else return binarySearch(arr,M+1,R,v);

}public static int binarySearch(int[] arr, int v){

16

Page 17: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

return binarySearch(arr,0,arr.length−1,v);}public static int binarySearchIter(int[] arr, int v){

int L = 0, R = arr.length−1;while (L <= R){

int M = (L+R)/2;if (arr[M] == v) return M;if (v < arr[M]) R = M−1;else L = M+1;

}return −1;

}}

4. The function h computes the sum of the given array. More precisely, hHelper computesthe sum of the entries of arr between the indices left and right, inclusive. To do this itsplits the array in half, and recurses on each half. The memory usage is Θ(lg n) wheren is the length of arr. To see why, note that each recursive call halves the number ofindices we are summing. It takes lg n halvings to reduce the interval to size 1, the basecase. There are a few ways to see the runtime is Θ(n). One is to see that L + R willoccur n− 1 times: one for each plus in the following expression

arr[0] + arr[1] + arr[2] + ... + arr[n−2] + arr[n−1]

Here we will give some intuition behind the running time and the memory usage forthose that know what a binary tree is. We will come back to this later in the semester.Consider a complete binary tree of height k. It has

1 + 2 + 4 + 8 + · · ·+ 2k =k∑

i=0

2i = 2k+1 − 1

nodes and 2k leaves. The two things to notice are that there are roughly half as manyleaves as nodes, and the height is the logarithm of the number of leaves. The logarithmthat shows up here is essentially the source of all logarithms we will encounter in thisclass. In this particular example we have an implicit tree formed by breaking eachinterval into its left and right halves. There will be n leaves so the height will beroughly log n (n may not be a power of 2). Furthermore, the total number of nodeswill be less than double the number of leaves, so the runtime will be Θ(n).

As we have seen, recursive implementations can be more costly than iterative (loop based)implementations. There are some languages that have optimizations to improve the spaceand performance of certain recursive calls (such as tail-call optimization; you can look thisup if you are interested). Later in the course we will use recursion when dealing with trees.

17

Page 18: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

2.1 OOP Segue Exercise

1. You are given as input (through standard input, i.e., System.in) earthquake data. The�rst line contains a single integer n, the number of earthquakes. Each of the n followinglines has the format:

City,Date,Magnitude

City and Date are just strings you can store. Magnitude will be a decimal value. Anexample of the input will be:

2Valdivia,5/22/1960,9.5Kamchatka,10/17/1737,8.5

Write a program that reads the input and outputs (in the format below) the city, date,and magnitude of the largest magnitude earthquake given. If there is a tie, output anyof the largest ones. On the above example you should output:

City = ValdiviaDate = 5/22/1960Magnitude = 9.5

It's ok if you don't remember how to do the input in Java. Focus on how the programwould be structured.

2.2 Solutions

1. In this problem we review I/O and start to talk about objects. Listed below is asolution.

Earthquakes.java

1 import java.util.Scanner;2 public class Earthquakes3 {4 public static int �ndMax(double[] arr) {5 int pos = 0;6 for (int i = 1; i < arr.length; ++i) {7 if (arr[i] > arr[pos]) pos = i;8 }9 return pos;10 }11 public static void main(String[] args) {12 Scanner in = new Scanner(System.in);

18

Page 19: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

13 int n = in.nextInt();14 in.nextLine(); //Why?15 String[] cities = new String[n];16 String[] dates = new String[n];17 double[] magnitudes = new double[n];18 for (int i = 0; i < n; ++i) {19 String line = in.nextLine();20 String[] parts = line.split(",");21 cities[i] = parts[0];22 dates[i] = parts[1];23 magnitudes[i] = Double.parseDouble(parts[2]);24 }25 int pos = �ndMax(magnitudes);26 System.out.printf("City = %s\n",cities[pos]);27 System.out.printf("Date = %s\n",dates[pos]);28 System.out.printf("Magnitude = %f\n",magnitudes[pos]);29 }30 }

We decided to split out �ndMax into its own function for cleanliness. Note that online 17 we need to call in.nextLine(). This is because the initial call to nextInt on line16 reads the integer n but doesn't read the newline after it on the �rst line. The callto nextLine on line 17 �nishes reading the �rst line (it will return "", an empty string,which we discard). On line 24 we use the split method of String. It accepts a pattern(called a regular expression) and uses it to match delimeters. It returns an array ofthe substrings that have been separated by the given delimeter we pattern. We wonthave time to delve into regular expressions, but as an example of their usefulness wecould have written the following code which splits a String using commas surroundedby optional whitespace:

String[] parts = line.split("\\s*,\\s*");

2.3 Objects

Most of the material here is also discussed in Liang's book on Java Programming (chapters9-13 in the 10th edition).

In the Earthquakes program above we had separate arrays for dates, cities, and magni-tudes. Instead we can create a class called Earthquake to represent our data.

Earthquake.java

1 public class Earthquake2 {3 public String city;4 public String date;

19

Page 20: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

5 public double magnitude;6 public Earthquake(String c, String d, double m)7 {8 city = c;9 date = d;10 magnitude = m;11 }12 }

EarthquakesObj.java

1 import java.util.Scanner;2 public class EarthquakesObj3 {4 public static int �ndMax(Earthquake[] arr)5 {6 int pos = 0;7 for (int i = 1; i < arr.length; ++i)8 {9 if (arr[i].magnitude > arr[pos].magnitude) pos = i;10 }11 return pos;12 }13 public static void main(String[] args)14 {15 Scanner in = new Scanner(System.in);16 int n = Integer.parseInt(in.nextLine().trim());17 Earthquake[] earthquakes = new Earthquake[n];18 for (int i = 0; i < n; ++i)19 {20 String line = in.nextLine();21 String[] parts = line.split(",");22 Earthquake e = new Earthquake(parts[0],parts[1],23 Double.parseDouble(parts[2]));24 earthquakes[i] = e;25 }26 int pos = �ndMax(earthquakes);27 Earthquake big = earthquakes[pos];28 System.out.printf("City = %s\n",big.city);29 System.out.printf("Date = %s\n",big.date);30 System.out.printf("Magnitude = %f\n",big.magnitude);31 }32 }

The above code is cleaner than our original implementation since we only have a singlearray earthquakes. You can see that if an earthquake had 10 properties, storing 10 arrayscould become very annoying. Secondly, we now have the concept of an Earthquake in our

20

Page 21: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

program. One of the virtues of OOP (object-oriented programming) is that some of theimportant concepts in our mental view of a problem can be represented as objects in aprogram.

Here we have used the notion of a compound data structure or record. Our objects arejust a way of taking multiple pieces of data and aggregating them in one location. OOPextends this usage of objects by allowing us to mix behavior with data, and provides toolsto enable this method of programming/design.

As another example, here is a Student class.

Student.java

public class Student {public static int numStudents;public String name;public String email;public int credits;

public Student(String n, String e) {name = n;email = e;numStudents++;

}

public String getName() { return name; }public String getEmail() { return email; }public int getCredits() { return credits; }public void addCredits(int creds) { credits += creds; }public static int getNumStudents() { return numStudents; }public String toString() {

return String.format("Student: %s, %s, %d",name,email,credits);}

}

As with the Earthquake class, the Student class de�nes a data type. Each object of typeStudent will have 3 �elds: name, email, credits. We also have a class variable called num-Students and a class method named getNumStudents. The class has 6 methods. The �rstmethod above is called a constructor, and is used when you create objects of type Stu-dent. The last method (toString) is what is used by Java to turn your object into a Stringwhen needed. This is what allows us to use System.out.println(s) below. Here are some codesnippets that use this class.

Snippet1

1 Student s = new Student("Brett","[email protected]");2 s.addCredits(150);3 System.out.println(s.getCredits());4 System.out.println(s);

21

Page 22: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

Snippet2

1 Student s = new Student("Roger","[email protected]");2 s.credits += 150;3 System.out.println(s.credits);4 System.out.println(s);

Both of these snippets show how objects of type Student can be used. Let's �rst go into detailon what is occurring in Snippet1. On line 1 we create a variable of type Student. All typeswe create by de�ning classes are reference types: a variable of a reference type is a referenceor pointer to the value as opposed to holding the value itself (in contrast with a primitivedata type like int or double). The variable s points at an object of type Student which wecreate using its constructor. Note that credits is not given a value in the constructor. Any�eld we do not initialize is given the value 0 (or null if its a reference type). The constructoralso increments the static variable numStudents. Since numStudents is static, it belongs tothe class and not each instance. This allows numStudents to accurately count the numberof students that have been created.

Each object of type Student will have 3 �elds. Note that the 3 �elds are declared inthe Student class without using the keyword static. This means that each object of typeStudent will have its own variables named email, name, and credits. If we had used thekeyword static we would have declared a class variable. On line 2 we call the addCreditsmethod with argument 150. Note that when we de�ned addCredits in class Student, wedidn't use the keyword static. This means that addCredits is used on objects (also calledinstances) of type Student as is shown in the snippet.

We can imagine that Snippet2 exists somwhere in another �le and also uses the Studentclass. Snippet2 and Snippet1 seem similar in what they do. Is there a reason to prefer oneover the other? To see why Snippet1 is preferrable, suppose we have to change the Studentclass as follows: No student is allowed to have more than 130 credits; if credits are added toviolate this, the student is capped at 130 credits. To implement this we modify addCredits:

public void addCredits(int creds){credits += creds;if (credits > 130) credits = 130;

}

After the change Snippet1 continues to work as desired but Snippet2 is broken. If youimagine we have a huge program with hundreds of lines of code that use Student in themanner that Snippet2 does, then implementing this change would require a lot of work. Asanother example, imagine we wanted to change to using �rst and last name instead of justname. Then code using getName would be �ne since we can �x the method, but any codedirectly accessing the name �eld would all break. To prevent Snippet2 from happening weintroduce access modi�ers.

public class Student {private static int numStudents;private String name;

22

Page 23: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

private String email;private int credits;

public Student(String n, String e) {name = n;email = e;numStudents++;

}

public String getName() { return name; }public String getEmail() { return email; }public int getCredits() { return credits; }public void addCredits(int creds) { credits += creds; }public static int getNumStudents() { return numStudents; }public String toString() {

return String.format("Student: %s, %s, %d",name,email,credits);}

}

Now Snippet2 will not compile since the private modi�er means that those �elds can onlybe accessed from code within class Student. Modi�ers can be put on �elds, static variables,methods and static methods. The other two modi�ers are package and protected which wewill see later. Classes can also carry access modi�ers, but we wont discuss this now.

Access modi�ers can be thought of as compiler error generators. You may be at thepoint in your programming career where you hate compiler errors and generating more ofthem sounds horrible. Part of becoming better as a programmer is learning to love compilererrors. The hope is that we generate more compiler errors and wind up with fewer runtimeerrors.

By making the �elds of an object private we are restricting the way in which users ofour class can access it. This is part of what is called encapsulation. Except in a few rarecases (such as academic examples) we will make all of our �elds private (or maybe protected;see later). To a user, all that matters is which methods are public and what their statedbehaviour is.

2.4 Object Exercises

1. Assume you have the following Stock class already written:

public class Stock{

private String name;private double price;public Stock(String n, double p) { name = n; price = p; }public String getName() { return name; }public double getPrice() { return price; }

23

Page 24: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

public void setPrice(double p) { price = p; }public String toString() {

return String.format("%s $%f",name,price);}

}

We left out comments in the code for brevity. Design a class Position that has a Stockstock and an int quantity as members. Give a constructor, and accessor methods (i.e.,getters) for the �elds. Also give a method getValue that returns a double indicatingthe value of the position (quantity times price). Then create a class called TestPositionthat has a main method for creating a sample Position and outputs the value of eachmethod call.

2. In the following code, say what e�ect each of the 4 swap methods has.

IntValue.java

public class IntValue {public int val;public IntValue(int v) { val = v; }

}

SwapExample.java

1 import java.util.Arrays;2 public class SwapExample {3 public static void swap(int a, int b) {4 int tmp = a;5 a = b;6 b = tmp;7 }8 public static void swap(int[] a, int[] b) {9 int[] tmp = a;10 a = b;11 b = tmp;12 }13 public static void swap(int[] arr, int i, int j) {14 int tmp = arr[i];15 arr[i] = arr[j];16 arr[j] = tmp;17 }18 public static void swap(IntValue v, IntValue w) {19 int tmp = v.val;20 v.val = w.val;21 w.val = tmp;22 }23 public static void main(String[] args) {

24

Page 25: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

24 int a = 4, b = 5;25 int[] arr = {0,1,2,3};26 int[] arr2 = {4,5,6,7};27 int[] arr3 = arr2;28 swap(a,b);29 swap(arr,arr2);30 swap(arr,0,1);31 arr3[0] = 9;32 IntValue x = new IntValue(1), y = new IntValue(2);33 swap(x,y);34 System.out.printf("a=%d,b=%d\n",a,b);35 System.out.printf("arr=%s\n",Arrays.toString(arr));36 System.out.printf("arr2=%s\n",Arrays.toString(arr2));37 System.out.printf("arr3=%s\n",Arrays.toString(arr3));38 System.out.printf("x.val=%d,y.val=%d\n",x.val,y.val);39 }40 }

2.5 Object Solutions

1. The code follows.

Position.java

public class Position {private Stock stock;private int quantity;public Position(Stock stock, int q) {

this.stock = stock;quantity = q;

}public int getQuantity() { return quantity; }public Stock getStock() { return stock; }public double getValue() { return quantity*stock.getPrice(); }

}

TestPosition.java

public class TestPosition{

public static void main(String[] args){

Stock s = new Stock("IBM",147.24);Position p = new Position(s,1000);System.out.printf("Stock: %s, Quantity: %d, Value: $%f\n",

p.getStock(),p.getQuantity(),p.getValue());}

25

Page 26: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

}

The code above has no methods that allow you to set the �elds. In other words, oncethe objects are constructed, they will never change. These kinds of objects are calledimmutable. Another example of immutable objects are Java Strings.

2. The �rst two swap methods have no e�ect due to pass-by-value. The third swaps thevalues in positions i, j of the array referred to by arr. The fourth swaps the values inthe val �elds of v, w, respectively. To further illustrate this point, note that main willoutput the following.

a=4,b=5arr=[1, 0, 2, 3]arr2=[9, 5, 6, 7]arr3=[9, 5, 6, 7]x.val=2,y.val=1

Also important to notice is that line 31 changed the arrays referenced by arr2 and arr3.

2.6 Inheritance and Polymorphism

For our next topic today on OOP we look at inheritance. Earlier we had a Student class.Suppose we want to create a class called CIMSStudent that has all of the �elds of Student,but adds on the two extra String �elds cimsLogin and cimsEmail. When creating the newclass CIMSStudent we could copy and paste all of the code in Student and just add a fewmore �elds. More often than not, if you are programming and doing a lot of copying andpasting you are doing something wrong. Java provides a way to say one class is an extension(or a more specialized form) of another class:

CIMSStudent.java

1 public class CIMSStudent extends Student2 {3 private String cimsEmail;4 private String cimsLogin;5 public CIMSStudent(String name, String email,6 String cEmail, String cLogin)7 {8 super(name,email);9 cimsEmail = cEmail;10 cimsLogin = cLogin;11 }12 public String getCIMSEmail() { return cimsEmail; }13 public String getCIMSLogin() { return cimsLogin; }14 public String toString()15 {

26

Page 27: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

16 //String n = name; //Doesn't compile17 return String.format("CIMSStudent: %s, %s, %s, %d\n",getName(),18 cimsEmail,cimsLogin, getCredits());1920 }21 }

The �rst thing to notice is the extends keyword on line 1. We say the class CIMSStudentsextends (or inherits from, or derived from, or is a subclass of, or is a child of) the class Stu-dent. Conversely we say Student is the superclass (or parent, or base class) of CIMSStudent.Here we are saying that every object of type CIMSStudent also has all the �elds a Studentobject would have.

On line 8 we use the keyword super. The �rst thing any constructor does is call theconstuctor of its parent class. If we don't provide the super keyword, it calls the no-argumentconstructor of the parent class if it exists (if it doesn't exist, a compiler error occurs). Byusing the super keyword we explicitly state which constructor to call, and what argumentsto pass in. Before we explain the rest, let's give a code snippet using CIMSStudent:

Snippet

CIMSStudent s = new CIMSStudent("A","[email protected]","[email protected]","a1234");

System.out.println(s.getCIMSEmail());System.out.println(s.getName());System.out.println(s);

As you can see in the snippet the method getName can be called on objects of type CIMSStu-dent. In fact, all public �elds or methods of any ancestor are accessible. This is what wehad in mind when we extended Student. Next we come to line 16 of CIMSStudent. Thereason that line will not compile is that private members of a class are not accessible in itschildren. Thus to access the name and credits �elds of the parent class we use the accessor(get) methods we wrote earlier. Stated di�erently, a CIMSStudent object does have thename, email, and credits �eld from its parent, but it cannot access/see them directly dueto the private access modi�er. If you want to create a �eld that your children can see, butpeople outside your inheritance hierarchy cannot you use the protected keyword. [This isalmost true. Technically, protected means any descendent or anyone in your package hasaccess.]

The �nal thing we must address is line 14. The method toString occurs in both theparent Student and child CIMSStudent (with the same arguments, i.e., none). This is calledmethod overriding and is a very important part of OOP. The following illustrates some ofthe true power of inheritance: polymorphism.

Snippet

1 Student s = new CIMSStudent("A","[email protected]",2 "[email protected]","a1234");3 //System.out.println(s.getCIMSEmail()); //Doesn't compile

27

Page 28: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

4 System.out.println(s.getName());5 String t = s.toString(); //Calls the CIMSStudent version6 System.out.println(t);

On line 1 we create a CIMSStudent object and our Student variable points at it. Java allowsa reference of type T to point at objects whose type is any direct or indirect subclass of T(i.e., you can point at children, grandchildren, etc.). Here we say the reference s has typeStudent (or the static type is Student) and the object it points at has type CIMSStudent (orthe dynamic type is CIMSStudent). On line 2 we would get a compiler error since Studentsdon't have a method getCIMSEmail. Thus, the name and arguments of the methods we cancall must match the available methods on the type of the variable (and not the type of theobject it points at). On line 4 we call the getName method which calls the getName methodof Student. On line 5 we call the CIMSStudent version of toString. This gives the following:

1. The type of the variable (i.e., reference) is used to determine (at compile time) whatmethod calls are allowed, BUT

2. the version of the method is determined by the object. The one chosen is either in theobject's type, or if it doesn't exist, in its closest ancestor.

This behavior where the runtime type of the object determines which method is used iscalled polymorphism. It is extremely powerful.

2.7 Inheritance and Polymorphism Exercises

1. Consider the following code.

Child.java

1 class Grandparent {2 public Grandparent() {3 System.out.println("Constructing Grandparent");4 }5 public int getValue() { return −1; }6 }7 class Parent extends Grandparent {8 protected int pi;9 public Parent(int i) {10 System.out.println("Constructing Parent "+i);11 pi = i;12 }13 public void printName() {14 System.out.printf("P %d %d\n",pi,getValue());15 }16 }17 public class Child extends Parent {18 private int ci;

28

Page 29: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

19 public Child(int i, int pi) {20 super(pi);21 System.out.println("Constructing Child "+i);22 ci = i;23 }24 public int getValue() { return ci; }25 public void printName() {26 System.out.printf("C %d %d %d\n",pi,ci,getValue());27 }28 public static void main(String[] args) {29 Parent p = new Parent(4);30 Parent c = new Child(6,7);31 Grandparent c2 = c;32 p.printName();33 c.printName();34 //c2.printName(); //Compiler error35 System.out.printf("%d %d %d\n",p.getValue(),36 c.getValue(),c2.getValue());37 System.out.println(c);38 }39 }

(a) What does it mean when you don't have public in front of a class?

(b) What does it output when we run Child?

2. Why does System.out.println(v) work for any object v?

2.8 Inheritance and Polymorphism Solutions

1. (a) It means the class has package access. That is, can be accessed from other classesin the same package. At this point we haven't created any explicit packages soall of our classes are in the default package which has no name. Two classes arein the same unnamed package if they share a folder on the �le system (technical:not always true, but true in nearly every case; see Java Language Spec 8 section7.4.2).

(b) The output is as follows:

Constructing GrandparentConstructing Parent 4Constructing GrandparentConstructing Parent 7Constructing Child 6P 4 −1C 7 6 6

29

Page 30: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

−1 6 6Child@14ae5a5

Line 29 generates lines 1,2 of the output. Line 30 generates lines 3-5 of the output.Lines 6,7 are the two printName calls. Line 8 shows that Parent objects use theGrandparent getValue and Child object's use the Child's getValue (independent ofthe reference type). Line 9 shows what happens when you don't override toString.A few other things to note here:

� Child's printName can access pi since it is protected.

� All calls to getValue use the corresponding object's version.

� getValue cannot be called using a reference of type Grandparent.

2. System.out is a type of PrintStream (more precisely, out is a public static �eld of typePrintStream of the class System). All PrintStreams have the following method:

In java.io.PrintStream

public void print(Object obj) {write(String.valueOf(obj));

}

which calls the static String method:

In java.lang.String

public static String valueOf(Object obj) {return (obj == null) ? "null" : obj.toString();

}

The write method of PrintStream does the actual writing and is omitted. The key hereis that print (and println too) have an overloaded version that takes an Object. A fewpoints on this:

� All reference types (including arrays) descend from Object. When you don'texplicitly extend a class you end up extending Object instead.

� Object has various useful methods such as toString, hashCode, equals and others.These have default implementations that are frequently overriden.

� PrintStreams have a overloaded print methods that take Objects and Strings. Ifyou call System.out.print("Hello") it knows to call the String version since it willalways choose the closest matching version of the function. Here closest meansprefering your type over parents, parents over grandparents, etc (i.e., closest an-cestor in the type hierarchy).

30

Page 31: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

3 Lecture 3: More on OOP

3.1 Review Exercises

1. Given the following code, what happens when you call factorial(−1)?

public static int factorial(int n) {if (n == 0) return 1;return n * factorial(n−1);

}

2. Write a recursive function that takes a double x and a non-negative integer n andreturns xn. What is the runtime and space utilization of your function?

3. Assume that Child extends Parent and that both have no-argument constructors.Which of the following lines will give a compile-time error?

1 Child c = new Child();2 Parent p = c;3 c = p;4 p = new Parent();5 c = p;

Is there a way to force Java to make it compile?

4. Is it possible to construct a class that cannot be extended (i.e., the compiler will giveyou an error if you try)?

3.2 Review Solutions

1. If we call factorial(−1) then the program will keep recursing until we run out of stackspace and the program exits (called a stack over�ow).

2. Below are some implementations:

PowerFunctions.java

public class PowerFunctions {public static double pow(double x, int n) {

if (n == 0) return 1;return x*pow(x,n−1);

}public static double fasterPow(double x, int n) {

if (n == 0) return 1;if (n % 2 == 0) {

double d = fasterPow(x,n/2);return d*d;

31

Page 32: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

}else return x*fasterPow(x−1,n);

}}

The runtime and space of pow is Θ(n). The runtime and space of fasterPow is Θ(lg n).

3. Lines 3 and 5 give compile-time errors since it will only allow you to assign a descen-dent reference to a base type reference, and not vice-versa. A set of mnemonics thatsometimes help when dealing with OOP are is-a and has-a. If a class C is a descendentof class P then we say that a C is a P . Consider the case where we had a Student anda CIMSStudent. It makes sense to say a CIMSStudent is a Student, since it is a spe-ci�c kind of Student that adds on extra behavior/�elds. We use has-a when describing�elds. For example, we say a Position has a Stock.

In this example we can resolve the compile-time errors using casts:

1 Child c = new Child(1,2,3);2 Parent p = c;3 c = (Child)p;4 p = new Parent(4,5);5 c = (Child)p;

The compiler will not complain now, but you will get a ClassCastException when youtry to run line 5 (line 3 will run just �ne). Java will let you test whether a cast willwork using the instanceof keyword that we will see later.

4. We could produce this behavior by creating a class with only private constructors, butJava provides a much more �exible way allowing for arbitrary constructors. By usingthe �nal keyword we can declare a class that cannot be extended:

�nal class Leaf {}

The keyword �nal has another usage in Java. If you put �nal in front of a variable youare declaring then its value cannot change. For references, �nal means the variablecannot be repointed. For example, if we have �nal int[] arr = {1,2,3}; then the valuesin the array that arr points at can change, but you cannot point arr at a di�erent arrayobject. When people declare constants in a class (for instance Math.PI) they wouldmake it a public static �nal variable.

3.3 Interfaces and Abstract Classes

Unlike some other languages that support OOP, in Java a single class cannot extend morethan 1 other class. To somewhat makeup for this limitation, Java allows a class to implementany number of interfaces. But before we get to interfaces, we �rst describe abstract classeswhich will segue into interfaces. If you use the keyword abstract when declaring a class, thenJava makes it impossible to make objects of that class. The purpose of an abstract is to

32

Page 33: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

extend it. Within an abstract class you can create methods without bodies and label themas abstract. This has the e�ect of forcing derived concrete (non-abstract) classes to overridethe abstract methods. Other than enabling abstract methods, there is no di�erence betweenabstract classes and regular classes. Here is an example:

AbstractClasses.java

1 abstract class InterestCalculator {2 abstract public String getName();3 abstract public double getInterest(double principal,4 double rate, int periods);5 public String toString() { return getName(); }6 }7 class StandardCompounding extends InterestCalculator {8 public String getName(){ return "Standard Compounding"; }9 public double getInterest(double principal,10 double rate, int periods) {11 return principal * Math.pow(1+rate,periods);12 }13 }14 class ContinuousCompounding extends InterestCalculator {15 public String getName(){ return "Continuous Compounding"; }16 public double getInterest(double principal,17 double rate, int periods) {18 return principal * Math.exp(rate*periods);19 }20 }21 class Account {22 private double balance;23 public Account() { balance = 0;}24 public void addMoney(double m) { balance += m; }25 public void removeMoney(double m) { balance += m; }26 public double getBalance() { return balance; }27 public void accrueInterest(double rate, int periods,28 InterestCalculator calc) {29 System.out.printf("Using a %s calculator\n",calc);30 balance = calc.getInterest(balance,rate,periods);31 }32 }33 public class AbstractClasses {34 public static void main(String[] args) {35 //InterestCalculator ic =36 // new InterestCalculator(); //Doesn't compile37 Account ac = new Account();38 ac.addMoney(100);39 ac.accrueInterest(.01,10,new StandardCompounding());

33

Page 34: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

40 ac.accrueInterest(.01,10,new ContinuousCompounding());41 System.out.println(ac.getBalance());42 }43 }

Here InterestCalculator is an abstract class de�ning 2 abstract methods and 1 concretemethod. Note that the concrete method toString calls getName which will be overriden insome subclass. The method accrueInterest in Account illustrates the power of polymorphism.By taking an InterestCalculator, it allows any class that extends InterestCalculator to beused. This includes the two derived classes we have written here, and any new ones wedecide to write in the future. Also note that on line 35 that we cannot make any objects oftype InterestCalculator.

An interface is like an abstract class, but all methods are public, and you cannot have any�elds unless the �elds are public, �nal, and static (�nal means the value cannot be changed).Even more importantly, you can implement as many interfaces as you want:

InterfaceTest.java

1 interface A {2 void aMethod();3 static void hmm() { System.out.println("Hmm"); }4 }5 interface B {6 void bMethod();7 default void cMethod() { System.out.println("Wow"); }8 }9 class Wow {}10 public class InterfaceTest extends Wow implements A,B {11 public void aMethod() { }12 public void bMethod() { }13 public static void main(String[] args) {14 InterfaceTest i = new InterfaceTest();15 A a = i;16 B b = i;17 a.aMethod();18 b.bMethod();19 b.cMethod();20 A.hmm();21 }22 }

Here we see how to implement an interface, and that you can implement multiple interfaces.If an interface method is not labeled as default, then it is abstract (labeling as default isa Java 8 feature). An interface may also have static methods with implementations (alsoJava 8). Any interfaces that a class implements are considered its ancestors, so you canassignment works as above. Just like abstract classes, it is not possible to construct anobject whose type is an interface; it must be implemented.

34

Page 35: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

3.4 Abstract Classes and Interfaces Exercises

1. What two modi�ers would make no sense if they were both on a class at the sametime?

3.5 Abstract Classes and Interfaces Solutions

1. It would make no sense for a class to be �nal and abstract. The compiler will actuallygive you an error if you try it.

35

Page 36: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

4 Lecture 4: Dynamic Arrays, Exceptions and Generics

4.1 Exercises

1. Design a class TeamRoster (list of the members on a team) that has the followingmethods:

� void addMember(String name) : Adds a new member to the roster.

� boolean hasMember(String name) : Returns true if the team has the given mem-ber, and false otherwise.

(a) Give the runtime for both methods you have implemented assuming the Team-Roster currently has n members on it.

(b) (?) Suppose you are told that the addMember will be rarely used but hasMemberis used very frequently. How could you change your implementation to improveits performance?

4.2 Solutions

1. One potential answer would be use an array and have an upper bound on how big theroster can get:

BoundedTeamRoster.java

public class BoundedTeamRoster {private String[] roster;private int rosterSize;public BoundedTeamRoster(int capacity) {

roster = new String[capacity];}public void addMember(String name) {

roster[rosterSize] = name;rosterSize++;

}public boolean hasMember(String name){

for (int i = 0; i < rosterSize; ++i)if (roster[i].equals(name)) //Can't use ==

return true;return false;

}public int size() { return rosterSize; }

}

The above implementation works, but has the downside of bounding the maximumnumber of members. If we try to add too many members we will get an exception

36

Page 37: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

when we exceed the size of the array. Furthermore, if we make our rosters very largejust in case, we may be wasting a ton of space. Let's improve our roster by letting itgrow:

TeamRoster.java

import java.util.Arrays;

public class TeamRoster {private String[] roster;private int rosterSize;public TeamRoster(int initCap) {

roster = new String[initCap];}public void addMember(String name) {

if (rosterSize == roster.length)roster = Arrays.copyOf(roster, roster.length+1);

roster[rosterSize] = name;rosterSize++;

}public boolean hasMember(String name){

for (int i = 0; i < rosterSize; ++i)if (roster[i].equals(name)) //Can't use ==

return true;return false;

}public int size() { return rosterSize; }

}

This solves our bounding issue but has its own problems as we will see.

(a) For TeamRoster we have Θ(n) and Θ(n), for the runtimes of addMember andhasMember, respectively. Here we take a large time penalty on addMember sincewe have to copy the entire array to a larger one if it is full.

(b) The point of this question is that access patterns can determine how we implementour data structures. We can modify TeamRoster so that the array is alwayssorted. Here addMember will take Θ(n) and hasMember will be Θ(lg n) since wewill perform a binary search. The implementation follows:

SortedTeamRoster.java

import java.util.Arrays;

public class SortedTeamRoster {private String[] roster;private int rosterSize;

37

Page 38: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

public SortedTeamRoster(int initCap) {roster = new String[initCap];

}public void addMember(String name) {

if (rosterSize == roster.length)roster = Arrays.copyOf(roster, roster.length+1);

roster[rosterSize] = name;rosterSize++;int pos = rosterSize−1;while (pos > 0 &&

roster[pos].compareTo(roster[pos−1]) < 0){

String tmp = roster[pos];roster[pos] = roster[pos−1];roster[pos−1] = tmp;pos−−;

}}public boolean hasMember(String name) {

return Arrays.binarySearch(roster, 0,rosterSize, name) >= 0;

}public int size() { return rosterSize; }

}

Note that hasMember uses the built-in binarySearch of the Arrays class. We havealso changed addMember to maintain the roster order. To do this once we haveadded a new name to the end, we keep shifting it earlier in the array until it hasfound its correct position. This is the idea behind insertion sort which we willlook at later in the course. Later in the course we will learn about other datastructures that will improve on these runtimes.

4.3 Our First Data Structure: Dynamic Array

In the TeamRoster class we implemented an array of Strings that we grew as we neededmore space. This gave us the virtues of an array without the limitations of �xed size. Manyprograms require this type of storage for their data. It would be very ine�cient to have eachprogram that needed such storage to implement it themselves. As such, we will implementa general purpose dynamic array that programs can use. Below we repeat the work we didabove with a few changes:

DynamicStringArray.java

import java.util.Arrays;

public class DynamicStringArray {

38

Page 39: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

private String[] data;private int size;public DynamicStringArray(int initCap) {

data = new String[initCap];}private void ensureCapacityForAdd(){

if (size == data.length)data = Arrays.copyOf(data, data.length*2);

}public void add(String s) {

ensureCapacityForAdd();data[size] = s;size++;

}public boolean contains(String s) {

for (int i = 0; i < size; ++i)if (data[i].equals(s)) return true;

return false;}

}

Here we have made one very noteable change to the behaviour. Instead of increasing the sizeby 1 when the array is full, we double its size. The goal here is to avoid recopying the arrayover and over on each subsequent addition. Note as before that a dynamic array has twodi�erent notions of size: capacity and size. The capacity is what what we call data.length,i.e., the number of values the dynamic array is capable of holding before you need to growit again. The size is the current number of elements in the array.

4.4 Dynamic Array Exercises

1. Implement the method add(int pos, String value) which inserts value into the array inposition pos and shifts the values in positions pos and later one position back to makeroom. (This is similar to what happens when you insert a row in a spreadsheet.) Forexample, if data currently has {"A","B","C","D"} and we call add(1,"WOW") then theresulting data is {"A","WOW","B","C","D"}. What is the runtime in terms of n, thesize of the dynamic array before the add?

2. Implement the method remove(int pos) which removes the String in position pos andshifts all of the later String down to �ll the gap. For example, if data currently has{"A","B","C","D"} and we call remove(1) then the resulting data is {"A","C","D"}.What is the runtime in terms of n, the size of the dynamic array before the add?

3. (??) Suppose we begin with an empty DynamicStringArray with capacity 1. What isthe runtime to add n elements to the back of the array (i.e., to call add(String value)

39

Page 40: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

n times)?

4. How can we make a DynamicArray that works with any type (not just Strings)?

4.5 Dynamic Array Solutions

1. Here we implement add:

public void add(int pos, String value){ensureCapacityForAdd();for (int i = size; i >= pos+1; −−i) {data[i] = data[i−1];

}data[pos] = value;size++;

}

Note that looping backwards works but doing a similar process looping forwards willnot. The implementation above has one issue. Suppose the size is 10, the capacity is 20and we add a value in position 15. The function will not complain but the behaviouris unintended. To �x this we make the following change:

public void add(int pos, String value){if (pos < 0 || pos > size)throw new IndexOutOfBoundsException("Index = "+pos+" Size = "+size);

ensureCapacityForAdd();for (int i = size; i >= pos+1; −−i)data[i] = data[i−1];

data[pos] = value;size++;

}

We will discuss exceptions more later. The (worst-case) runtime here is Θ(n) forseveral reasons. Firstly, we know that ensureCapacityForAdd is Θ(n) in the worst-case. Secondly, we may have to shift Θ(n) elements when we insert.

2. Here we implement remove:

public void remove(int pos){if (pos < 0 || pos >= size)throw new IndexOutOfBoundsException(

40

Page 41: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

"Index = "+pos+" Size = "+size);for (int i = pos+1; i < size; ++i)data[i−1] = data[i];

data[size−1] = null; //Why?size−−;

}

The runtime is Θ(n) as we may have to shift a large portion of the array (considerremove(0)).

3. First a simple analysis: a single add takes at most Θ(n) operations in the worst-case,where n is the size of the array. Our adds will occur on an array of size 1, then 2, then3, all the way up to size n− 1. Carefully summing we �nd an upper bound of O(n2).

The problem is this analysis is way to pessimistic. We will do linear work very infre-quently (only when the size is 1, 2, 4, 8, . . .). If we only count the amount of copyingdone by the resizing, when the size is 2 we will need to enlarge to an array of size 4.When the size is 4 we will enlarge to 8. More generally, when the size is 2k we willenlarge to 2k+1. For simplicity, assume n = 2m + 1. Then if we add n times we will dothe following work copying from the enlarging process:

2 + 4 + 8 + 16 + · · ·+ 2m+1 = 2m+2 − 2 = Θ(n).

Thus n adds will take Θ(n) work. Said di�erently, each add will take roughly Θ(1)work on average. This is called amortized analysis (not to be confused with averagecase analysis).

4. One way to allow for more types is to make our dynamic array take Objects insteadof Strings. The problem with this is heterogeneity. In other words, if our add methodwas void add(Object value) then we could do the following:

DynamicObjectArray doa = new DynamicObjectArray(10);doa.add("Hello");doa.add(new Random(1));doa.add(new int[4]);

This is perfectly legal, but potentially not what we want. How can we make a generalDynamicArray type that only allows a single type in it? The answer is Java generics,which we will discuss in a moment.

4.6 Exceptions

You have undoubtedly seen exceptions before when running your Java code. Exceptions area way to indicate that something wrong has occurred, and you want your block of code or

41

Page 42: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

function to stop and indicate the error. For example, in our dynamic arrays above, we wantto indicate when the user has passed a bad position as argument to one of our methods.

We won't cover all of Java exception handling here (such as try with resource allocation,or �nally blocks) but we will give the main idea. The �rst thing to know about exceptionsis the throw statement. To throw (cause) an exception to occur you write throw obj; whereobj must extend the class Throwable. The other key construct is a try-catch. It has theform

try {//Code that may throw an object of type//ExceptionType1 or ExceptionType2

}catch (ExceptionType1 e1) {//Handle exception

}catch (ExceptionType2 e2) {//Handle exception

}

If the code in the block after the try keyword throws an exception, then it can be �caught� inone of the catch blocks that follow if the exception matches one of the types listed. Above,if the try block throws an exception object that is a descendent of ExceptionType1 then itwill be handled by the �rst block. If not, but it is a descendent of type ExceptionType2 thenit is handled by the second block. Otherwise, it isn't caught by the try-catch statement.An uncaught exception causes the current function exit. This continues until we hit anenclosing try-catch statement that catches the exception, or we exit from main and outputan error along with the program call stack (i.e., only the names and line numbers of eachfunction called on the stack and not the full activation records). This is possible since whenan exception is thrown it stores a copy of the program's call stack. Below is an example:

Exceptions.java

1 import java.util.Scanner;23 public class Exceptions {4 static int func(int a) {5 if (a > 2)6 throw new ArithmeticException("AE func");7 if (a < 0)8 throw new IndexOutOfBoundsException("IOB func");9 if (a == 0)10 throw new IllegalArgumentException("IAE func");11 return a;12 }13 static int wow(int a) {14 try {15 func(a);

42

Page 43: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

16 } catch(ArithmeticException e)17 {18 System.out.println("Caught in wow: "+e);19 }20 return a;21 }22 public static void main(String[] args) {23 Scanner in = new Scanner(System.in);24 try {25 int a = in.nextInt();26 wow(a);27 } catch (IndexOutOfBoundsException e)28 {29 System.out.println("Caught in main: "+e);30 }31 System.out.println("Exited OK");32 }33 }

On line 25 we get an int from the user, and then on line 26 we call wow with it. Wow callsfunc. If a > 2 then func will throw an ArithmeticException. This causes func to exit towow. Then the try-catch statement in wow catches the exception and prints a statementand then returns normally. If a is 0 then wow will not catch the IllegalArgumentException,and neither will main so Exited OK is never printed. Finally, if a < 0 then func will throwan IndexOutOfBoundsException that will cause func and wow to both exit abruptly, butmain will catch it. Then the function main will exit normally.

The last thing we will discuss about Java exception handling is the type of Throwablesthat exist. Throwable is a class that has two subclasses: Exception and Error. Subclassesof Error are thrown when something truly horrible occurs in the JVM such as running outof memory. You typically never want to catch a subclass of Error. Exception has a subclasscalled RuntimeException that has a special meaning. Errors, RuntimeExceptions and anydescendent of RuntimeException are called unchecked exceptions. Descendents of Exceptionthat aren't descendents of RuntimeException are called checked exceptions. The distinctionis as follows: if a function might exit due to a thrown checked exception, then it must includea throws statement indicating this. For example,

static int func(int a) {if (a < 0) throw new Exception("This is checked");return a;

}

will not compile. To �x this we can either use a try-catch block to catch the checked exceptionor write

static int func(int a) throws Exception {if (a < 0) throw new Exception("This is checked");return a;

43

Page 44: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

}

People di�er on whether they prefer checked or unchecked exceptions. If you want to convertan checked exception e that you have caught into an unchecked exception you do throw new

RuntimeException(e); since RuntimeException has a constructor that will take an Exceptionand wraps it.

As an example of some checked exceptions you may seen in practice, many of the classesin the java.io package throw exceptions that are descendendts of IOException which extendsException. These indicate I/O issues such as failing to �nd the �le you were try to open.

4.7 Generics

Now that we have dealt with exceptions, the other issue to resolve is how to create a generalpurpose homogeneous dynamic array. We will solve this problem using generics. Insteadof giving a comprehensive in-depth discussion of generics, we will give a rough simpli�edoverview, and add a few details later as we need them. More speci�cally, we will focus nowon class generics, and will not use any wildcards or bounds.

Here is a simple example of generics at work:

GenericsTest.java

class TwoThings<S,T>{

private S one;private T two;public TwoThings(S s, T t) {

one = s;two = t;

}public S getOne() { return one; }public T getTwo() { return two; }//static void wow(S a, T b) {} //Doesn't compile//static S wow2; //Doesn't compile

}

class GParent {}class GChild extends GParent {}

public class GenericsTest<T> {private TwoThings<T,String> data;public GenericsTest(T t) {

data = new TwoThings<T,String>(t,"wow");}public TwoThings<T,String> getData() { return data; }public void bad() {

//T hmm = new T(); //Doesn't compile

44

Page 45: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

//T[] arr = new T[10]; //Doesn't compile}public static void main(String[] args){

TwoThings<String,String> t = new TwoThings<>("","");GChild c = new GChild();GenericsTest<GChild> gc = new GenericsTest<GChild>(c);//Generics<GParent> gp = gc; //Doesn't compileGChild[] carr = new GChild[10];GParent[] parr = carr; // Fine

}}

The �rst class we see is TwoThings, and it takes 2 generic type parameters S and T. Ingeneral, if you write

class SomeName<A,B,C> {//stu�

}

then A,B,C are generic type parameters of the class SomeName. When you make an objectof type TwoThings you will write

TwoThings<String,Random> t =new TwoThings<String,Random>("wow",new Random(1));

TwoThings<String,Random> s =new TwoThings<>("wow",new Random(1));

Java allows you to use the shorthand for s above to reduce typing since it can �gure out youmean String and Random. When you use an object of type t or s you can think of all theS types being replaced by String and all of the T types being replaced by Random. This isalmost true as we will see in a moment. An important thing to notice about the TwoThingsclass is that the static method will not compile. When you use generic type parameterson a class they do not apply to any static �elds or methods (i.e., only instance �elds andmethods).

Next we focus on the bad method of GenericsTest. This is one situation where genericclass parameters di�er from what you may expect (and are very di�erent from templatesin C++). The generic types only exist at compile-time. At runtime they are all "erased"and replaced by Objects (almost true; we will see generic bounds later). This is a processcalled type erasure. Due to this, you cannot construct any objects of a generic type, and youcannot construct an array of a generic type. You will see that in all the other examples here,the objects referred to by references of generic type were created elsewhere without generics.

One last comment about generics. Notice at the bottom of main that GenericsTest<GParent>and GenericsTest<GChild> have no inheritance relationship. Just as a reminder the arraytype GParent[] is an ancestor of GChild[]. This may seem obvious, but sometimes fools peoplewhen they can't do ArrayList<GParent> al = new ArrayList<GChild>();.

45

Page 46: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

4.8 Exceptions and Generics Exercises

1. Suppose a block could throw many di�erent types of exceptions. How would you catchall possible (non-Error) exceptions in a single catch block?

2. In the package java.io there is a class FileReader which is used to read characters froma �le. It has a constructor that takes a �lename like new FileReader("data.txt"). If itcannot �nd the �le it will throw a FileNotFoundException (a subclass of IOExceptionwhich extends Exception). Write a main function that constructs a FileReader for the�le data.txt and has no compile-time errors.

3. (a) Explain how to modify DynamicStringArray into a new generic type DynamicAr-ray that can accept arbitrary types.

(b) Will our new generic DynamicArray work with ints?

4.9 Exceptions and Generics SOlutions

1. Catch Exception:

try {//do stu�

} catch (Exception e) { //Catches all non−errors//handle stu�

}

2. Here are two possible solutions:

public static void main(String[] args) throws FileNotFoundException {FileReader r = new FileReader("data.txt");

}

and

public static void main(String[] args) {try {FileReader r = new FileReader("data.txt");

} catch (FileNotFoundException e) {//do something

}}

In either case we could have used IOException or Exception in our throws clause or inour catch block.

3. (a) We have done this below. The most subtle part is that we cannot make an arrayof the generic type, so our underlying array is of Objects. We also added arangeCheck function to avoid code duplication.

46

Page 47: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

DynamicArray.java

import java.util.Arrays;

public class DynamicArray<T> {private Object[] data;private int size;public DynamicArray(int initCap) {

data = new Object[initCap];}private void ensureCapacityForAdd(){

if (size == data.length)data = Arrays.copyOf(data, data.length*2);

}public void add(T s) {

ensureCapacityForAdd();data[size] = s;size++;

}public boolean contains(T s) {

for (int i = 0; i < size; ++i)if (data[i].equals(s)) return true;

return false;}public void add(int pos, T s) {

if (pos < 0 || pos > size)throw new IndexOutOfBoundsException(

"pos = "+pos+" size = "+size);ensureCapacityForAdd();for (int i = size; i >= pos+1; −−i)

data[i] = data[i−1];data[pos] = s;size++;

}private void rangeCheck(int pos){

if (pos < 0 || pos >= size)throw new IndexOutOfBoundsException(

"pos = "+pos+" size = "+size);}public void remove(int pos){

rangeCheck(pos);for (int i = pos+1; i < size; ++i)

data[i−1] = data[i];

47

Page 48: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

data[size] = null; //Why?size−−;

}@SuppressWarnings("unchecked")public T get(int pos){

rangeCheck(pos);return (T)data[pos];

}public void set(int pos, T s){

rangeCheck(pos);data[pos] = s;

}}

As an aside, the reason we do data[size] = null in the remove call is to allow thegarbage collector to work properly. In Java, there is something called a garbagecollector that cleans up all of the objects that have been allocated but aren'tneeded any more. It determines they aren't needed if none of the �live� referencespoint at them. If an object wasn't being used anymore but our DynamicArrayhad a stray reference to it, then it couldn't be cleaned up.

In the get method we have to cast to type T since our array is of type Object[].The compiler will generate a warning message that we have an unchecked cast.Since we don't want to clutter our project with unnecessary warnings, and sincewe are sure our cast is �ne we can suppress the warning message using the Sup-pressWarnings line above the function (called an annotation).

(b) Not directly. Generics only work with reference types. To help alleviate this issueJava has provided wrapper classes that support automatic boxing and unboxing.You may have already noticed that in addition to int, long, double, etc., thereare also Integer, Long, Double, etc. These are called wrapper classes since thecorresponding objects simply store a single int, long, double, etc. They also pro-vide useful methods, static methods, and static constants such as Integer.parseInt,Integer.MAX_VALUE, and many others. The language allows for the followingcode:

Boxing.java

1 public class Boxing2 {3 public static void main(String[] args)4 {5 Integer i = 7;6 Integer i2 = new Integer(7);

48

Page 49: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

7 int x = i;8 int xx = i.intValue();9 double d1 = 7.0*47/9.3;10 Double d = d1;11 double d2 = d;12 System.out.println(i == i2);13 System.out.println(i.intValue() == i2.intValue());14 System.out.println(i.equals(i2));15 }16 }

Lines 5 and 6 do the same thing. We call line 6 automatic boxing. Lines 7 and8 do the same thing. We call line 7 automatic unboxing. Automatic boxing andunboxing are only supported for the special wrapper classes that wrap the primi-tive types. Lines 9-11 show we can do the same thing with doubles and Doubles.Lines 12-14 illustrate something that is often unexpected. Even though boxingand unboxing allows us to go back and forth between primitives and wrapperseasily, we must be a bit careful. On line 12 the references are compared and falseis output. Lines 13 and 14 both return true as expected.

49

Page 50: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

5 Lecture 5: Comparable, Comparator and the Stack

ADT

5.1 Exercises

1. Design a class BrowerHistoryImpl that implements the following interface:

//Implements a history for a web browser that lets you go forward and backward.//You cannot go forward after the last URL, nor can you go backward at the �rst URL.public interface BrowserHistory {

//Adds an URL u after the current one. The new URL becomes both current//and the last URL.void addURL(String u);//Get the current URL. Returns the String "blank" if there are no URLs.String getCurrentURL();//Returns true if there is an URL before the current oneboolean canGoBack();//Returns true if there is an URL after the current oneboolean canGoForward();//Makes the current URL the previous one//Throws a NoSuchElementException if there is no URL before the currentvoid goBack();//Makes the current URL the next one//Throws an NoSuchElementException if there is no URL after the currentvoid goForward();

}

2. This question deals with some Java we haven't covered yet, but you may have seenbefore.

(a) In Java, certain ArrayLists and certain kinds of arrays can be sorted using thestandard library. Which kinds?

(b) (??) Last class we indirectly discussed a sorted dynamic array data structure thattries to optimize searching for elements by using binary search. To facilitate this,we must always keep the underlying array in sorted order. Is it possible make ageneric SortedDynamicArray class?

3. (a) Write a function isBalanced that takes a String s composed of '(' and ')' charactersand returns a boolean saying if the parentheses are balanced. For example:

(()) => true

()(()()) => true

(() => false

())() => false

)( => false

50

Page 51: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

(b) (?) Write a new function isBalanced2 that assumes the String s can use parenthe-ses, brackets or braces: '(', ')', '[' ,']', '{', '}'. The new isBalanced2 should check ifthese are balanced and properly paired. For example:

[()] => true

[(]) => false

[({})[()]] => true

[()(){}{}] => true

()[]{} => true

((()) => false

[} => false

5.2 Solutions

1. Below is our implementation:

BrowserHistoryImpl.java

import java.util.ArrayList;import java.util.NoSuchElementException;

public class BrowserHistoryImpl implements BrowserHistory{

private ArrayList<String> data;private int current;public BrowserHistoryImpl(){

data = new ArrayList<String>();current = −1;

}public void addURL(String url){

while (data.size() > current+1)data.remove(data.size()−1);

data.add(url);++current;

}public String getCurrentURL(){

return current == −1 ? "blank" : data.get(current);}public boolean canGoBack() { return current > 0; }public boolean canGoForward(){

return current+1 < data.size();}

51

Page 52: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

public void goBack(){

if (canGoBack()) current−−;else throw new NoSuchElementException();

}public void goForward(){

if (canGoForward()) current++;else throw new NoSuchElementException();

}}

Here we use the ArrayList class that comes with Java, and we have an integer currentthat points at the current URL.

2. (a) To be able to sort a list of objects, you need to know how to compare eachpair of objects. There are two ways to indicate this in Java: the interfacesComparable<T> and Comparator<T>. You can make your class implement Com-parable when you want your objects to have a default ordering amongst them-selves. For example, you may want to order students by how many credits theyhave. Comparator (in java.util package) is used when you want to order a givengroup of objects, but they either aren't comparable, or aren't comparable in theway you want. By creating a separate class that implements Comparator, you arewriting an external comparison function that can be used by thing like sortingmethods to order your objects. We will see both of these at work below:

Student.java

import java.util.ArrayList;import java.util.Arrays;import java.util.Collections;import java.util.Comparator;

public class Student implements Comparable<Student>{

private String name;private String id;private int credits;public Student(String n, String i){

name = n;id = i;

}public void setCredits(int c) { credits = c; }public String getID() { return id; }

52

Page 53: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

@Overridepublic int compareTo(Student o){

if (credits < o.credits) return −1;if (credits > o.credits) return 1;return 0;//return Integer.compare(credits,o.credits);

}@Overridepublic boolean equals(Object o){

if (o instanceof Student){

Student s = (Student)o;return credits == s.credits;

}return false;

}public String toString() { return name; }public static void main(String[] args){

Student a = new Student("A","A");Student b = new Student("B","B");Student c = new Student("C","C");a.setCredits(30);b.setCredits(20);c.setCredits(20);System.out.println("Using Credits");System.out.println(a.compareTo(b));System.out.println(b.compareTo(a));Student[] arr = {a,b,c};ArrayList<Student> al =

new ArrayList<>(Arrays.asList(a,b,c));Arrays.sort(arr);System.out.println(Arrays.toString(arr));Collections.sort(al);System.out.println(al);System.out.println(Collections.min(al));

System.out.println("Now by ID");Comparator<Student> co =

new StudentIDComparator();Arrays.sort(arr,co);Collections.sort(al,co);System.out.println(Arrays.toString(arr));

53

Page 54: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

System.out.println(al);System.out.println(Collections.min(al,co));

}}

class StudentIDComparator implements Comparator<Student>{

public int compare(Student o1, Student o2) {return o1.getID().compareTo(o2.getID());

}}

A few things to point out above:

i. Student implements the generic type Comparable<Student>. Since interfaceComparable<T> has the abstract method int compareTo(T o) we must over-ride it. We have done this above and have compared the credits of eachStudent. As described in the javadocs for Comparable, we must return anegative number if this object is less than o, a positive number if this objectis greater than o, and 0 if they are considered equal by the ordering. Notethat Integer provides a helper method we could have used that does the samething.

ii. We have included the @Override annotations in the code. This doesn't e�ectthe program at all but has two nice side-e�ects. Firstly, it shows that readerthan we intend to override a method. Secondly, it tells the compiler thatwe intend to override a method, so if we have a typo and aren't actuallyoverriding, the compiler will complain. It is a good habit to always includethe @Override on every method that is overriding an ancestor's method.

iii. We overrided the equals method in Object. It is often a good idea to do this sothat users can check if two objects are considered equal (since == only checksreference equality). It is an especially good idea to override equals when youhave implemented Comparable since users will expected a.compareTo(b)==0

to be the same as a.equals(b). Our equals method above does this. Note thatequals takes an Object as argument. The �rst thing we do is test if the Objectis actually a Student. More precisely,

a instanceof TypeName

is true if and only if a is not null, and the dynamic type (type of the objectreferred to by a) is a descendent of TypeName.

iv. In main we show that arrays and ArrayLists of type Student are now sortable.The last thing we want to do is sort students by their ids. To do this we createa StudentIDComparator.

(b) Here we are going to go a bit deeper into generics. Even though we won't need

54

Page 55: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

this much in class, it will help clear up things you may see in the JavaDocs. If wesimply do something like

public class SortedArrayList<T> {//stu�

}

we cannot be sure that objects of type T are Comparable. To enforce this re-quirement we can do the following:

public class SortedArrayList<T extends Comparable<T>> {//stu�

}

This is called a bounded type parameter, since we are restricting our parametersT to extend T extends Comparable<T>. This is a step in the right direction, butthe above will not work for the following situation.

BoundedGenerics.java

class BParent implements Comparable<BParent> {private int val;public BParent(int v) { val = v; }@Overridepublic int compareTo(BParent p) { return Integer.compare(val, p.val); }

}class BChild extends BParent {

public BChild(int v) { super(v); }}

class SortedArrayList<T extends Comparable<T>> {}class BetterSortedArrayList<T extends Comparable<? super T>> {}

public class BoundedGenerics {public static void main(String[] args) {

SortedArrayList<BParent> good;//SortedArrayList<BChild> al; //Doesn't compileBetterSortedArrayList<BChild> al2;

}}

Here BetterSortedArrayList has a generic parameter T. T must extend a Com-parable, and the type parameter of that Comparable must be an ancestor of T.This is a �ne implementation, but will only work for Comparable objects and notfor objects using a Comparator. For a de�nition that works with both, look atMaxStack and MaxStackImpl2 below.

3. Below is our implementation:

55

Page 56: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

Balanced.java

import java.util.ArrayList;

public class Balanced{

public static boolean isBalanced(String s){

int numOpen = 0;for (int i = 0; i < s.length(); ++i){

char c = s.charAt(i);if (c == '(') numOpen++;else numOpen−−;if (numOpen < 0) return false;

}return numOpen == 0;

}public static boolean isBalanced2(String s){

ArrayList<Character> al = new ArrayList<Character>();String open = "([{", close = ")]}";for (int i = 0; i < s.length(); ++i){

char c = s.charAt(i);if (open.indexOf(c) != −1) al.add(c);else

{if (al.isEmpty()) return false;char d = al.remove(al.size()−1);int j = close.indexOf(c);if (d != open.charAt(j)) return false;

}}return al.size() == 0;

}

public static void main(String[] args){

String[] barr = {"(())","()(()())","(()","())()",")("};

String[] barr2 = {"[()]","[(])","[({})[()]]","[()(){}{}]","()[]{}"};

System.out.println("isBalanced:");for (int i = 0; i < barr.length; ++i)

System.out.println(isBalanced(barr[i]));

56

Page 57: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

System.out.println("isBalanced2:");for (int i = 0; i < barr2.length; ++i)

System.out.println(isBalanced2(barr2[i]));}

}

(a) For isBalanced we simply count how many open parentheses haven't been closedyet. If this number ever becomes negative, or doesn't end at zero we return false.

(b) Here we keep adding the opening parenthesis, bracket, or brace to the back of ourArrayList. Whenever we �nd a closing character we attempt to pair it with thelast unpaired opening character, which we �nd at the back of our ArrayList. Ifno such character exists, or it is the wrong type of character then we return false.When we are done, we return true if and only if the ArrayList is empty.

5.3 ADT: Stack

When writing isBalanced2 above we used an ArrayList but didn't really need all of itsfunctionality. We could have gotten away with only having the following operations:

Stack.java

public interface Stack<T>{

//Adds an element to the Stackpublic void push(T t);//Removes the most recently pushed element that wasn't//already popped//Throws an NoSuchElementException if the stack is emptypublic T pop();//Determines if the stack is emptypublic boolean isEmpty();

}

As we have alluded to in the name above, the above operations constitute a Stack ADT.An ADT (Abstract Data Type) is an abstract de�nition of what behaviors a data structureshould support, without stating how the data structure should be implemented. Here wehave used a Java interface to describe our ADT, but we could have simply used Englishtext. Some ADTs also include runtime requirements of the behaviors in big-Oh or big-Thetaformat. Another example of an ADT is a List:

List.java

public interface List<T>{

//Remove element in position i and shift later elements to �ll gap

57

Page 58: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

void remove(int i);//Adds element in position i and shifts elements in position i and later//to make a gapvoid add(int i, T t);//Retrieves the existing element in position iT get(int i);//Sets the existing element in position ivoid set(int i, T t);//Gets sizeint size();

}

The dynamic arrays we created last time were data structures that implemented the behaviorspeci�ed in the List ADT. Note that a List can be used to implement at Stack. In otherwords, any problem we can solve with a Stack can be solved with a List. This begs thequestion why we need Stacks at all if we can just use Lists. This also touches on thequestion of why ADTs are useful. Here are some important points to consider.

1. Communication: If you tell someone your algorithm/code uses a Stack, then you areconveying some information. The person hearing this then realizes that you only needpushing and popping behavior, and thus better understands your algorithm.

2. Optimization: In some cases, a data stucture with a simpler interface may allow for asimpler or more performant implementation.

3. More generally, in this class we will often look at a particular ADT and then considerdi�erent data structures that implement the ADT. We can then see what the tradeo�sare when using one data structure over another for implementing a given ADT.

5.4 Stack Exercises

1. Create a class that implements the Stack<T> interface using an ArrayList.

2. (?) Consider the IntMaxStack ADT with the following interface:

IntMaxStack.java

public interface IntMaxStack{

//Adds an element to the Stackvoid push(int t);//Removes the most recently pushed element that wasn't already popped//Throws an NoSuchElementException if the stack is emptyint pop();//Gives the maximum value of all values currently on the stack.//Throws an NoSuchElementException if the stack is empty

58

Page 59: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

int getMax();//Returns if emptyboolean isEmpty();

}

(a) Write a class that implements this interface. Give the runtime for each methodon your implementation. [It is possible to get Θ(1) for getMax without sacri�cingmuch performance in the other methods.]

(b) How would you make this interface and implementation generic?

5.5 Stack Solutions

1. Our solution follows:

ArrayListStack.java

import java.util.ArrayList;import java.util.NoSuchElementException;

public class ArrayListStack<T> implements Stack<T>{

private ArrayList<T> data;

public ArrayListStack() { data = new ArrayList<T>(); }@Overridepublic void push(T t) { data.add(t); }@Overridepublic T pop(){

if (data.isEmpty()) throw new NoSuchElementException();int n = data.size()−1;T t = data.get(n);data.remove(n);return t;

}@Overridepublic boolean isEmpty() { return data.isEmpty(); }

}

2. (a) Here is one implementation:

IntMaxStackImpl.java

import java.util.ArrayList;import java.util.NoSuchElementException;

59

Page 60: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

public class IntMaxStackImpl implements IntMaxStack{

private ArrayList<Integer> data;public IntMaxStackImpl() { data = new ArrayList<Integer>(); }@Overridepublic void push(int t) { data.add(t); }@Overridepublic int pop(){

if (data.isEmpty()) throw new NoSuchElementException();int n = data.size()−1;int t = data.get(n);data.remove(n);return t;

}@Overridepublic int getMax(){

if (data.isEmpty()) throw new NoSuchElementException();int max = data.get(0);for (int i = 1; i < data.size(); ++i) max = Math.max(max, data.get(i));return max;

}@Overridepublic boolean isEmpty() {

return data.isEmpty();}

}

Let n denote the current size of the stack. Then the worst-case runtime for popis Θ(1), for getMax is Θ(n) and for push is Θ(n) but has Θ(1) amortized time.Here is a di�erent implementation that uses roughly double the space, but hasimproved runtime for getMax:

IntMaxStackImpl2.java

import java.util.ArrayList;import java.util.NoSuchElementException;

public class IntMaxStackImpl2 implements IntMaxStack{

private ArrayList<Integer> data;private ArrayList<Integer> maxes;public IntMaxStackImpl2() {

data = new ArrayList<Integer>();maxes = new ArrayList<Integer>();

60

Page 61: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

}@Overridepublic void push(int t){

data.add(t);int max = t;if (!maxes.isEmpty())

max = Math.max(max, maxes.get(maxes.size()−1));maxes.add(max);

}@Overridepublic int pop(){

if (data.isEmpty()) throw new NoSuchElementException();int n = data.size()−1;int t = data.get(n);data.remove(n);maxes.remove(n);return t;

}@Overridepublic int getMax(){

if (data.isEmpty()) throw new NoSuchElementException();return maxes.get(data.size()−1);

}@Overridepublic boolean isEmpty() {

return data.isEmpty();}

}

Here we maintain two stacks: a data stack and a max stack. Each element ofthe max stack stores the max of all the data before it. For example, suppose thestack is (bottom-to-top)

1, 5, 2, 3, 2, 6, 4, 9.

Then maxes will have (bottom-to-top)

1, 5, 5, 5, 5, 6, 6, 9.

On each push and pop we can e�ectively update both stacks. The push and popoperations have the same runtimes as above, but getMax is now Θ(1).

(b) Below we have the interface and implementation (we modi�ed IntMaxStackImpl2above).

61

Page 62: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

MaxStack.java

public interface MaxStack<T>{

//Adds an element to the Stackvoid push(T t);//Removes the most recently pushed element that wasn't already popped//Throws an NoSuchElementException if the stack is emptyT pop();//Gives the maximum value of all values currently on the stack//Throws an NoSuchElementException if the stack is emptyT getMax();

}

MaxStackImpl2.java

import java.util.ArrayList;import java.util.Comparator;import java.util.NoSuchElementException;

public class MaxStackImpl2<T> implements MaxStack<T>{

private ArrayList<T> data;private ArrayList<T> maxes;private Comparator<? super T> c;public MaxStackImpl2() {

this(null); //Calls other constructor}public MaxStackImpl2(Comparator<? super T> co){

data = new ArrayList<T>();maxes = new ArrayList<T>();c = co;

}@SuppressWarnings("unchecked")private int compare(T a, T b){

if (c == null){

Comparable<? super T> ca = (Comparable<? super T>)a;return ca.compareTo(b);

}return c.compare(a, b);

}@Overridepublic void push(T t){

62

Page 63: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

data.add(t);T max = t;if (!maxes.isEmpty()){

T prevMax = maxes.get(maxes.size()−1);if (compare(max,prevMax) < 0) max = prevMax;

}maxes.add(max);

}@Overridepublic T pop(){

if (data.isEmpty()) throw new NoSuchElementException();int n = data.size()−1;T t = data.get(n);data.remove(n);maxes.remove(n);return t;

}@Overridepublic T getMax(){

if (data.isEmpty()) throw new NoSuchElementException();return maxes.get(data.size()−1);

}}

Here we properly allow for a Comparator or a Comparable. If no Comparator isprovided and no Comparable is provided then a ClassCastException will occur inour private compare helper method.

63

Page 64: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

6 Lecture 6: Queues, Circular Arrays and Linked Lists

6.1 Exercises

1. Consider the following snippet taken from a dynamic array implementation:

1 public class DynamicArray<T> {2 private Object[] data;3 private int size;4 public DynamicArray() {5 this(10);6 }7 public DynamicArray(int initCap) {8 data = new Object[initCap];9 }10 }

What does line 5 do?

2. Consider the following interface describing a list of tasks that must be done in theorder they were given.

TaskList.java

public interface TaskList<T> {//Adds a task to the listvoid addTask(T task);//Removes the earliest added unremoved task from the list//Throws an NoSuchElementException if the list is emptyT removeNextTask() ;//Returns whether there are any tasks in the listboolean hasTask();

}

(a) Using an ArrayList, give an implementation of this interface. Also give the run-times of your methods.

(b) (??) Can you think of an ArrayList-based implementation that has Θ(1) remove-Task performance without sacri�cing performance elsewhere?

3. (??) You are given a double[] power describing the amount of power your companyuses each hour over some long period of time (many years let's say). The amount ofpower used over k consecutive hours is simply the sum of the power used over eachhour. Implement the function maxPowerUsed below that returns the maximum powerused over any k consecutive hours (start with hours 0 through k-1, then 1 through k,etc.). You may assume power has at least k elements.

64

Page 65: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

static double maxPowerUsed(double[] power, int k)

6.2 Solutions

1. Calls the other constructor with the argument 10. The �rst line of a constructor canoptionally be used to call a super class constructor, or another constructor in the sameclass.

2. (a) Here is an implementation that uses an ArrayList:

ArrayTaskList.java

import java.util.ArrayList;import java.util.NoSuchElementException;

public class ArrayTaskList<T> implements TaskList<T>{

private ArrayList<T> data;

public ArrayTaskList() { data = new ArrayList<>(); }

@Overridepublic void addTask(T task) { data.add(task); }@Overridepublic T removeNextTask(){

if (data.isEmpty()) throw new NoSuchElementException();return data.remove(0);

}@Overridepublic boolean hasTask() { return data.isEmpty(); }

}

Note that removeNextTask must remove the �rst element of the ArrayList, andthus has a worst-case runtime of Θ(n), where n is the number of elements. ad-dTask has Θ(n) worst-case and Θ(1) amortized runtimes. hasTask is Θ(1).

(b) See the circular array below.

3. One way to solve this problem is to directly simulate a sliding window which keepstrack of the current k hours we are considering. Each time we advance the window wemust remove the power for the earliest added hour and add the next hour's power. Todo this we can use a queue (which we will describe in the next section). We keep trackof the sum of the elements in the queue in a separate variable curr.

65

Page 66: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

PowerUsage.java

public class PowerUsage{

public static double maxPowerUsedQueue(double[] power, int k) {CircularQueue<Double> q = new CircularQueue<>(k);double curr = 0;for (int i = 0; i < k; ++i) {

q.enqueue(power[i]);curr += power[i];

}double best = curr;for (int i = k; i < power.length; ++i){

curr −= q.dequeue();curr += power[i];q.enqueue(power[i]);if (curr > best) best = curr;

}return best;

}public static double maxPowerUsedSums(double[] power, int k) {

double curr = 0;for (int i = 0; i < k; ++i) curr += power[i];double best = curr;for (int i = k; i < power.length; ++i){

curr += power[i] − power[i−k];if (curr > best) best = curr;

}return best;

}public static void main(String[] args) {

double[] arr = {1,2,2,2,4,2,1,0,3,3,3};System.out.println(maxPowerUsedQueue(arr,3));System.out.println(maxPowerUsedSums(arr,3));System.out.println(maxPowerUsedQueue(arr,4));System.out.println(maxPowerUsedSums(arr,4));

}}

The enqueue and dequeue are basically equivalent to the addTask and removeNextTaskfrom the previous problem (we will see this in the next section). The runtime willdepend on the implementation of our queue. We will have a Θ(n) runtime, where n isthe number of elements in power if we use a circular array (described in a moment).

66

Page 67: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

It will use Θ(k) additional space.

A slicker implementation is given in the sum-based method above. It uses the fact thatthe sum over a range of values can be written as the di�erence of two partial sums:the sum of all elements in the array up to the end of the range, and a lagged sum ofall the elements up to the index before the beginning of the range. This di�erence willgive the same result as the sum of the queue elements discussed earlier.

6.3 ADT: Queue

The TaskList and the maxPowerUsed function both only require the behavior of the QueueADT shown below as a Java interface:

Queue.java

public interface Queue<T> {//Adds an element to the queuepublic void enqueue(T t);//Removes and returns the earliest enqueued item that//hasn't been dequeued.//Throws an NoSuchElementException if the stack is emptypublic T dequeue();//Shows the next item to be dequeued.//Throws an NoSuchElementException if the stack is emptypublic T peekNext();//Returns if the queue is empty.public boolean isEmpty();

}

It is common to call a Queue a FIFO (�rst in, �rst out) data structure and a Stack aLIFO (last in, �rst out) data structure. Queues are often used as a task list, where youwant to process a sequence of tasks in the order they were added. Another usage is as acommunication pipe: one part of your system generates data messages that need to be sent,the other part wants to receive them in order. Later in the course we will use queues toperform breadth-�rst searches of trees and graphs.

6.4 Data Structure: Circular Array

Unfortunately, our simple ArrayList-based implementation of a queue above is much slowerthan we would like. In particular, dequeue requires Θ(n) time (recall that popping a stackwas a Θ(1) operation). To improve on this we introduce a new data structure: the circulararray.

For simplicity, suppose we have a �xed array (instead of an ArrayList) holding our values.We will implement methods that let us add to the end and remove from the beginning.Removing the �rst element is costly since we have to shift all of the other elements over. Toimprove upon this we will store indices into the array which point at where the data begins

67

Page 68: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

and ends. Then to remove the �rst element we simply update the index which says wherethe �rst element lies. More precisely, suppose we have an array

String[] arr = {"A","B","C","D",null};

This stores 4 values and has one spot free at the end. Let int head = 0 saying that "A" is inthe �rst spot. Let int tail = 4 to denote that 4 is the next free spot we could add into. Toremove the �rst element we simply set head to 1 to indicate that our data begins at index1. If we then set arr[4]="E" we can add the next element. Finally we must update tail tothe next available spot. To do this properly we set tail = (tail+1)%arr.length. The modulooperation means that we wrap around so 4 becomes 0. This is where the term circular comesfrom. Using these ideas we can arrive at an e�cient queue implementation.

6.5 Circular Array Exercises

1. Below we have an un�nished implementation of the Queue interface. Finish all of themethods using the circular array ideas above. It is important to understand how weuse isEmpty.

import java.util.NoSuchElementException;

public class Un�nishedBoundedCircularQueue<T> implements Queue<T>{

private Object[] data;private int head;private int tail;private boolean isEmpty;public Un�nishedBoundedCircularQueue(int cap) {

cap = Math.max(cap, 1);data = new Object[cap];isEmpty = true;

}@Overridepublic boolean isEmpty() { return isEmpty; }public boolean isFull() {

//More code needed}@Overridepublic void enqueue(T t) {

if (isFull()) throw new IllegalStateException("Queue Full");//More code needed

}@Overridepublic T peekNext() {

if (isEmpty()) throw new NoSuchElementException();//More code needed

68

Page 69: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

}@Overridepublic T dequeue() {

T t = peekNext();//More code needed

}}

2. (a) How would you determine the number of elements in a circular array?

(b) How would you implement a method T get(int k) that obtains the kth element ofa circular array?

3. Explain how we can create a circular array-based queue implementation that growswhen it is full as opposed to throwing an exception like above.

6.6 Circular Array Solutions

1. Below we have �nished the bounded circular queue:

import java.util.NoSuchElementException;

public class BoundedCircularQueue<T> implements Queue<T>{

private Object[] data;private int head; //Remove pointprivate int tail; //Next add pointprivate boolean isEmpty;

public BoundedCircularQueue(int cap) {cap = Math.max(cap, 1); //Force cap >= 1data = new Object[cap];isEmpty = true;

}@Overridepublic boolean isEmpty() {

return isEmpty;}public boolean isFull() {

return tail == head && !isEmpty;}@Overridepublic void enqueue(T t) {

if (isFull())throw new IllegalStateException("Queue Full");

data[tail] = t;

69

Page 70: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

tail = (tail + 1) % data.length;isEmpty = false;

}@Override@SuppressWarnings("unchecked")public T peekNext() {

if (isEmpty()) throw new NoSuchElementException();return (T)data[head];

}@Overridepublic T dequeue() {

T t = peekNext();data[head] = null; //Help GChead = (head + 1) % data.length;isEmpty = head == tail;return t;

}}

2. Both have been implemented in the CircularQueue below. For get, note that we mustmod to properly get to the correct index (since we may need to wrap around the end).For size, we must have a special case for when we are full since cap % cap == 0 andwhen the queue is full we will have head == tail.

3. Below we have extended the implementation to grow when it is full and an enqueueoccurs. The main change from the typical growing behavior of an ArrayList is how wecopy all of the elements into the new array.

import java.util.NoSuchElementException;//Has a few ine�ciencies://1) Has to update and use boolean isEmpty//2) ensureCapacityAdd uses an ine�cient copy//3) Could force capacity to be a power of 2 to avoid mods//If you want to see how to resolve these, look at the source code of//java.util.ArrayDequepublic class CircularQueue<T> implements Queue<T> {

private Object[] data;private boolean isEmpty;private int head; //Remove pointprivate int tail; //Next add point

public CircularQueue(int initCap) {initCap = Math.max(initCap, 1); //force initCap >= 1data = new Object[initCap];isEmpty = true;

}

70

Page 71: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

@Overridepublic boolean isEmpty() { return isEmpty; }public boolean isFull() {

return tail == head && !isEmpty;}private void ensureCapacityForAdd(){

if (isFull()){

Object[] newData = new Object[data.length*2];int s = size();//Copy data (could be optimized into 2 loops with no mods)for (int i = 0; i < s; ++i)

newData[i] = data[(head+i)%data.length];data = newData;head = 0;tail = s;

}}@Overridepublic void enqueue(T t) {

ensureCapacityForAdd();data[tail] = t;tail = (tail+1) % data.length;isEmpty = false;

}@Overridepublic T dequeue() {

T t = peekNext();data[head] = null; //Help GChead = (head + 1)%data.length;isEmpty = head==tail;return t;

}@Override@SuppressWarnings("unchecked")public T peekNext() {

if (isEmpty()) throw new NoSuchElementException();return (T)data[head];

}@SuppressWarnings("unchecked")public T get(int k) {

if (k < 0 || k >= size())throw new IndexOutOfBoundsException();

return (T)data[(head + k) % data.length];

71

Page 72: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

}public int size() {

int cap = data.length;if (isFull()) return cap;return (head−tail+cap) % cap;

}}

6.7 Static Inner Classes

Java let's you declare a class from within the body of another class. These classes are calledinner classes. Here we will focus on static inner classes. You declare a static inner class asfollows:

public class Outer {public static class PublicInner {}private static class PrivateInner {}

}

Here Outer has two static inner classes de�ned in it. A class that isn't declared within anyother class is called a top-level class (like Outer above). Static inner classes are just likeregular classes with a few modi�cations:

1. You can access them using the names Outer.Inner. Above the two classes are Outer.PublicInnerand Outer.PrivateInner. If Outer has generic type parameters, you should not includethose. For example,

public class GOuter<T> {public static class GInner<S> {}

}

you would write GOuter.GInner<String>. This is because the outer type parameter Tdoes not have anything to do with the static aspects of the class GOuter, and thus hasno e�ect on GInner.

2. The access parameters on the classes decide who has access to them, and this behavesjust like members of a class. For instance, anyone can access PublicInner above butonly code within Outer can access PrivateInner.

3. We need to slightly review the meaning of private now that we have static inner classes.If something is declared private anywhere within a top-level class C (including in anyinner class within C), then it is accessible anywhere within C (including in any innerclass within C). This is examined in the following example.

72

Page 73: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

InnerClasses.java

public class InnerClasses{

private int x;private static class Priv {

private int a;void wow(InnerClasses i) { a = i.x; }

}public static class Pub {

private int b;void wow(InnerClasses i) { b = i.x; }

}public static void main(String[] args){

InnerClasses i = new InnerClasses();Priv pr1 = new Priv();InnerClasses.Priv pr2 = new InnerClasses.Priv(); //Same as abovePub pu1 = new Pub();InnerClasses.Pub pu2 = new InnerClasses.Pub(); //Same as abovepr1.wow(i);System.out.println(pr1.a);pu1.wow(i);System.out.println(pu1.b);

}}

Below is an example using static inner classes.

6.8 Data Structure: Singly Linked List

A singly linked list is composed of Nodes. Here is a typical node class:

class Node<S> {private Node<S> next;private S value;public Node(Node<S> n, S v) {next = n;value = v;

}public S getValue() { return value; }public Node<S> getNext() { return next; }

}

Each node in a singly linked list points at the next node, or null to signify the end ofthe list. As you can see above, this is implemented by the Node reference next that eachnode possesses. Our singly linked list data structure will manage this list of nodes, along

73

Page 74: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

with a pointer to the �rst and last nodes of the list. In the following exercises you willimplement each of the standard linked list operations. In general, when trying to �gure outthe implementations below, it is a good idea to draw a picture of the nodes and the links.Furthermore, make sure to give special consideration to the case when the list is empty.

6.9 Singly Linked List Exercises

1. Below we have a skeleton for a singly linked list class. Fill in the missing methods.What are the runtimes of your implementations?

public class Un�nishedSinglyLinkedList<T> {//Static inner classpublic static class Node<S> {

private Node<S> next;private S value;

public Node(Node<S> n, S v) {next = n;value = v;

}public S getValue() { return value; }public Node<S> getNext() { return next; }

}

private Node<T> �rst; //Sometimes called headprivate Node<T> last; //Sometimes called tail

public Un�nishedSinglyLinkedList() {}public boolean isEmpty() { return �rst == null; }public Node<T> getFirst() { return �rst; }public Node<T> getLast() { return last; }public Node<T> addLast(T t) {/*Fill in*/}public Node<T> addFirst(T t) {/*Fill in*/}public T removeFirst() {/*Fill in*/}public Node<T> get(int i) {/*Fill in*/}public int size() {/*Fill in*/}

}

2. (?) Design methods to add and remove a node from anywhere in the singly linked list.You can take whatever argument you like, but your method must have Θ(1) runtime.

3. Using a singly linked list, discuss how you would:

(a) Implement a Queue ADT. (Just explain how enqueue and dequeue will work.)

74

Page 75: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

(b) Implement a Stack ADT. (Just explain how push and pop will work.)

Give the runtimes of your methods.

6.10 Singly Linked List Solutions

1. Here is our implementation of a singly linked list.

SinglyLinkedList.java

import java.util.NoSuchElementException;

public class SinglyLinkedList<T> {//Static inner classpublic static class Node<S> {

private Node<S> next;private S value;public Node(Node<S> n, S v) {

next = n;value = v;

}public S getValue() { return value; }public Node<S> getNext() { return next; }

}

private Node<T> �rst; //Sometimes called headprivate Node<T> last; //Sometimes called tailprivate int size;

public SinglyLinkedList() {}public boolean isEmpty() { return �rst == null; }public int size() { return size; }public Node<T> getFirst() { return �rst; }public Node<T> getLast() { return last; }private Node<T> addToEmpty(T t) {

Node<T> node = new Node<T>(null,t);�rst = node;last = node;size++;return node;

}public Node<T> addLast(T t) {

if (isEmpty()) return addToEmpty(t);Node<T> node = new Node<T>(null, t);last.next = node;last = node;size++;

75

Page 76: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

return node;}public Node<T> addFirst(T t) {

if (isEmpty()) return addToEmpty(t);Node<T> node = new Node<T>(�rst,t);�rst = node;size++;return node;

}public T removeFirst() {

if (isEmpty()) throw new NoSuchElementException();Node<T> node = �rst;if (�rst == last) last = null; //Case when size = 1�rst = �rst.next;size−−;node.next = null;return node.value;

}public Node<T> getNode(int i) {

if (i < 0 || i >= size())throw new IndexOutOfBoundsException();

Node<T> curr = �rst;for (int j = 0; j < i; ++j) curr = curr.next;return curr;

}public Node<T> addAfter(Node<T> listNode, T v){

boolean updateLast = listNode == last;Node<T> newNode = new Node<T>(listNode.next,v);listNode.next = newNode;if (updateLast) last = newNode;size++;return newNode;

}public T removeAfter(Node<T> listNode){

if (listNode.next == null)throw new NoSuchElementException();

Node<T> node = listNode.next;boolean updateLast = node == last;listNode.next = node.next;if (updateLast) last = listNode;size−−;node.next = null;return node.value;

76

Page 77: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

}}

All methods above have Θ(1) runtime except get, which has a Θ(n) runtime (where nis the size of the list).

2. Above we have also implemented the required methods for this question. Note thatwe have called them addAfter and removeAfter. In a singly linked list, if we want toinsert or remove a node somewhere in the middle of the list we must supply the nodebefore it. Otherwise we will not have access to the required next pointer. In bothcases the runtime is Θ(1).

3. Below are our implementations of a Queue and Stack using a linked list. For complete-ness we have included the Stack interface from last lecture.

Stack.java

public interface Stack<T>{

//Adds an element to the Stackpublic void push(T t);//Removes the most recently pushed element that wasn't//already popped//Throws an NoSuchElementException if the stack is emptypublic T pop();//Determines if the stack is emptypublic boolean isEmpty();

}

SListStack.java

public class SListStack<T> implements Stack<T>{

private SinglyLinkedList<T> data;

public SListStack() {data = new SinglyLinkedList<>();

}

@Overridepublic void push(T t) { data.addFirst(t); }@Overridepublic T pop() { return data.removeFirst(); }@Overridepublic boolean isEmpty() { return data.isEmpty(); }

}

77

Page 78: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

SListQueue.java

public class SListQueue<T> implements Queue<T>{

private SinglyLinkedList<T> data;

public SListQueue() {data = new SinglyLinkedList<>();

}@Overridepublic void enqueue(T t) { data.addLast(t); }@Overridepublic T dequeue() { return data.removeFirst(); }@Overridepublic T peekNext() { return data.getFirst().getValue(); }@Overridepublic boolean isEmpty() { return data.isEmpty(); }

}

All of the runtimes above are Θ(1). The major idea above is that singly linked listsprovide easy removals from the �rst element, but not from the last. Thus the di�erencebetween our stack and queue implementations is only where we decide to add the newnodes (front for stack, back for queue).

78

Page 79: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

7 Lecture 7: Doubly Linked Lists and Unit Testing

7.1 Exercises

Here we have included the SinglyLinkedList from last time to help you with the fol-lowing questions.

Lecture6/SinglyLinkedList.java

import java.util.NoSuchElementException;

public class SinglyLinkedList<T> {//Static inner classpublic static class Node<S> {

private Node<S> next;private S value;public Node(Node<S> n, S v) {

next = n;value = v;

}public S getValue() { return value; }public Node<S> getNext() { return next; }

}

private Node<T> �rst; //Sometimes called headprivate Node<T> last; //Sometimes called tailprivate int size;

public SinglyLinkedList() {}public boolean isEmpty() { return �rst == null; }public int size() { return size; }public Node<T> getFirst() { return �rst; }public Node<T> getLast() { return last; }private Node<T> addToEmpty(T t) {

Node<T> node = new Node<T>(null,t);�rst = node;last = node;size++;return node;

}public Node<T> addLast(T t) {

if (isEmpty()) return addToEmpty(t);Node<T> node = new Node<T>(null, t);last.next = node;last = node;size++;return node;

79

Page 80: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

}public Node<T> addFirst(T t) {

if (isEmpty()) return addToEmpty(t);Node<T> node = new Node<T>(�rst,t);�rst = node;size++;return node;

}public T removeFirst() {

if (isEmpty()) throw new NoSuchElementException();Node<T> node = �rst;if (�rst == last) last = null; //Case when size = 1�rst = �rst.next;size−−;node.next = null;return node.value;

}public Node<T> getNode(int i) {

if (i < 0 || i >= size())throw new IndexOutOfBoundsException();

Node<T> curr = �rst;for (int j = 0; j < i; ++j) curr = curr.next;return curr;

}public Node<T> addAfter(Node<T> listNode, T v){

boolean updateLast = listNode == last;Node<T> newNode = new Node<T>(listNode.next,v);listNode.next = newNode;if (updateLast) last = newNode;size++;return newNode;

}public T removeAfter(Node<T> listNode){

if (listNode.next == null)throw new NoSuchElementException();

Node<T> node = listNode.next;boolean updateLast = node == last;listNode.next = node.next;if (updateLast) last = listNode;size−−;node.next = null;return node.value;

}

80

Page 81: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

}

1. Write code that will print out each element of a SinglyLinkedList.

2. (?) Implement an equals method for SinglyLinkedList. It should return true if the listsstore the same values in the same order. Values should be compared using equals (i.e.,not ==).

3. (??) Famous interview problem: Write a reverse method for the above SinglyLinkedListthat runs in Θ(n) time and Θ(1) memory (not including the already existing list nodes).

7.2 Solutions

1. We �rst consider the following code:

public static void print(SinglyLinkedList<?> sll) {for (int i = 0; i < sll.size(); ++i)System.out.println(sll.getNode(i).getValue());

}

The <?> means we have a generic type parameter but don't know what it is. Theproblem with the above method is its runtime is Θ(n2). Instead we do the following:

public static void print(SinglyLinkedList<?> sll) {SinglyLinkedList.Node<?> curr = sll.getFirst();while (curr != null) {System.out.println(curr);curr = curr.next;

}}

2. Below we have an implementation of equals:

public boolean equals(Object o){if (o instanceof SinglyLinkedList<?>){SinglyLinkedList<?> sll = (SinglyLinkedList<?>)o;if (sll.size() != size) return false;Node<T> curr = �rst;Node<?> ocurr = sll.�rst;while (curr != null){if (curr.value == null){if (ocurr.value != null) return false;

}

81

Page 82: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

else

{if (!curr.value.equals(ocurr.value)) return false;

}curr = curr.next;ocurr = ocurr.next;

}return true;

}else return false;

}

Above we cannot say o instanceof SinglyLinkedList<T> since the T doesn't exist at run-time. In other words, there is no way to determine at runtime if the other SinglyLinkedListhas the same generic type parameter as the current one. If there is a type incompati-bility, the equals method of curr.value will catch it. Note that we properly handle thecase that the list contains some nulls that the user entered intentionally.

3. To solve this in one pass over the list, we need to take each node and point it back at thenode before it. Since we don't have pointers backward we always maintain referencesto the current node and the one before it as we iterate (a technique sometimes calledpiggybacked pointers). Each step we �x our references and advance both pointers.Draw a picture here if you want to better sort out the logic. Code follows:

public void reverse(){last = �rst;Node<T> beforeCurr = null;Node<T> curr = �rst;while (curr != null){Node<T> aftCurr = curr.next;curr.next = beforeCurr;beforeCurr = curr;curr = aftCurr;

}�rst = beforeCurr;

}

7.3 Doubly Linked Lists

Here are some of the limitations of singly linked lists:

1. Getting the kth element requires iterating over the whole list (Θ(n) worst-case).

82

Page 83: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

2. Cannot remove the last element e�ciently.

3. Cannot iterate through the list in reverse order.

4. Cannot delete a node e�ciently without a reference to the node before it.

By paying the price of an extra reference per node (that we must store and update) we canimprove on all of these (for getting the kth element, we will only gain a factor of 2 in theworst-case). The idea is to store a prev reference in each node that points to the previousnode in the list. This gives us more �exibility when moving around the list.

7.3.1 Sentinels

We could proceed to design a doubly linked list that mimics our singly linked list: maintainhead and tail references, have ends pointing at null, etc. Instead we will use a trick thatwill greatly simplify our code at the price of a single node. Our simpler code will also havefar fewer if-statements which can lead to performance gains. The trick is to create a singledummy node called a sentinel. Even when our doubly linked list is empty it will still containthe sentinel node. The sentinel node has the following properties:

1. When the list is empty the following is true:

sentinel == sentinel.next && sentinel == sentinel.prev

2. Suppose the list is not empty. Let �rstNode denote the �rst node and lastNode thelast. Then the following are all true:

sentinel == �rstNode.prevsentinel.next == �rstNodesentinel == lastNode.nextsentinel.prev == lastNode

By building the sentinel node into our doubly linked list we will never have to deal withnull references and pesky NullPointerExceptions. Furthermore, we will not have to storeand maintain references to the �rst and last node since they can be found quickly throughthe sentinel. Recall that some of the trickiest code in the singly linked list was correctlyupdating the �rst and last references on each operation.

7.4 Doubly Linked List Exercises

1. Suppose a doubly linked list (as described above) is empty and we add a node withvalue 1. Explain how this is done.

2. Suppose a doubly linked list (as described above) has the values 1, 2, 3, 4 in that order.

83

Page 84: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

(a) Explain what happens when we delete the node with value 2.

(b) Explain what happens when we append a 5 to the end of the list.

3. Suppose a doubly linked list is sorted. How long does it take to �nd a value v in thelist?

4. (? ? ?) Suppose you have a list of 1000000 numbers that you want to iterate over. Willit be faster if we used a dynamic array or a doubly linked list?

7.5 Doubly Linked List Solutions

1. A new node is created containing the value 1 with both its prev and next referencespointed at the sentinel. Both of the prev and next references of the sentinel will pointat the new node.

2. (a) The next pointer of the node with value 1 will be repointed at the node with value3. The prev pointer of the node with value 3 will be repointed at the node withvalue 1.

(b) A new node with value 5 is created with next reference pointing at the sentinel,and previous reference pointing at the node with value 4. The prev pointer of thesentinel is repointed at the new node. The next pointer of the node with value 4is repointed at the new node.

3. The worst case is still Θ(n). Just getting access to any node/value in the middle ofthe list requires Θ(n) work.

4. The ArrayList should be measurably faster. There are several reasons for this which wewill quickly mention below. The basic idea is that memory accesses are more expensivethan CPU operations. Understanding how much data must be loaded from memorycan sometimes be the most important factor when optimizing a program. The termsused below may be unfamiliar to you, but I will discuss them in class and you can lookthem up online if you want to learn more.

(a) The ArrayList is stored in contiguous memory (i.e., consecutive addresses in mem-ory), so each cache line pulled from memory will contain the data you need toaccess. Furthermore, the cache prefetching done by the memory system will cor-rectly load the next data you will be accessing.

(b) The doubly linked list node has 2 references and some object overhead for eachvalue stored. Thus a node object may be more than 3 times larger than the spaceused for the value reference. Since iterating over the linked list will need to pullat least 3 times more data from memory, it will run more slowly.

This also addresses the following related question: �If the RAM (i.e., memory) on mymachine is so large, why should I care about saving space?� There is some truth to this

84

Page 85: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

thought. Programmers these days de�nitely spend less time optimizing their memoryusage than before, when memory was scarce. That said, even though RAM is huge,caches are much smaller. For example, one of the biggest factors that separates a goodlinear algebra library from a bad one is how it optimizes cache utilization.

7.6 Lab 3 Preparation: JUnit and import static

In today's lab we will be writing tests for a simple set data structure. To do this wewill be using the Java library JUnit. A typical work �ow for JUnit is to �rst create aclass named ClassNameTests that will contain the tests for your class ClassName. Eachmethod in ClassNameTests will verify that a particular assumption is true of ClassName.JUnit requires all tests to be instance methods (i.e., not static). Furthermore, each of yourtest methods will run in a freshly constructed object, so you can do common setup in theconstructor (there is another way, but we won't discuss it here). It is convention to start eachtest method with the word test. For example, testConstructor or testAddSingleElement. Allof your test methods must be annotated with the annotation @Test. This is how you tellthe JUnit library that this is a test method, and that it should be used when running yourtests. To use @Test we must include import org.junit.Test; in our import statements.

Within your tests you will want to verify that certain assumptions hold true. To dothis we will use the assert statements that come with JUnit. For example, suppose youare testing the constructor of your data structure, and you want to verify that a newlyconstructed object has size 0. You could write the following test:

1 @Test2 public void testConstructor() {3 BoundedIntSet bis = new BoundedIntSet();4 Assert.assertEquals("Size of newly constructed set",0,bis.size());5 Assert.assertEquals(0,bis.size()); //Same as above without message6 Assert.assertFalse("0 in newly constructed set",bis.contains(0));7 Assert.assertFalse(bis.contains(0)); //Same as above without message8 }

where Assert is a class in the package org.junit. Since all of these helpful assert methods arestatic methods of class Assert, we will have to type Assert.whatever many times. To ease thisburden we can use import static. By typing

import static org.junit.Assert.*;

Java will let us just write assertEquals(...) instead of Assert.assertEquals(...). Another examplewhere this could be useful is if you use many of the static functions from the Math class inyour �le. Then you could put

import static java.lang.Math.*;

and write abs(x) instead of Math.abs(x).Sometimes your test will want to verify that a particular type of exception is thrown.

This can be done with a try-catch and asserts. A shorter method is to use the followingsyntax:

85

Page 86: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

@Test(expected=IndexOutOfBoundsException.class)public void testAddNegativeException() {set.add(−1);

}

Above set.add(−1) is supposed to throw an IndexOutOfBoundsException and the test abovewill pass if and only if it does.

86

Page 87: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

8 Lecture 8: Iterators, Maps, and Hashtables

8.1 Exercises

1. Show how to print out the elements of a doubly linked list in reverse order.

2. What are the Java API classes that correspond to dynamic arrays, doubly linked lists,and circular arrays?

3. Solve the following:

(a) Give an operation where a circular array clearly outperforms an ordinary dynamicarray.

(b) Give an operation where a dynamic array clearly outperforms a doubly linked list.

(c) Give an operation where a doubly linked list clearly outperforms both a circulararray and an ordinary dynamic array.

(d) Give an operation that a doubly linked list has that a singly linked list does not.

4. (???) Famous Interview Problem: Suppose you have a head reference to a singly linkedlist whose last node is pointed at one of the earlier nodes in the list (instead of at null).You have no direct access to the last node of the list. Determine which node the lastnode is pointing at with Θ(1) memory and Θ(n) runtime. [Hint: First try to �nd somenode of the list that is in the loop.]

5. (?) Give an example of an operation you can perform on our doubly linked list (fromthe homework) that could corrupt the list. How could you �x this? [Hint: Suppose wehave two lists.]

8.2 Solutions

1. Using the DoublyLinkedList class from Homework 3 we could use the following code:

public static void printReverse(DoublyLinkedList<?> dll) {for (DoublyLinkedList.Node<?> curr = dll.getLast();

curr != dll.getSentinel();curr = curr.getPrev()) {

System.out.println(curr.getValue());}

}

The Java API also has a doubly linked list called LinkedList, and we will show how toiterate through it in a moment.

2. ArrayLists are dynamic arrays, LinkedLists are doubly linked lists, and ArrayDequesare circular arrays.

87

Page 88: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

3. (a) Removing the �rst element. Adding the �rst element is also usually faster.

(b) Getting the ith element.

(c) Deleting a node (somewhere in the middle of the list) that you have a referenceto. Inserting a node before or after a node in the middle of the list that you havea reference to.

(d) Remove last element. Add before a node you have a reference to (singly linkedlist only allows add after). Delete a node you have a reference to (singly linkedlist requires a reference to the node before you must delete).

4. We will break the answer into 3 parts:

(a) Find a node in the loop: To do this we employ the Tortoise and the Hare strategy.Make 2 node references pointed at the �rst node of the list called Tortoise andHare. Each iteration we will advance the Tortoise by 2 steps and the Hare by 1step. When the Tortoise and the Hare cross paths again, they will both be in theloop.

(b) Find the length of the loop: Once the Tortoise and the Hare meet at some node,stop the Hare and let the Tortoise keep stepping until it rejoins the Hare. Counthow many steps this takes.

(c) Find the node the last node points at: Start two new Tortoises at the beginningof the list. Give one Tortoise a head start that is the length of the loop. Thenadvance each Tortoise one step at a time. When they meet they will be at theanswer.

5. Suppose we get a node from one list and then use addAfter, addBefore, or removeNodewith it, but on the other list. This will break the size �eld, and can corrupt the listfurther. To protect against this we could store a reference to the DoublyLinkedList inthe Node object. If someone tries to use a node on the wrong list, we can check thisin the method and throw an exception. We will handle this in a di�erent way in amoment using iterators.

8.3 Iterators and For Each

The code we have used thus far to iterate over dynamic arrays and linked lists have beenvery di�erent. For linked lists we must obtain a node and keep getting the next elementuntil we hit the end (sentinel in the doubly linked case, null in the singly linked case). Fordynamic arrays we can simply loop over the indices. Furthermore, we must have access tothe Node to perform the Θ(1) mid-list insertions and removals. As we saw above, this caneither lead to corrupt lists, or force us to store extra information in each Node. In order tomake collection access more uniform and safer (by restricting access to the nodes), we willuse iterators.

An iterator is an object that acts like a fancy reference to your list. Below are the essentialparts of the Iterator interface in Java:

88

Page 89: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

public interface Iterator<T> {//Returns the next element or throws NoSuchElementExceptionT next();//Returns if calling next will return an elementboolean hasNext();//If supported, removes the last element returned by nextdefault void remove() {throw new UnsupportedOperationException("remove");

}}

There is also an Iterable interface that many collections support. Here is the important partof Iterable:

public interface Iterable<T> {Iterator<T> iterator();

}

Thus any collection (such as ArrayList, LinkedList, ArrayDeque) that implements Iterablewill allow the following code:

for (Iterator<T> it = col.iterator(); it.hasNext();) {T t = it.next();//do stu�

}

To make the above code even cleaner looking, Java supports the for-each loop:

for (T t : col) {//do stu�

}

For convenience, you can also use an array of type T[] in the for-each loop above (or adescendent of T[]) instead of a collection implementing Iterable.

This for-each notation is nice, but doesn't give you access to the iterator in case youwanted to remove a value. Lists in the Java API also have another type of iterator called aListIterator that will let you go backwards, go forwards, add and set elements, along withknowing your current index in the list. To learn more you can lookup java.util.ListIterator.Below we show a few ways to implement the reverse printing method we had earlier:

ReversePrinting.java

import java.util.Iterator;import java.util.LinkedList;import java.util.ListIterator;

public class ReversePrinting{

static void printForwards(LinkedList<Integer> list){

89

Page 90: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

for (Iterator<Integer> it = list.iterator(); it.hasNext();)System.out.print(it.next()+" ");

System.out.println();}static void printForwards2(LinkedList<Integer> list){

for (int i : list)System.out.print(i+" ");

System.out.println();}static void printForwards3(LinkedList<Integer> list){

for (ListIterator<Integer> it = list.listIterator(); it.hasNext();)System.out.print(it.next()+" ");

System.out.println();}static void printReverse(LinkedList<Integer> list){

//Descending iterator gets an Iterator that starts at the end and goes backwardsfor (Iterator<Integer> it = list.descendingIterator(); it.hasNext();)

System.out.print(it.next()+" ");System.out.println();

}static void printReverse2(LinkedList<Integer> list){

for (ListIterator<Integer> it = list.listIterator(list.size());it.hasPrevious();)

System.out.print(it.previous()+" ");System.out.println();

}public static void main(String[] args){

LinkedList<Integer> list = new LinkedList<>();for (int i = 0; i < 10; ++i) list.add(i);printForwards(list);printForwards2(list);printForwards3(list);printReverse(list);printReverse2(list);

}}

Note that we construct the ListIterator at position list.size() in printReverse2. This refersto the position just after the last entry (so calling previous gets the last entry). Look at theJava API documentation for ListIterator to learn more about the indices.

90

Page 91: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

8.4 Iterator Exercises

1. Suppose you are using an iterator to process a list, and then mid-iteration you modifythe list:

for (Iterator<T> it = list.iterator(); it.hasNext();) {T t = it.next();//Modify list not using the iterator//Like list.remove or list.addit.remove(); //Removes last element returned by next from list

}

What could go wrong?

2. Write an iterator for a doubly linked list.

3. Explain how you would use an array to implement the following interface:

FloorInfo.java

//Gives the company on each �oor of a commercial building//It's assumed that �oor numbers begin at 0 and have very few gaps.public interface FloorInfo{

//Assigns a company to a given �oor.//If the �oor already has a company, replace it.void assignFloor(int �oor, String company);//Get the company assigned to the given �oor.//Returns null if there is no company assignedString getCompany(int �oor);

}

You may assume the constructor will take the maximum number of �oors in the build-ing.

4. Explain how you would use an ArrayList to implement the following interface:

Balances.java

//Stores balances associated with each customerpublic interface Balances {

//Associates a balance with a given name//If the name already has a balance, replace it//with the current argument.void put(String name, Double balance);//Gets the balance for the given name. Returns null//if the name isn't stored.Double getBalance(String name);

}

What are the runtimes of your methods?

91

Page 92: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

8.5 Iterator Solutions

1. Since the remove method on an iterator tries to remove the last value it returned,that value may not exist any more, or other aspects of the list could have changed.We say that these list operations invalidated the iterator. To detect this iterators andcollections maintain an integer, the modi�cation count. If the list is modi�ed throughthe iterator, the modi�cation count is incremented on both the list and the iterator. Ifthe list is changed without the iterator, only the list's modi�cation count is changed.If you try to use the iterator and the modi�cation counts don't match, you get aConcurentModi�cationException.

2. Below we give code that would sit inside of the DoublyLinkedList class. We have leftout modCounts for simplicity. We could have used non-static inner classes below, butwe haven't covered that.

public class DoublyLinkedList<T> implements Iterable<T> {private static class Iter<S> implements Iterator<S>{private DoublyLinkedList<S> list;private Node<S> node;private Node<S> lastNext;private Iter(DoublyLinkedList<S> dll){list = dll;node = list.getFirst();

}@Overridepublic boolean hasNext() { return node.next != list.sentinel; }@Overridepublic S next(){if (!hasNext()) throw new NoSuchElementException();lastNext = node;node = node.next;return lastNext.value;

}@Overridepublic void remove(){if (lastNext == null) throw new IllegalStateException();list.removeNode(lastNext);lastNext = null;

}}

public Iterator<T> iterator() { return new Iter<T>(this); }

92

Page 93: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

// Other stu�}

3. Our solution is below:

BoundedFloorInfo.java

public class BoundedFloorInfo implements FloorInfo{

private String[] companies;public BoundedFloorInfo(int maxFloor){

companies = new String[maxFloor+1];}@Overridepublic void assignFloor(int �oor, String company){

companies[�oor] = company;}@Overridepublic String getCompany(int �oor) { return companies[�oor]; }

}

4. Our solution is below:

BalancesImpl.java

import java.util.ArrayList;

public class BalancesImpl implements Balances{

private static class Entry{

private String name;private Double balance;private Entry(String n, Double b) {

name = n;balance = b;

}}

private ArrayList<Entry> data;

public BalancesImpl() { data = new ArrayList<>(); }

93

Page 94: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

// Finds an entry with the given name, or returns null otherwiseprivate Entry getEntry(String name){

for (int i = 0; i < data.size(); ++i){

Entry e = data.get(i);if (e.name.equals(name)) return e;

}return null;

}

@Overridepublic void put(String name, Double balance){

Entry e = getEntry(name);if (e != null){

e.balance = balance;return;

}data.add(new Entry(name, balance));

}

@Overridepublic Double getBalance(String name){

Entry e = getEntry(name);if (e != null) return e.balance;return null;

}

}

Both put and getBalance both use the helper method getEntry which has a Θ(n)runtime.

8.6 Maps and HashMaps

Both FloorInfo and Balances have the idea of mapping some piece of information (�oornumber or name) to another (company name or balance). We can generalize this idea usingthe Map (or Dictionary) ADT:

Map.java

//Stores a mapping of keys to valuespublic interface Map<K,V> {

94

Page 95: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

//Adds key to the map with the associated value. If key already//exists, the associated value is replaced.void put(K key, V value);//Gets the value for the given key, or null if it isn't found.V get(K key);//Returns true if the key is in the map, or false otherwise.boolean containsKey(K key);//Removes the key from the mapvoid remove(K key);//Number of keys in the mapint size();

}

Above we saw that FloorInfo has an e�cient implementation using an array/ArrayList, butBalances does not. This is because the keys of FloorInfo directly map to the indices of ourarray. What we want is a way to have the �exibility of a Map, but the e�ciency of an array.Is there a common ground?

This idea leads us to the hashtable data structure (called a HashMap in Java). In ahashtable we apply a function, called a hash function, that will map each key object to anindex of our array. We call each entry of our array a bucket. Suppose we plan on adding1000 keys (with associated values) to our hashtable. An ideal situation would be to have anarray of 1000 buckets, and then an e�cient function h that for each of these keys gives adistinct array index between 0 and 999, inclusive. This is called a perfect hash (actually aminimal perfect hash). For example, h("Bob")=0, h("Frank")=1, etc. While this can be doneto some extent if you know all of the keys ahead of time, in general this isn't possible to doe�ciently. Thus we must lower some of our expectations:

1. Our array will be larger than the full number of keys. For example, Java's defaultHashMap will never be more than 75% used. The number of keys stored divided bythe number of buckets is called the load factor of the table.

2. We will allow our hash function to map distinct keys to the same value (this is calleda collision). Hopefully the keys are well spread out amongst the available values.

One requirement we must have is that if key1.equals(key2) then h(key1)==h(key2). We willdiscuss how to get a hash function h a bit later. For now, suppose that we have some hashfunction h that maps our keys to the range 0, . . . , N − 1 where N is the size of our array.The issue we must deal with is how to handle collisions.

8.6.1 Collision Handling Using Chaining

With chaining, each element of our table points to a linked list of nodes. Each node storesthe key-value pair. Whenever we need to add a new key k to the table we simply add a newkey-value pair to the head of the list pointed at from index h(k). We choose the head sinceclients will often access recently added keys, so placing them at the head will lead to quickersearches.

95

Page 96: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

8.6.2 Collision Handling Using Probing

Here we will discuss a method of collision handling confusingly called open addressing andcalled closed hashing. Such a hashtable (if implemented in Java) will maintain two arrays:an array of key references, and an array of value references. Suppose we try to add anelement to the table with key k. As with all hashtables, we �rst compute h(k). If h(k) isalready occupied in the key array, we will look at the entry (h(k) + 1)%N where N is thetable size. If this is occupied we continue to (h(k) + 2)%N and so forth. Eventually we will�nd an open slot (since we always keep the table only partially full), and put both the keyand the value. The hope is that if we maintain a fairly empty table (like half full) then wewont have to look very far. To determine if a key is already in the table we start at indexh(k) and must keep adding 1 until we �nd the key in the table, or �nd an empty cell.

The big advantage of probing over chaining is that we access memory in a more e�cientpattern since we use contiguous arrays instead of scattered linked lists. The negative is thatthe collision handling code is more complex, and will often need to look at more nodes thanwith chaining. To help address this there are other methods of probing that can lead tobene�ts, but we wont discuss them here (e.g., quadratic probing, double hashing, cuckoohashing, hopscotch hashing).

8.7 Hashtable Exercises

1. Suppose we have a hashtable with 10 buckets. Suppose our keys are non-negativeintegers, our values are Strings, and our hash function h simply takes the last digit ofthe number. We add the following key-value pairs, in the order given, to our dictionary

(1,"A"), (2,"B"), (11,"C"), (32,"D"), (14,"E"), (99,"A")

(a) Explain how the �nal hashtable will look if chaining is used.

(b) Explain how the �nal hashtable will look if linear probing is used.

(c) Assume chaining is used. Explain how we will get the value for the keys 2 and31.

2. Recall that the load factor of a hashtable is the number of keys divided by the numberof buckets. We have decided that our hash table must always have a load factor thatis at most .75. Suppose we add a new key to the table that puts our load factor overthe threshold (say we added the 751st key to a table with 1000 buckets). What shouldbe done?

3. (??) Suppose we have a hashtable with 100 buckets and a hash function h that uniformlydistributes our keys over the buckets (a desirable property for a hash function). Moreprecisely, suppose each new key that is added is equally likely to go into any bucket,and that each key is independent. How many keys must be added till a collision islikely?

96

Page 97: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

8.8 Hashtable Solutions

1. (a)

0

1

2

3

4

5

6

7

8

9

11 C 1 A

32 D 2 B

14 E

99 A

(b)

97

Page 98: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

0

keys values

1

keys values

2

keys values

3

keys values

4

keys values

5

keys values

6

keys values

7

keys values

8

keys values

9

keys values

1 A

2 B

11 C

32 D

14 E

99 A

(c) We �rst apply the hash function h to the key 2 to obtain the bucket h(2) = 2.We check the �rst entry and see the key is 32. This doesn't match 2, so we checkthe next key and see it is 2. We return the value �B�. For 31, we �rst apply thehash function h and obtain the bucket h(31) = 1. We then check the �rst entryin the list for bucket 1 and �nd the key 11. This doesn't match 31, so we moveto the next entry with key 1. This also doesn't match 31, so we conclude the keyis not in the table.

2. Just as in the ArrayList case, we will create a new bucket table that is double the size.Unfortunately we can't just copy all the nodes over to their same positions since thehash function depends on the size of the table. Recall that a hash function takes keysand turns them into table indices; thus the hash function depends on the size of thetable. The way to resolve this issue is to move each entry from the original table intothe new table making sure we compute the new value of h for each key, and add theentry to the correct bucket list in the new table.

3. This problem is called the birthday paradox. For K keys and B buckets the probabilityof a collision is

1− B(B − 1)(B − 2) · · · (B −K + 1)

BK.

Letting B = 100 we see that the �rst value of K where this probability is larger than.5 is 13 (where it is .557). For general B, the minimum number of keys to make acollision likely grows like Θ(

√B).

98

Page 99: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

9 Lecture 9: Hashtables and HashMap

9.1 Exercises

1. Explain a small change that could speed up the following code:

Scanner in = new Scanner(System.in);int n = in.nextInt();ArrayList<String> al = new ArrayList<>();for (int i = 0; i < n; ++i) al.add(in.nextLine());Collections.sort(al);for (int i = 0; i < n; ++i) {process(al.get(i)); //does something

}

2. What is the runtime for �nding a particular value (not key) in a hashtable?

3. You have a hashtable whose keys are Integers, values are Doubles, and that uses chain-ing for collision handling. Suppose it has 5 buckets and that the hash function takesthe absolute value of the integer mod 5. What does the hashtable look like after thefollowing operations, in order:

put(8,0.0); put(11,1.0); put(21,1.0); put(53,9.2); put(11,−7.9); remove(21);

4. Below is a Java interface for the Set ADT:

Set.java

//Stores elements but disregards duplicates (according to .equals).public interface Set<T>{

//Adds t to the setvoid add(T t);//Determines if t is in the setboolean contains(T t);//Removes t from the set if it is the set.//Otherwise does nothing.void remove(T t);//Returns the number of elements in the setint size();

}

How could this be easily implemented using a Map?

5. Suppose we wanted a special Map that associated multiple values to a single key. Howcan this be done easily using a standard Map (without modifying its implementation)?

99

Page 100: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

9.2 Solutions

1. By plugging n into the constructor we can avoid the unnecessary copying and alloca-tions made as we grow the ArrayList.

2. To �nd a value we will iterate through the bucket table, and for each bucket index,iterate through each chain. Thue runtime is thus Θ(n + B) where n is the number ofkeys and B is the number of buckets. Usually n and B will be pretty close. With extramemory we could improve this to Θ(n) by maintaining a separate linked list of all ofthe entries in the table. The Java class LinkedHashMap maintains a linked list of allof the entries in order of their insertion into the map.

3.

0

1

2

3

4

11 -7.9

53 9.2

4. Store the elements of the set as keys of a Map, and just use null for all the values.Since Maps force all keys to be distinct, this gives the desired behavior.

5. Use an ArrayList as the value type. For instance, we might use code as what follows:

HashMap<String,ArrayList<Integer>> map = new HashMap<>();//do stu�//Next we add 3 to the list of key "A"ArrayList<Integer> al = map.get("A");if (al == null) {al = new ArrayList<>();map.put("A",al);

}al.add(3);

9.3 Hash Functions

What remains is to �nd a good hash function h that determines which bucket each keycorresponds to. Recall that any hash function must take equal keys to the same hash code.

100

Page 101: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

Furthermore, we require it to be deterministic. That is, the hash of a �xed key will neverchange (e.g., we don't return a random answer each time). Let's see what a good hashfunction would also have:

1. Fast: A very slow hash function would slow down all hashtable operations.

2. Well distributed relative to keys: If S is your potential set of keys then the hashfunction should try to uniformly distribute S over the bucket indices. That is, eachbucket should have roughly the same number of keys that correspond to it. This canbe very dependent on your set S. For example, if your keys are Strings, then potentialsets could be all English words, last names, social security numbers, or random strings.

3. Memory E�cient: Some hash functions use tables to help them compute their values.If used, these tables should be relatively small.

If a relatively good hash function is used, then all of the Map operations implemented in ahashtable have Θ(1) expected performance. If chaining is used, the average length of eachchain is the load factor. The chain lengths will roughly have a Poisson distribution.

In many languages the computation of the hash function is broken into 2 parts. In Javaevery Object has a hashCode method that returns an int. The default implementation usesthe address of the object in memory, but this is often overridden. A good hashCode usuallyhas to be designed without knowing anything speci�c about the potential key set S. Thus thehashCode implementation should have properties that are good in a general sense: e�cient,use all possible integer values, perform well (i.e., distributes the values evenly across theintegers) on all commonly used key sets (if these exist).

Hashtables will then take these ints and turn them into bucket addresses. There areseveral recommended ways to �nd a bucket address once we have a hashCode. Below weassume k is the hashCode of your key, and N is the number of buckets.

1. Division Method: Force N to be a nice prime and compute h(k) = k mod N . If youare implementing this in Java you can use the code (int)(Integer.toUnsignedLong(k)%N)since simply writing k%N can give you a negative result. You can also use (k&0x7FFFFFFF)%Nif you are willing to throw away the sign bit. Bit representation of integers will be dis-cussed next lecture.

2. Fibonacci Hashing (using �oating point arithmetic):

h(k) = bN(ϕk − bϕkc)c

where ϕ =√5−12

. Here bxc means round x down to an integer.

As another alternative, Java's HashMap forces N to be a power of two, and tries to shu�ethe bits of k a bit before modding by N . This avoids the ine�ciency of modding, but cancreate more collisions. To deal with the increased collisions, HashMap uses a more complexchaining system that we may discuss later in the course.

101

Page 102: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

9.3.1 Object's hashCode

As we saw above, the performance of HashMap is dependent on the quality of the hashfunction. Thus overriding hashCode is usually a good idea. If you override equals thenyou should de�nitely override hashCode since equal objects are expected to have the samehashCode. Moreover, if you override equals then your hashCode method should only use the�elds that equals compares. Some good guidelines (recommended by Joshua Bloch in hisgreat but slightly old book E�ective Java) are:

1. To compute a hash code for a primitive type, look at the implementations of hashCodein the wrapper classes. For instance, to hash a double you can look at the API for thedescription of Double.hashCode().

2. To hash an array, use Arrays.hashCode.

3. To hash an Object with several �elds a,b,c you can use Objects.hash(a,b,c) but thiswill be a bit ine�cient (it allocates an array each time). To avoid the allocation youcan just implement it yourself:

int hash = a.hashCode();hash = 31*hash + b.hashCode();hash = 31*hash + c.hashCode();

This is almost correct, since it fails if any of the �elds are null. We can write a bunchof if-statements, or we can use the helper method Objects.hashCode:

int hash = Objects.hashCode(a);hash = 31*hash + Objects.hashCode(b);hash = 31*hash + Objects.hashCode(c);

Objects also has a helper method Objects.equals which compares objects for equalityand handles nulls (could make containsValue on Homework 4 cleaner).

4. For collections you should treat each element like a �eld, and combine them as above.As an example, here is the calculation that is e�ectively used by Java lists:

int hash = 0;for (E e : this)hash = 31 * hash + Objects.hashCode(e);

return hash;

Eclipse will actually auto-generate equals and hashCode if you right-click on a Java �le, andselect source.

9.3.2 Varargs

In case you were interested, the Objects.hash method has the following declaration:

102

Page 103: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

public static int hash(Object... values)

This is called using a varargs (variable length argument list). The function hash will take anynumber of arguments. The variable values just becomes a Object[] which has the argumentsin it. To implement this Java simply turns hash(a,b,c) into hash(new Object[]{a,b,c}) so thereis an implicit array allocated each time you call the function. If you want to use varargs inyour programs there are only two things to know (in addition to what I already said): thevarargs must be the last parameter, and you can use any type before the elipsis (includingprimitive types). For example,

static void func(int a, double b, long... cs) {//Treat cs like a long[] here

}

9.4 Object.hashCode Exercises

1. What optimization can be made when computing the hashCode of an immutable ob-ject?

2. Why do we force objects that are equal (with respect to .equals) to have the samehashCode?

3. Several times we have seen System.out.printf used in class. What do you think itsarguments are?

4. What is wrong with the following potential hashCode implementations for String?

(a) Use the �rst character's value as the hashCode (and 0 if the String is empty).

(b) Use a uniform randomly generated int value as the hashCode.

(c) Use the sum of all the characters' values.

9.5 Object.hashCode Solutions

1. Once computed you can store the hashCode, since it cannot change. The extra memoryrequired is usually worth it for an object like a String (which does use this optimization)but not for the wrapper classes (which do not).

2. If they had di�erent hashCodes, they could go into di�erent buckets, and thus di�erentchains. Then all of our hashtable operations would treat the keys as distinct when theyshould be treated as equal.

3. public static void printf(String format, Object...) {/*stu�*/}

4. (a) The main issue is that the maximum possible hashCode would be limited by thesize of a character. Thus even if we had a large number of buckets only a smallportion would ever get used.

103

Page 104: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

(b) Even though the generated hashCodes would be well distributed amongst theintegers, there is no repeatability. That is, every time you rehash the same keyyou would get a di�erent value.

(c) Firstly, the hashCodes aren't large enough so we run into the same issues as the�rst part. Secondly, it ignores the ordering of the letters in the string. For exam-ple, note that anagrams all collide. In general, if there is a simple transformationof a key leads to a collision then the hashCode isn't as strong as we would like(as a general purpose hashCode).

9.6 Java HashMap

The HashMap class in the Java API supports all of the Map functionality we have discussedusing a hashtable and chaining. It also provides several methods of iteration by using themethods keySet, valueSet and entrySet as we show below.

HashMapIteration.java

import java.util.Collection;import java.util.HashMap;import java.util.Iterator;import java.util.Set;

public class HashMapIteration{

public static void main(String[] args){

HashMap<String,Integer> wordMap = new HashMap<>();wordMap.put("Hello", 3);wordMap.put("Great", 9);wordMap.put("Great2", 9);wordMap.put("Frank", 2);wordMap.put("hmm", −1);wordMap.put("wow", null);

Set<String> keys = wordMap.keySet();Collection<Integer> values = wordMap.values();Set<HashMap.Entry<String,Integer>> entries = wordMap.entrySet();

System.out.println("Keys:");for (String k : keys) System.out.print(k+" ");System.out.println();

System.out.println(keys.contains("hmm"));keys.remove("Frank");values.remove(9);

104

Page 105: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

System.out.println(keys.contains("Frank"));System.out.println(keys.contains("Great"));

System.out.println("Keys:");for (Iterator<String> it = keys.iterator(); it.hasNext(); ){

String k = it.next();System.out.print(k+" ");if (k.equals("Hello")) it.remove();

}

System.out.println("Values:");for (Integer d : values) System.out.print(d+" ");System.out.println();

System.out.println("Entries:");for (HashMap.Entry<String,Integer> e : entries){

System.out.println(e.getKey()+","+e.getValue());}

}}

The methods keySet, valueSet and entrySet are all Θ(1) and use Θ(1) memory since insteadof creating a new collection, they simply represent a view into the data in the HashMap. Asseen above, removals from these collections cause removals from the HashMap.

One important di�erence between the Java HashMap and our Map is that the get, remove,containsKey, all take type Object instead of the key type. In addition, containsValue takestype Object instead of the value type. The implementations only use hashCode and equalson the argument, so they don't really care what the type of the argument is. This decisionwas made to allow for greater �exibility, but gives less protection against unintentionalprogrammer errors:

HashMap<String,Integer> map = new HashMap<>();map.put("12",94);System.out.println(map.containsKey(12)); //Returns false

105

Page 106: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

10 Lecture 10: BitSets and Packages

10.1 Exercises

1. Suppose you are using a HashMap<String,Integer>. What is the runtime of put and getassuming you have a reasonable hash function?

2. Suppose you make a Java HashMap that uses ArrayLists as the keys. What happensif you add an element to one of the ArrayLists currently being used as a key?

3. Suppose you need to be able to translate user logins to emails and vice-versa. Assumeboth user logins and emails are Strings that are unique across the organization. Whatcan be done to allow lookups in both directions to be done in Θ(1)?

4. Given an ArrayList of some type of objects, show how to create a new list without theduplicate entries.

5. Give a simple e�cient implementation of a Set whose possible values will be between0 and 30. Below we have included the Set ADT:

Set.java

//Stores elements but disregards duplicates (according to .equals).public interface Set<T>{

//Adds t to the setvoid add(T t);//Determines if t is in the setboolean contains(T t);//Removes t from the set if it is the set.//Otherwise does nothing.void remove(T t);//Returns the number of elements in the setint size();

}

10.2 Solutions

1. Expected Θ(1) running time. This signi�es that we will usually have a small numberof collisions, so we will compare our key to a small number of entries. A more accurateruntime would be Θ(L) where L is the length of our key. This indicates that eventhough we will make a small number of key comparisons, each comparison will takeΘ(L) time. In other words, we have not assumed equals is Θ(1) and have incorporatedit into our runtime.

2. The hashCode of the ArrayList will probably change, and thus it will likely be inthe wrong bucket list. In light of this, never modify an ArrayList that is currently

106

Page 107: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

being used as a key in a hashtable. This can be made a little easier by using Collec-tions.unmodi�ableList (look it up in the Java API if you are curious).

3. Make two HashMaps taking Strings to Strings. The �rst maps logins into emails. Thesecond maps emails into logins. When ever you add a new user, you put an entry inboth maps.

4. We can stick all of our elements into a HashSet and then build our new list. Codefollows:

RemoveDuplicates.java

import java.util.ArrayList;import java.util.HashSet;

public class RemoveDuplicates {public static ArrayList<String> removeDups(

ArrayList<String> list){

HashSet<String> set = new HashSet<>();for (String s : list) set.add(s);ArrayList<String> ret = new ArrayList<>();for (String s : set) ret.add(s);return ret;

}

public static <T> ArrayList<T> removeDups2(ArrayList<T> list)

{HashSet<T> set = new HashSet<>();set.addAll(list);ArrayList<T> ret = new ArrayList<>();ret.addAll(set);return ret;

}

public static <T> ArrayList<T> removeDups3(ArrayList<T> list)

{//HashSet<T> set = new HashSet<>(list);//return new ArrayList<>(set);return new ArrayList<>(new HashSet<>(list));

}}

The method removeDups implements a String version of what we described above. InremoveDups2 we use a generic parameter on the method to allow for any type, and we

107

Page 108: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

use the method addAll to remove some code. In removeDups3 we remove the addAllby using a constructor which does the same thing in one step.

5. Here we can just use the implementation from Lab 3: a boolean array. Each entry ofthe array corresponds to a value between 0 and 30. If the ith entry is true, then thecorresponding element is in the set, otherwise it isn't.

10.3 Binary Representation of Numbers

Given any non-negative integer we can represent it in base 2 (i.e., binary):

5 = 1 · 22 + 0 · 21 + 1 · 20 = 1012

9 = 1 · 23 + 0 · 22 + 0 · 21 + 1 · 20 = 10012

15 = 1 · 23 + 1 · 22 + 1 · 21 + 1 · 20 = 11112,

where the subscript 2 just means we are writing the number in base 2. Typically we writenumbers in base 10 (decimal):

237 = 2 · 102 + 3 · 101 + 7 · 100 = 23710.

In Java we can write integers in binary by prefacing them with 0b:

5 == 0b1019 == 0b100115 == 0b1111

For Java ints (which are 32-bit values), all representable non-negative integers are storedin the lowest 31 bits (in base 2) with the highest bit �xed at 0. Negative numbers willall have the highest bit set to 1. To handle negative numbers the computer uses the two'scomplement representation: to negate a number we �ip all of the bits and then add 1. Forexample, the representation of 1 is 00 . . . 001. To get the two's complement representationof −1 we �rst �ip to get 11 . . . 110 and then add 1 to get 11 . . . 111. If we repeat this processwe get 1 again. Since 5 is 00 . . . 0101 then −5 is 11 . . . 1011. Although the two's complementformula may seem unintuitive at �rst, it actually makes a lot of sense. Consider the value−1 we looked at above. What happens when you add 1 to it? As we go through the additionbit by bit we will keep getting a 0 and carrying a 1. The �nal carried 1 is lost and we obtainthe value 0. In other words, by simple binary addition we have −1+1 = 0. That is, all of thestandard arithmetic operations can be applied to numbers represented in two's complementand they yield the correct results.

The long data type is just like an int, but it uses 64-bits. Thus the non-negative valueswill use the lowest 63-bits with the highest bit 0, and the negatives will have the highestbit set to 1. If you have a long or int and want to see its representation in bits you can useInteger.toBinaryString and Long.toBinaryString.

108

Page 109: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

10.4 Bitwise Operations

In addition to the usual arithmetic operations, Java supports bitwise operations that workon the individual bits of the values. These are bitwise complement (~), bitwise-and (&),bitwise-or (|), and bitwise-xor (^). There is also shift left (<<), signed shift right (>>) andunsigned shift right (>>>). Each of these operations work separately on each bit, so knowinghow they work on single bit inputs shows you how they work in general. Complement a bitmeans to �ip it, so the bitwise complement �ips all of the bits of a number. Letting p and qdenote bits, the following table shows how the operations work bitwise.

p q ∼ p p&q p|q p∧q0 0 1 0 0 00 1 1 0 1 11 0 0 0 1 11 1 0 1 1 0

Acting on ints, the bitwise operators work on every bit simultaneously. For example,

5 & 6 == 0b101 & 0b110 == 0b100 == 45 | 6 == 0b101 | 0b110 == 0b111 == 75 ^ 6 == 0b101 ^ 0b110 == 0b011 == 3~5 == ~0b101 == 0b111...010 == −6~5+1 == ~0b101 + 1 == 0b111...011 == −5

The shift operators work as you would expect. If you write x<<k it will shift the bits of xto the left by k bits. To do this it shifts in k zero bits on the right (lowest order) side. Ifyou write x>>>k it shifts x to the right by k bits by shifting in k zero bits on the left side.If you write x>>k then you shift to the right by k bits, but the bits you shift in on the leftwill match the highest order bit (i.e., preserving the sign of the number). For example,

5 << 2 == 0b101 << 2 == 0b101005 >> 2 == 0b101 >> 2 == 15 >>> 2 === 0b101 >>> 2 == 1−5 << 2 == 0b11...1011 << 2 == 0b11...101100−5 >> 2 == 0b11...1011 >> 2 == 0b11...1011−5 >>> 2 == 0b11...1011 >>> 2 == 0b0011...10

10.5 Binary Exercises

1. Write the 32-bit binary representations of the following values: 37, 15, -37, -15.

2. Compute the following expressions (assuming 32-bit ints):

37 & 15, 37 | 15, 37 ^ 15, ~37

3. What ints will be equal to their negative?

109

Page 110: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

4. What operation is equivalent to left shifting by k bits? What operation is equivalentto signed right shifting by k bits (i.e., x>>3)?

5. Let x be an arbitrary integer. What are the values of the following expressions?

(a) x^x

(b) x<<3

(c) x&0

(d) x&1

6. What is the int value y so that x&y == x for any int x?

10.6 Binary Solutions

1.

37 == 0b00...010010115 == 0b00...0001111−37 == 0b11...1011011−15 == 0b11...1110001

2.

37 & 15 == 0b00...0000101 == 537 | 15 == 0b00...0101111 == 4737 ^ 15 == 0b00...0101010 == 42~37 == 0b11...1011010 == −38

3. 0 and −231.

4. Left shifting by k bits is the same as multiplying by 2k. Right shifting is equivalentto dividing by 2k. This is only true since we used >> instead of >>>. If a numberis negative and we use the unsigned right shift then it isn't equivalent to division (itturns out to be equivalent to division if we treat the integer as unsigned, but we wontget into this).

5. (a) 0

(b) 8x

(c) 0

(d) 1 if x is odd and 0 if x is even.

6. y = −1, i.e., every bit 1.

110

Page 111: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

10.7 BitSets (also called Bitmasks)

Our goal is to improve upon our earlier data structure for storing subsets of the integers0, 1, . . . , N where N ≤ 63. To do this we will use a single long variable where each bitrepresents one of the numbers. For instance, the long 5L (the L makes the number have typelong) which has binary representation 1012 will represent the set {0, 2}. If the bit in positioni is 1, then we will say i is in the set. If the bit in position i is 0, then we will say i is not inthe set. For this reason we will often want to obtain information about the bit in positioni of a given long. It will be helpful to use the quantity (1L<<i) which is entirely zero bitsexcept for a 1 in position i (i.e., the (i + 1)th bit). For example, (1L<<3)==0b1000.

10.8 BitSet Exercises

1. What does x^(1<<3) do to the long variable x?

2. Assume the long variables x, y represent some subsets of 0, 1, . . . , 63 using the conven-tion that the ith bit determines whether the subset contains i. Show how to implementthe following methods in Java code:

(a) Determine if the set (corresponding to x) contains the number i.

(b) Add i to the set (corresponding to x), or do nothing if it already contains it.

(c) Remove i from the set (corresponding to x) if it contains it, otherwise do nothing.

(d) Union the two sets corresponding to x and y.

(e) Intersect the two sets corresponding to x and y.

(f) Remove all elements of the set corresponding to y from the set corresponding tox (ignore the elements from y but not in x).

3. Let the long variable x represent a subset of 0, 1, . . . , 63. Show how to count the numberof elements in the set.

4. Let n = 20. Suppose there is a function void process(int bitset) which performs therequired work on the given subset of 0, 1, . . . , n. Show how to call process on allsubsets of 0, 1, . . . , n.

5. How can we make a bitset for the values 0, . . . , N where N > 63?

10.9 BitSet Solutions

1. Flips the 4th bit (i.e., the bit in position 3).

2. (a) (x & (1L<<i)) != 0

(b) x = (x | (1L<<i)) or we could write x |= 1L<<i

(c) x = x & (~(1L<<i)) or x &= ~(1L<<i)

111

Page 112: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

(d) x | y

(e) x & y

(f) x & (~y)

3. Below we give some di�erent implementations:

CountBits.java

import java.util.Random;

public class CountBits {public static int countOnes(long x) {

int ret = 0;for (int i = 0; i < 64; ++i){

long b = 1L << i;if ((x & b) != 0) ret++;

}return ret;

}public static int countOnes2(long x) {

int ret = 0;while (x != 0) {

if ((x & 1) != 0) ret++;x = (x>>>1); //Must use >>>//Could also write x >>>= 1;

}return ret;

}public static int countOnes3(long x) {

int ret = 0;while (x != 0) {

ret++;x = x&(x−1); //Sneakily zeros out lowest 1

}return ret;

}

public static int countOnes4(long x) {return Long.bitCount(x);

}

public static void main(String[] args){

long x = new Random().nextLong();System.out.println(x);

112

Page 113: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

System.out.println(Long.toBinaryString(x));System.out.println(countOnes(x));System.out.println(countOnes2(x));System.out.println(countOnes3(x));System.out.println(countOnes4(x));

}}

The �rst two are fairly standard. The only trick with the second is that if you don'tuse >>> it will loop in�nitely on negative numbers. The third one uses the sneakyoperation x&(x−1) that zeros out the lowest bit that is 1. This reduces the loop toonly iterate k times, where k is the number of 1's (instead of 64 times like the otherloops could). The last one shows that the Java library has this operation.

4. for (int i = 0; i < (1<<21); ++i) process(i);

5. Use an array of longs where each corresponds to 64 values in the range. This is alreadyimplemented for you in the Java class BitSet.

10.10 Java Packages

Packages are used to organize code in to bundles that can be treated as separate units.Putting your code in a package in Eclipse is easy. You just right-click on your project andcreate a new package. At the top of each of your source �les will be the line package

PackageName; For example, if you look at the top of the Java ArrayList class you will see theline package java.util; A period in a package name indicates subpackaging. Thus ArrayListbelongs to the subpackage util which belongs to the package java. On the �le system, eachsubpackage corresponds to a di�erent folder. Thus code in the package brett.ds2016.util willbe in the folder brett/ds2016/util/ o� of my project's src folder in the workspace. Up tothis point in the class we have never created our own packages. Thus all of our code is inthe src folder directly in what is called the default (unnamed) package. By always creatingnew projects for each lab or assignment we have separated our code nicely, but code in oneproject cannot use code in another project. Thus every project is on an island. If instead wemade a single project with multiple packages then the code would be separated, but wouldhave access to each other via import statements.

Now that we know what packages are, we can �nish learning about access modi�ers. Toobtain access to the public classes in a di�erent package we must use an import statementat the top of our code. You have already done this countless times in your programs. Ifyou declare a class but you do not mark it as public it has package access. This means thatthe only code that has access to it lies in the same package. The same goes for variablesand methods (static or instance) that you declare in a class. If you don't specify an accessmodi�er then they have package access and can only be accessed within the same package.

113

Page 114: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

If you mark a �eld or method as protected then it is accessible within the same package, orin any descendent class (possibly in another package).

114

Page 115: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

11 Lecture 11: Binary Trees

11.1 Exercises

1. Below is an expression tree with numbers as leaves and operators as internal nodes(non-leaves). Each internal node's value is obtained by applying the operator to thevalues of its left and right children. What is the value of the tree below?

+

* -

8+

1 2

9 *

4 3

2. A rooted binary tree is a tree where each node has at most 2 children, and one of thenodes is called the root. Suppose you have such a tree with n nodes.

(a) What is the maximum height of such a tree? The height is the maximum numberof steps needed to get from the root to a leaf.

(b) (??) What is the minimum possible height of such a tree?

3. Consider the following folder structure on a �le system:

Stu�/Docs/MathDocs/

Calc.pdfLinAlg.pdf

CSDocs/DS.pdfAlgo.pdf

Other/hw1.pdfhw2.pdfhw3.pdf

Draw the above as a tree with each folder or �le in a node, and Stu�/ as the root.

4. (?) In java.io there is a class File that represents a �le or directory (folder) on a �lesystem. Here are some of its many methods:

� �le.getName(): Returns the name (a String) of the �le/directory.

� �le.isDirectory(): Returns (a boolean) if �le is a directory.

115

Page 116: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

� �le.listFiles(): Returns a File[]. If it is a directory, it returns all of the contained�les/directories. Otherwise returns null;

� �le.length(): If it is a �le, returns its length (a long) in bytes.

(a) Implement the following method which computes the total size of all �les con-tained within the given folder (directly, or indirectly). If the argument f is just a�le, simply return its size.

public static long totalSize(File f)

(b) Implement the following method which prints out all �les/directories beneath thegiven folder. Each time you descend into a subdirectory indent by 4 spaces. Usethe example in Problem 3 above as a guideline.

public static void printDirectoryTree(File f)

11.2 Solutions

1. ((1 + 2) ∗ 8) + (9− (4 ∗ 3)) = 24 + (−3) = 21

2. (a) n− 1 (every non-leaf node has 1 child)

(b) blg(n)c. To see this, note that you can �t 2n− 1 nodes into a tree of height n− 1by making every non-leaf node have 2 children (a complete binary tree). If youhave more than 2n − 1 nodes, you need a tree of height at least n. Thus if youhave between 2n and 2n+1− 1 nodes the minimum possible height is n giving ourformula.

3. Note that the tree below is not binary since Other has 3 children.

Stuff/

Docs/ Other/

MathDocs/ CSDocs/

Calc.pdf LinAlg.pdf DS.pdf Algo.pdf

hw1.pdf hw2.pdf hw3.pdf

4. Below is our code for both parts.

FileUtils.java

import java.io.File;import java.util.Scanner;

public class FileUtils

116

Page 117: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

{public static long totalSize(File f){

if (!f.isDirectory()) return f.length();long ret = 0;for (File � : f.listFiles()) ret += totalSize(�);return ret;

}

public static void printDirectoryTree(File f){

printDirHelp(f,"");}public static void printDirHelp(File f, String tab){

String name =f.getName() + (f.isDirectory() ? "/" : "");

System.out.println(tab+name);if (f.isDirectory())

for (File � : f.listFiles())printDirHelp(�,tab+" ");

}public static void main(String[] args){

Scanner in = new Scanner(System.in);File f = new File(in.nextLine());System.out.println("Total size = "+totalSize(f));printDirectoryTree(f);

}}

Both are implemented recursively. We will see this type of code is common when youneed to visit all of the nodes of a tree. We haven't optimized the code at all, especiallyin our String handling.

11.3 Data Structure: Binary Tree

In a binary tree each node has up to 2 children, which we will label as left and right. As inthe case of linked lists, our trees will be composed of nodes which look as follows.

TreeNode.java

public class TreeNode<T>{

private T value;private TreeNode<T> left, right; //Children

117

Page 118: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

//private TreeNode<T> parent; //optional

public TreeNode(T t, TreeNode<T> l, TreeNode<T> r){

value = t;left = l;right = r;

}

public T getValue() { return value; }public void setValue(T t) { value = t; }public TreeNode<T> getLeft() { return left; }public TreeNode<T> getRight() { return right; }public void setLeft(TreeNode<T> t) { left = t; }public void setRight(TreeNode<T> t) { right = t; }

}

As can be seen above, we sometimes include a reference to the parent node. One node inour tree will be designated as the root, and it will not be the child of any other node (has noparent). Not having a left or right child is indicated with null. Leaves of the tree will havenull for both their left and right children.

11.3.1 Tree Terminology

The size of a tree is the number of nodes in it. The height is the longest number of steps(i.e., edges) from the root to any leaf. Given a node, its depth is the number of steps fromit to the root. The set of all nodes with a �xed depth is called a level. If we �x a node ina tree, and look at all of its descendents we get a subtree of the original tree that is rootedat the given node. In many of our algorithms, we will decompose a tree into the subtreeswhich we act on recursively.

11.4 Tree Exercises

1. Suppose you have a binary tree where each node is a TreeNode<T>. Implement thefollowing static methods. For each give the runtime and space usage.

(a) static <T> int size(TreeNode<T> root): Gives the size of the tree with the givenroot.

(b) static <T> int height(TreeNode<T> root): Gives the height of the tree with thegiven root.

(c) static int sum(TreeNode<Integer> root): Gives the sum of the values of the nodesin the tree with the given root. Here we assume the node values are Integers.

(d) (?)

static <T> ArrayList<T> getLevel(TreeNode<T> root, int d)

118

Page 119: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

Returns all values at depth d in an ArrayList. Order the ArrayList left-to-rightin how the nodes would appear in a diagram of the tree.

(e) (??)

static <T> void printLevelOrder(TreeNode<T> root)

Prints each value in the tree on a separate line. Go level-by-level with lower depthlevels coming earlier. Order each level left-to-right as in the previous problem.Your implementation should have Θ(n) worst-case performance, where n is thenumber of nodes in the tree.

2. Consider the following 3 functions.

public static <T> void preOrder(TreeNode<T> root) {if (root == null) return;System.out.print(root.getValue()+" ");preOrder(root.getLeft());preOrder(root.getRight());

}public static <T> void inOrder(TreeNode<T> root) {if (root == null) return;inOrder(root.getLeft());System.out.print(root.getValue()+" ");inOrder(root.getRight());

}public static <T> void postOrder(TreeNode<T> root) {if (root == null) return;postOrder(root.getLeft());postOrder(root.getRight());System.out.print(root.getValue()+" ");

}

Using the following tree show what the output is when each of the above methods isrun.

+

* -

8+

1 2

9 *

4 3

11.5 Tree Solutions

1. Below is the source code for all of the parts.

119

Page 120: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

TreeUtils.java

import java.util.ArrayDeque;import java.util.ArrayList;import java.util.Queue;

public class TreeUtils{

public static <T> int size(TreeNode<T> root){

if (root == null) return 0;return 1 + size(root.getLeft()) + size(root.getRight());

}

public static <T> int height(TreeNode<T> root){

if (root == null) return −1;return 1 + Math.max(height(root.getLeft()), height(root.getRight()));

}

public static int sum(TreeNode<Integer> root){

if (root == null) return 0;return root.getValue() + sum(root.getLeft()) + sum(root.getRight());

}

public static <T> ArrayList<T> getLevelSlow(TreeNode<T> root,int d)

{ArrayList<T> ret = new ArrayList<>();if (root == null) return ret;if (d == 0){

ret.add(root.getValue());return ret;

}ret.addAll(getLevelSlow(root.getLeft(),d−1));ret.addAll(getLevelSlow(root.getRight(),d−1));return ret;

}

public static <T> ArrayList<T> getLevel(TreeNode<T> root,int d) {

ArrayList<T> ret = new ArrayList<>();getLevelHelp(root, d, ret);return ret;

120

Page 121: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

}public static <T> void getLevelHelp(

TreeNode<T> root, int d, ArrayList<T> al) {if (root == null) return;if (d == 0) {

al.add(root.getValue());return;

}getLevelHelp(root.getLeft(),d−1,al);getLevelHelp(root.getRight(),d−1,al);

}

public static <T> void printLevelOrder(TreeNode<T> root){

Queue<TreeNode<T>> q = new ArrayDeque<>();q.add(root);while(!q.isEmpty()){

TreeNode<T> node = q.poll();System.out.println(node.getValue());if (node.getLeft() != null)

q.add(node.getLeft());if (node.getRight() != null)

q.add(node.getRight());}

}//Creates an expression tree from a post�x expressionpublic static TreeNode<String> makeExpressionTree(String post�x){

//Used as a stackArrayDeque<TreeNode<String>> s = new ArrayDeque<>();String[] toks = post�x.split("\\s+");String operator = "+*−/";for (String tok : toks){

if (operator.indexOf(tok) != −1){

TreeNode<String> right = s.pop();TreeNode<String> left = s.pop();s.push(new TreeNode<>(tok,left,right));

} else s.push(new TreeNode<>(tok,null,null));}return s.pop();

}

121

Page 122: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

public static void main(String[] args){

TreeNode<String> n = makeExpressionTree("1 2 + 8 * 9 4 3 * − +");System.out.printf("Size = %d\n", size(n));System.out.printf("Height = %d\n", height(n));System.out.printf("getLevel(0) = %s\n", getLevel(n,0));System.out.printf("getLevel(1) = %s\n", getLevel(n,1));System.out.printf("getLevel(2) = %s\n", getLevel(n,2));System.out.printf("getLevel(3) = %s\n", getLevel(n,3));System.out.printf("getLevel(4) = %s\n", getLevel(n,4));printLevelOrder(n);

}}

As you can see above, for all but the last one we solved the required task by solvingthe same problem on the left and right subtrees. This type of problem decompositionoccurs frequently when writing recursive functions. In all cases the worst-case runtimeis Θ(n), the number of nodes in the tree. The worst-case space usage is determinedby the height of the tree, which is also Θ(n) in the worst case. We also include aslower getLevelSlow method which must perform extra copying at each node. This hasruntime Θ(n2) in the worst case.

For printLevelOrder we traverse the tree level by level using a technique called breadth-�rst search which we will see again later with graphs. This is implemented using a queueto keep track of each new node. The worst-case runtime space are again both Θ(n).Here the space doesn't depend directly on the height of the tree, but on how large thequeue becomes. If we instead call getLevel repeatedly to get each level we would havea Θ(n2) runtime in the worst-case.

2. (a) preOrder: + * + 1 2 8 - 9 * 4 3

(b) inOrder: 1 + 2 * 8 + 9 - 4 * 3

(c) postOrder: 1 2 + 8 * 9 4 3 * - +

Here is a slightly altered version of the traversals where we include appropriately placedparentheses:

(a) preOrderParen: +( *( +( 1, 2 ), 8 ), -( 9, *( 4, 3 ) ) )

(b) inOrderParen: ( ( ( 1 + 2 ) * 8 ) + ( 9 - ( 4 * 3 ) ) )

(c) postOrderParen: ( ( ( 1 2 + ) 8 * ) ( 9 ( 4 3 * ) - ) + )

The code to generate both of these can be found in TreeTraversals.java.

122

Page 123: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

11.6 Tree Traversals and Expressions

We have already seen several examples of how to process all of the nodes of a tree (calledtraversing a tree). Some examples are preorder, inorder, postorder, and level-by-level (orbreadth-�rst) traversals.

When processing an expression tree, the output of the traversals are called pre�x, in�xand post�x notation. In�x corresponds to the standard method of placing the operatorsbetween their operands. For in�x the parentheses are important since otherwise it is unclearwhich order to perform the operations in. Pre�x puts the operator name before the operands.This is more common when the operators are named functions, like exp or max. For example,in the parenthesized pre�x above we write +(1, 2) instead of 1 + 2. In post�x the operatorappears after the operands. It turns out that pre�x and post�x do not require parenthesessince they fully specify the order of operations (assuming the arity of all the operators is�xed).

Expressions in post�x or pre�x notation are very easy to evaluate using a stack. Herewe outline an algorithm for evaluating a post�x expression (we include code below with oursolutions). Every time you see a number you push it on the stack. When you process anoperator pop the top two elements o� the stack, and then push the result of the operation(the �rst pop gives the right operand, the second pop gives the left operand). When donethe value remaining on the stack is the result.

11.7 Pre�x/Post�x Exercises

1. Determine if the following post�x expressions are valid, and evaluate them if so.

(a) 1 2 + 3 *

(b) 1 2 - 3 4 +

(c) 1 2 3 4 5 - - - +

(d) + 1 2

(e) 1 2 + +

2. Show how to determine if a post�x expression is valid.

3. Evaluate the following valid pre�x expression: + * 1 2 * + 1 3 - 4 5.

4. (?) Suppose a binary tree has distinct Integer values in all of its nodes. The preOrdertraversal is 1 2 4 5 6 3 7 8 9. The inOrder traversal is 5 4 2 6 1 7 8 3 9. Draw the tree.

5. (??) Describe an algorithm for evaluating a pre�x expression.

6. Implement the following function:

public static int countSums(int[] arr, long s)

123

Page 124: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

which counts the number of subsequences of arr that sum to s. A subsequence isobtained by omitting 0 or more elements of arr. If you omit all of arr then the sum is0. You can assume arr has at most 25 elements.

11.8 Pre�x/Post�x Solutions

1. (a) 9

(b) Not valid.

(c) -1

(d) Not valid.

(e) Not valid.

2. Use the stack algorithm mentioned earlier. If pop ever fails when processing an opera-tor, or if the stack doesn't have size 1 at the end the expression isn't valid. Otherwise,it is valid. If you are testing validity without also computing the expression, you don'teven need the stack. Simply keep track of the size of the stack after each operation inan int.

3. -2

4. The preOrder traversal starts with 1 so the root is 1. The inOrder traversal then tellsus the left subtree of the root has 4 nodes and the right subtree has 4 nodes. We canproceed in this fashion.

1

2 3

4 6 7 9

5 8

5. We will use an algorithm that is almost identical to that for the post�x expression, butwe process the expression right-to-left. The only di�erence is the order of the operandswhen processing an operator. Now the �rst pop yields the left operand, and the secondpop yields the right operand. We include code for both post�x and pre�x processingbelow.

ExpressionUtils.java

import java.util.ArrayDeque;

public class ExpressionUtils{

static String operators = "+*−/";

124

Page 125: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

public static int eval(char op, int left, int right){

if (op == '+') return left + right;else if (op == '−') return left − right;else if (op == '*') return left * right;else return left/right;

}public static int processPost�x(String expr){

String[] toks = expr.split("\\s++");ArrayDeque<Integer> stack = new ArrayDeque<>();for (String s : toks){

if (operators.indexOf(s) != −1){

int right = stack.pop();int left = stack.pop();stack.push(eval(s.charAt(0),left,right));

} else stack.push(new Integer(s));}if (stack.size() != 1) throw new IllegalStateException();return stack.pop();

}public static int processPre�x(String expr){

String[] toks = expr.split("\\s++");ArrayDeque<Integer> stack = new ArrayDeque<>();for (int i = toks.length−1; i >= 0; −−i){

String s = toks[i];if (operators.indexOf(s) != −1){

int left = stack.pop();int right = stack.pop();stack.push(eval(s.charAt(0),left,right));

} else stack.push(new Integer(s));}if (stack.size() != 1) throw new IllegalStateException();return stack.pop();

}public static void main(String[] args){

System.out.println(processPost�x("1 2 + 3 *"));System.out.println(processPost�x("1 2 3 4 5 − − − +"));System.out.println(processPost�x("1 2 + 8 * 9 4 3 * − +"));

125

Page 126: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

System.out.println(processPre�x("+ * 1 2 * + 1 3 − 4 5"));}

}

6. One way to solve this problem is by looping through bitmasks. Here we will give arecursive solution. First the code:

CountSums.java

public class CountSums{

public static int countSums(int[] arr, long s){

return countSumsHelp(arr, s, 0, 0);}public static int countSumsHelp(int[] arr, long s, int pos, long sum){

if (pos == arr.length) return s == sum ? 1 : 0;int ret = 0;ret += countSumsHelp(arr,s,pos+1,sum+arr[pos]);ret += countSumsHelp(arr,s,pos+1,sum);return ret;

}public static void main(String[] args){

int[] arr0 = {2,3,5};int[] arr1 = {0,0,0,0,0,0};int[] arr2 = {1,2,3,4,5,6,7,8};int[] arr3 = {1,1,1,1,1,1,1,1,1,1};System.out.println(countSums(arr0,5));System.out.println(countSums(arr1,0)); //2^6System.out.println(countSums(arr1,1)); //0System.out.println(countSums(arr2,8));System.out.println(countSums(arr2,10));System.out.println(countSums(arr3,3)); //10 choose 3

}}

The idea is that for each element of the array we have two choices: use the elementin our sum or not. Below is a binary tree representing our sequence of choices. Eachnode contains the corresponding sum when run with arr = {2,3,5} and s = 5.

126

Page 127: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

0

0

use 2

2

0

use 3

3 2

use 3

5

0

use 5

5 3

use 5

8 2

use 5

7 5

use 5

10

In the above implementation pos tracks what level of the tree we are on, and sumtracks the value in the node. The leaves represent all possible subsequence sums.

127

Page 128: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

12 Lecture 12: Implicit, N-ary, and Binary Search Trees

12.1 Exercises

1. (?) Famous interview problem: A list initially has the 1001 numbers 0, 1, 2, . . . , 1000.Then a single random number is removed from the list, and then it is shu�ed in to arandom order. Design an algorithm to �nd which number was deleted.

2. Implement the following function that computes the number of leaves of a tree.

public static <T> int numLeaves(BinaryTreeNode<T> root)

You can asssume BinaryTreeNode has getLeft and getRight methods.

3. Give the inorder, preorder, and postorder traversals of the following tree.

1

2 3

4 6 7

4. Evaluate the following post�x expression: 1 2 + 3 * 4 5 6 - - /.

5. (?) As in homework 5, compute the number ways to color (red or black) a row of boxesof length n where you cannot have 3 red boxes in a row. Use recursion.

12.2 Solutions

1. We have 3 solutions below:

FindMissing.java

import java.util.Arrays;

public class FindMissing {public static int missing(int[] arr){

Arrays.sort(arr);for (int i = 0; i < arr.length; ++i)

if (arr[i] != i) return i;return arr.length;

}public static int missing2(int[] arr){

long N = arr.length;long sum = N*(N+1)/2;for (int i = 0; i < arr.length; ++i)

128

Page 129: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

sum −= arr[i];return (int)sum;

}public static int missing3(int[] arr){

int tmp = 0;for (int i = 0; i < arr.length; ++i)

tmp ^= arr[i];for (int i = 1; i < arr.length; ++i)

tmp ^= i;return tmp;

}}

The �rst one uses sorting and is the slowest. In the second one we know what thesum of all the numbers 0 − 1000 should be, and we subtract the elements of the list.In the last one we use the fact that x^x=0, x^0=x, and that xor is associative andcommutative (i.e., you can rearrange the xors in any order you like).

2. public static <T> int numLeaves(BinaryTreeNode<T> root) {if (root == null) return 0;int cs = numLeaves(root.getLeft()) + numLeaves(root.getRight());return cs == 0 ? 1 : cs;

}

3. (a) preOrder: 1 2 4 3 6 7

(b) inOrder: 4 2 1 6 3 7

(c) postOrder: 4 2 6 7 3 1

4. Applying the algorithm from last class we obtain 9/5.

5. Our code follows:

Boxes.java

import java.util.Arrays;

public class Boxes{

static long[][] cache = new long[3][61];

public static long countValidRows(int n){

return cVRHelpMemo(n, 0);}public static long cVRHelp(int n, int red)

129

Page 130: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

{if (red > 2) return 0;if (n == 0) return 1;return cVRHelp(n−1,0) + cVRHelp(n−1,red+1);

}public static long cVRHelpMemo(int n, int red){

if (red > 2) return 0;if (n == 0) return 1;if (cache[red][n] > −1) return cache[red][n];cache[red][n] = cVRHelpMemo(n−1,0)

+ cVRHelpMemo(n−1,red+1);return cache[red][n];

}public static void main(String[] args){

for (int i = 0; i < 3; ++i) Arrays.�ll(cache[i], −1);System.out.println(countValidRows(1));System.out.println(countValidRows(3));System.out.println(countValidRows(6));System.out.println(countValidRows(20));System.out.println(countValidRows(45));System.out.println(countValidRows(60));

}}

In cVRHelp we consider for each box whether to color it red or black. The variable redstores the number of red boxes in a row we currently have. The variable n stores thenumber of remaining boxes to color. As cVRHelp traverses the implicit tree (at eachnode we choose red or black), it covers at most 2n nodes. That said, there are only 3npossible inputs to cVRHelp. Using a technique called memoization we build a tablethat caches the results of cVRHelp so we never have to recompute it. This reduces thetotal runtime of using cVRHelpMemo to Θ(n).

12.3 Implicit Trees

As we saw in the countSums problem above, and when we were looking at binary searchearlier in the semester, there is sometimes an implicit tree lurking in the background. Inother words, our recursive code is traversing a tree that doesn't explicitly exist as a datastructure in our code.

Here is a famous problem that we will solve using recursion, and observe the implicittree in the background. Consider the problem of placing 8 non-attacking queens on a chessboard. Recall that a chess board is 8× 8, and that a queen can move any number of squareshorizontally, vertically, or diagonally. To solve this problem we will recursively try all possible

130

Page 131: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

queen placements. One naive method is to try to put a queen on every square of the board,but this will require us to look at 264 possible placements, an infeasible task. Instead we willproceed one row at a time, since there must be exactly one queen in every row. Each timewe place a queen we will make sure it doesn't attack any of the previously placed queens.In this way we are pruning our tree by cutting o� subtrees we know will not yield solutions.Code follows:

Queens.java

public class Queens{

//Can be made signi�cantly fasterpublic static int placeQueens(int currRow, int[] columns){

int SIZE = columns.length;if (currRow == SIZE) //Leaf of implicit tree{

print(columns);return 1;

}int s = 0;for (int c = 0; c < SIZE; ++c)

//c is the potential column of the new queen{

boolean ok = true;for (int i = 0; i < currRow && ok; ++i)

//looping through previously placed queens{

if (columns[i] == c) ok = false;//Check if slope of line through both queens is 1 or −1if (Math.abs(c − columns[i]) == Math.abs(currRow−i))

ok = false;}if (ok){

columns[currRow] = c;s += placeQueens(currRow+1,columns);

}}return s;

}public static void print(int[] columns){

int SIZE = columns.length;for (int r = 0; r < SIZE; ++r)

131

Page 132: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

{for (int c = 0; c < SIZE; ++c){

System.out.print(columns[r] == c ? 'Q' : '.');}System.out.println();

}System.out.println();

}public static void main(String[] args){

System.out.println(placeQueens(0,new int[8]));}

}

12.4 Implicit Tree Exercises

1. Write a program that computes the nth Fibonacci number for 0 ≤ n ≤ 90 (�ts in along). The Fibonacci numbers an are de�ned by the following recurrence:

a0 = 0, a1 = 1, an = an−1 + an−2,

for n > 1.

2. (Knapsack Problem) (??) Suppose you have k items you want to return to the store.The array weights[] stores the weight of each item. The array value[] stores how muchmoney each item is worth. You cannot carry more than weightCap total weight. Deter-mine the maximum total value you can return without violating your weight restriction.You cannot bring a fraction of an item. You can assume k ≤ 25.

public static int maxValue(int[] weights, int[] values, int weightCap)

3. Consider the following two di�erent ways of make a tree that allows for more than 2kids per node.

ArrayTreeNode.java

import java.util.ArrayList;

public class ArrayTreeNode<T> {private T value;private ArrayList<ArrayTreeNode<T>> children = new ArrayList<>();public T getValue() { return value; }public void setValue(T t) { value = t; }public ArrayList<ArrayTreeNode<T>> getChildren() { return children; }

}

132

Page 133: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

ListTreeNode.java

public class ListTreeNode<T> {private T value;private ListTreeNode<T> �rstChild;private ListTreeNode<T> nextSibling;public ListTreeNode(T v, ListTreeNode<T> f, ListTreeNode<T> n) {

value = v;�rstChild = f;nextSibling = n;

}public T getValue() { return value; }public void setValue(T t) { value = t; }public ListTreeNode<T> getFirstChild() { return �rstChild; }public ListTreeNode<T> getNextSibling() { return nextSibling; }public void setFirstChild(ListTreeNode<T> f) { �rstChild = f; }public void setNextSibling(ListTreeNode<T> n) { nextSibling = n; }

}

The ArrayTreeNode is self-explanatory. For the ListTreeNode, each node only pointsto its �rst child. Then each node also has a reference to the next sibling. Given these,implement the following functions which compute the number of nodes in the tree.

(a)

public static int <T> getSize(ArrayTreeNode<T> root)

(b)

public static int <T> getSize(ListTreeNode<T> root)

12.5 Implicit Tree Solutions

1. Below we have several implementations.

Fibonacci.java

public class Fibonacci {

public static long �b(int n) {if (n <= 1) return n;return �b(n−1) + �b(n−2);

}static long[] cache = new long[91];public static long �bMemo(int n) {

if (n <= 1) return n;if (cache[n] > 0) return cache[n];

133

Page 134: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

return cache[n] = �bMemo(n−1) + �bMemo(n−2);}static long[] dpTable = new long[91];public static void buildTable() {

dpTable[0] = 0;dpTable[1] = 1;for (int i = 2; i < dpTable.length; ++i)

dpTable[i] = dpTable[i−1] + dpTable[i−2];}public static long �bLoop(int n) {

if (n <= 1) return n;long a = 0, b = 1;for (int i = 2; i <= n; ++i) {

long c = b + a;a = b;b = c;

}return b;

}public static void main(String[] args) {

long time = System.nanoTime();System.out.println(�b(40));long time2 = System.nanoTime();System.out.printf("Elapsed = %fs\n",(time2−time)*1e−9);System.out.println(�bMemo(40));long time3 = System.nanoTime();System.out.printf("Elapsed = %fs\n",(time3−time2)*1e−9);buildTable();System.out.println(dpTable[40]);long time4 = System.nanoTime();System.out.printf("Elapsed = %fs\n",(time4−time3)*1e−9);System.out.println(�bLoop(40));long time5 = System.nanoTime();System.out.printf("Elapsed = %fs\n",(time5−time4)*1e−9);

}}

The method �b uses a standard recursion. It uses Θ(n) space but has an runtime

of Θ(�b(n)) which grows like(

1+√5

2

)n. Using the same memoization technique as

earlier we get �bMemo which uses Θ(n) space and Θ(n) runtime. We can also makean iterative version which computes each Fibonacci number from 0 up to n and storesthe result in dpTable. This also uses Θ(n) space and Θ(n) runtime. Finally we have a�bLoop method which uses Θ(1) space and Θ(n) runtime. This uses the fact that weonly need to track the previous two values. There is a faster method that uses Θ(1)space and Θ(log n) runtime using matrix exponentiation.

134

Page 135: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

2. Code follows:

Knapsack.java

public class Knapsack{

public static int maxValue(int[] weights, int[] values, int weightCap){

return maxValueHelp(weights,values,weightCap,0);}public static int maxValueHelp(int[] weights, int[] values,

int weightCap, int pos){

if (pos == weights.length) return 0;//don't take itemint best = maxValueHelp(weights,values,weightCap, pos+1);if (weights[pos] <= weightCap) {

//take itemint newCap = weightCap−weights[pos];int rec = maxValueHelp(weights, values, newCap, pos+1);best = Math.max(best, values[pos]+rec);

}return best;

}public static void main(String[] args){

int[] weights = {7, 7, 7, 15};int[] values = {8, 8, 8, 23};System.out.println(maxValue(weights,values,21));System.out.println(maxValue(weights,values,20));System.out.println(maxValue(weights,values,40));

}}

Here we simply explore the implicit tree which either takes or doesn't take everyelement. If the weightCap isn't too large we can also apply memoization here toe�ciently solve the problem.

3. Below we have the two implementations.

SizeUtils.java

public class SizeUtils {public static <T> int size(ArrayTreeNode<T> root) {

if (root == null) return 0;int ret = 1;for (int i = 0; i < root.getChildren().size(); ++i)

135

Page 136: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

ret += size(root.getChildren().get(i));return ret;

}public static <T> int size(ListTreeNode<T> root) {

if (root == null) return 0;return 1 + size(root.getFirstChild()) + size(root.getNextSibling());

}}

Note that in the ListTreeNode case no looping is necessary.

12.6 Data Structure: Binary Search Trees

Until this point trees have been used to express hierarchical structure in our data. For exam-ple, the tree structure of an expression tree conveys information about how the arithmeticshould be carried out. We now exploit the binary tree structure to facilitate faster datastorage and retrieval. As a result, we will �nd an e�cient implementation of maps, sets.

In a binary search tree we only store nodes that are Comparable or have a Comparator(i.e., they are ordered). For simplicity, we will force all tree nodes to have distinct values.All values in the left subtree of a node must have values strictly less than the node itself.Every value in the right subtree must be strictly larger than the node. This rule must holdat every node. As you will show below, this gives us an algorithm for �nding values in thetree that depends only on the height of the tree. Although searches do not require it, tofacilitate iteration and removal we will assume that binary search tree nodes each have aparent node reference.

When adding a value to a binary search tree, we �rst look for it. If we �nd it, we donothing. Otherwise, we add it in the leaf position where our search terminates. You will�esh this out in a moment. As with many other data structures, the trickiest operation ona binary search tree is removal. To remove a value we �rst �nd the node in the tree holdingthat value. If the node is a leaf we simply remove it. Otherwise, we break removal into casesas we will see below.

12.7 Binary Search Tree Exercises

1. Consider the numbers 1, 2, 3, 4, 5, 6, 7.

(a) Give two examples of binary search trees of height 6 using the above values.

(b) Give a binary search of height 2 using the above values.

2. How can you print the values of a binary search tree in ascending order recursively(very short answer)?

3. Use the binary search tree below in the following questions. Apply each operation tothe given tree (not in sequence).

136

Page 137: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

5

1 9

-4 6 13

7

(a) Add the value 3 to the tree.

(b) Add the value 11 to the tree.

(c) Remove the value 13 from the tree.

(d) Remove the value 1 from the tree.

(e) Remove the value 5 from the tree.

4. Implement the following BST functions.

(a) Implement the following function that �nds a node with the given value, or returnsnull.

static BSTNode<Integer> �ndNode(BSTNode<Integer> root, int value)

You can assume that a BSTNode has getLeft, getRight, and getValue.

(b) Give an implementation of the following static function that adds a value to thegiven BST.

static void add(BSTNode<Integer> root, int value)

5. (??) Describe an algorithm for removing a value from a BST. Break your algorithminto the following cases:

(a) Value isn't in the tree.

(b) Value is in a leaf node.

(c) Value is in a node with 1 child.

(d) (??) Value is in a node with 2 children (and thus, not the largest node in thetree). [Hint: Find next largest value.]

12.8 Binary Search Tree Solutions

1. (a)

137

Page 138: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

7

6

5

4

3

2

1

1

2

3

4

5

6

7

(b)

4

2 6

1 3 5 7

2. Perform an inOrder traversal.

3. (a)

5

1 9

-4 3 6

7

13

(b)

5

1 9

-4 6 13

7 11

(c)

5

1 9

-4 6

7

138

Page 139: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

(d)

5

-4 9

6

7

13

(e)

6

1 9

-4 7 13

4. The solutions to both parts are given below.

BSTUtils.java

public class BSTUtils{

public static BSTNode<Integer> �ndNode(BSTNode<Integer> root, int value){

if (root == null) return null;if (root.getValue() == value) return root;if (value < root.getValue()) return �ndNode(root.getLeft(),value);return �ndNode(root.getRight(),value);

}

public static void add(BSTNode<Integer> root, int value){

if (root.getValue() == value) return;if (value < root.getValue()){

if (root.getLeft() == null)root.setLeft(new BSTNode<>(value,null,null,root));

else add(root.getLeft(), value);}else

{if (root.getRight() == null)

root.setRight(new BSTNode<>(value,null,null,root));else add(root.getRight(), value);

}

139

Page 140: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

}}

Both methods have Θ(h) worst-case runtime, where h is the height of the tree. As-suming nothing about the BST, this is Θ(n) when the tree degenerates to a linkedlist.

5. (a) Do nothing.

(b) Remove the leaf node.

(c) Remove the node and replace with the child.

(d) Find the node s containing the next largest value (the successor). Note that thesuccessor will be in the right subtree of the node being removed, and will not havea left child. Let r denote the node we are removing. If s is the right child of rwe can just replace r with s. Otherwise, �rst replace s with the right child of s(freeing it up), and then replace r with s.

140

Page 141: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

13 Lecture 13: AVL Trees and Binary Heaps

13.1 Review Exercises

1. (??) Interview question: Given an array show how to shu�e it randomly so that anypossible reordering is equally likely.

static void shu�e(int[] arr)

2. Write a function that given the root of a binary search tree returns the node with thelargest value.

public static BSTNode<Integer> getLargest(BSTNode<Integer> root)

3. Explain how you would use a binary search tree to implement the Map ADT. We haveincluded it below to remind you.

Map.java

//Stores a mapping of keys to valuespublic interface Map<K,V> {

//Adds key to the map with the associated value. If key already//exists, the associated value is replaced.void put(K key, V value);//Gets the value for the given key, or null if it isn't found.V get(K key);//Returns true if the key is in the map, or false otherwise.boolean containsKey(K key);//Removes the key from the mapvoid remove(K key);//Number of keys in the mapint size();

}

What requirements must be placed on the Map?

4. Consider the following binary search tree.

10

5 15

1 12 18

16

17

141

Page 142: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

Perform the following operations in order.

(a) Remove 15.

(b) Remove 10.

(c) Add 13.

(d) Add 8.

5. Suppose we begin with an empty BST. What order of add operations will yield thetallest possible tree?

13.2 Review Solutions

1. One method (popular in programs like Excel) is to generate a random double corre-sponding to each element of the array, and then sort the array by the correspondingdoubles. Here is a di�erent method that avoids sorting (used by Collections.shu�e).

RandomShu�e.java

import java.util.Random;

public class RandomShu�e{

static void swap(int[] arr, int i, int j){

int tmp = arr[i];arr[i] = arr[j];arr[j] = tmp;

}//Iterative implementationpublic static void shu�e(int[] arr){

Random ran = new Random();for (int i = arr.length−1; i >= 1; −−i) swap(arr,i,ran.nextInt(i+1));

}//Recursive implementationpublic static void shu�eRec(int[] arr){

sRHelp(arr, arr.length, new Random());}public static void sRHelp(int[] arr, int len, Random ran){

if (len <= 1) return;swap(arr, len−1, ran.nextInt(len));sRHelp(arr,len−1,ran);

}}

142

Page 143: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

Above we give an iterative and a recursive implementation. We �rst randomly chooseone of the elements and swap it into the �nal position. Then we repeat the process onthe �rst n− 1 elements (i.e., randomly choose last element, and then randomly shu�e�rst n− 1 elements).

2. public static BSTNode<Integer> getLargest(BSTNode<Integer> root) {while (root.getRight() != null) root = root.getRight();return root;

}

3. We require the keys of the Map to be Comparable or a Comparator to be provided.In each node instead of simply storing a value, store a key-value pair (i.e., an entry).The BST will be ordered by the keys. Here all operations above will be Θ(h) in theworst case (we don't have containsValue above which would always be Θ(n)). In amoment, we will show how to achieve Θ(lg n) height trees which gives an e�cient Mapimplementation without needing a hash function (but we need ordered keys).

4. (a)

10

5 16

1 12 18

17

(b)

12

5 16

1 18

17

(c)

12

5 16

131 18

17

(d)

143

Page 144: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

12

5 16

131 8 18

17

5. Ascending or descending order (i.e., sorted or reverse sorted).

13.3 Data Structure: AVL Tree

We have learned how to store values in a BST data structure that supports adding, removal,and searching in time Θ(h), where h is the height of the tree. We have also seen that inthe worst case, the height can be Θ(n), the size of the tree. If we can keep the tree fairlybalanced, then we can reduce the height to Θ(lg n) and obtain a fairly e�cient data structure.

To maintain a balanced structure we will use a technique invented by Adelson-Velskiiand Landis (hence the name AVL tree). The idea is to keep a counter in every node (called abalance factor) that measures the di�erence between the heights of the left and right subtrees(right minus left). We will add the following added constraint on our BST

� No balance factor will be greater than 1 or less than -1.

Any BST that satis�es this constraint will have Θ(lg n) height. Every time we add orremove a node we may violate this constraint. To return the tree to balance we will employa technique called a rotation. Consider the following AVL tree:

10+1

5

-1

15+1

1

0

12

0

20

-1

17

0

Each node has the value and balance factor in it. Now suppose we add 16 (just as we wouldin a BST).

144

Page 145: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

10+2

5

-1

15+2

1

0

12

0

20

-2

17

-1

16

0

As you can see, the tree now violates our constraints on the balance factors. To remedy theissue, we �nd the closest (lowest) ancestor of the newly added node that is out of balance.In this case, it would be the node containing the 20. We then perform a rotation so that 17takes the place of 20, and 20 becomes the right child of 17. The resulting tree is

10+1

5-1

15+1

10

120

170

160

200

Now the tree satis�es the constraint again. Let's generalize this example to all possible waysadding a node can imbalance the tree. First notice that if adding a value will cause a node toviolate its balance factor constraint, then it must have been −1 or +1 already, since addinga single node can add at most 1 to the height of any subtree. Let's assume a node currentlyhas balance factor −1 and some value X. We will model the possible ways that the nodecontaining X will be the lowest node that violates the balance factor constraint (by addingvalues).

145

Page 146: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

X-1

Y0

Ak B k

C k

Above Y < X and all of the labeled subtrees have the same height k ≥ 0. The subtreerooted at X has height k + 1. We �rst handle the case that a node is added to subtree Athat increases its height.

X-2

Y-1

Z

A1 A2

B k

C k

Here we assumed the value added was smaller than Y . Note that the subtree rooted atX now has height k + 2. We then zoom in on the subtree A by drawing its root Z andsubtrees A1 and A2. The possible heights of A1, A2 are (this will not e�ect our course ofaction though):

1. If k = 0 then A1, A2 are both empty. Z has a balance factor of 0

2. If k > 0 and the new value is smaller than Z then A1 has height k and A2 has heightk − 1. Z has a balance factor of −1.

3. If k > 0 and the new value is greater than Z then A1 has height k − 1 and A2 hasheight k. Z has a balance factor of +1.

To �x this we perform a rotation that puts Y where X is:

146

Page 147: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

Y0

Z X0

A1 A2 B C k

The balance constraints are no longer violated. Note that the subtree rooted at Y has heightk + 1, the same as the subtree rooted at X before a value was added. Thus all nodes aboveY in the tree are now balanced as well. This is the so called �left-left� case since Z is theleft child of Y which is the left child of X. The other �left-right� case occurs when we add avalue that is larger than Y to our original tree (the picture with A, B, and C) above.

X-2

Y+1

W

Ak

B1 B2

C k

Here we have zoomed in on the subtree B. We have similar cases as above that determinethe heights of B1, B2. Here we perform a rotation that puts W in place of Y :

X-2

W

Y

Ak B1

B2

C k

This gives us a case like the �left-left� situation. Thus we perform a second rotation as aboveputting W in place of X:

147

Page 148: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

W0

Y X

Ak B1 B2 C k

Again we have restored balance and the resulting tree has height k + 1. Thus all ancestorsof W are balanced too. Using the operations above and their re�ections (�right-right� and�right-left� cases) we can enforce the balance constraints on every add operation.

The TreeMap and TreeSet in Java use a related data structure called a red-black treethat uses a slightly di�erent constraint for balance, but also uses rotations to enforce theconstraint.

13.4 AVL Tree Exercises

1. Consider the following BST.

10

5 15

6 12

(a) Add balance factors to all of the nodes.

(b) Show what happens when we add the node 13 (treating the tree as an AVL tree).

(c) Next add 7 to the tree (treating the tree as an AVL tree).

(d) (?) Next delete 5, 7, and 6 from the tree. Make sure to remedy any violatedbalance constraints.

2. Give a simple Θ(n lg n) sorting algorithm assuming you have access to an AVL Treedata structure.

3. (??) The AVL Tree deletion algorithm is similar to the addition algorithm, but cancause as many as Θ(h) rotations to occur where h is the height of the tree. Can youexplain why?

4. (?) Let minNodes(h) denote the minimum number of nodes you need to have an AVL-Tree of height h. For h >= 2 give a recurrence relation satis�ed by minNodes(h).

148

Page 149: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

5. In the Priority Queue ADT elements have an ordering (Comparable or Comparator).When you dequeue an element, instead of removing the earliest added element, youremove the earliest element in the given ordering. You can imagine a situation whereyou are ordering tasks to work on, but each task has a priority governing when youmust work on it. It has the following operations

(a) add: Adds an element to the Priority Queue

(b) dequeue: Removes the smallest element (with respect to the ordering)

Describe an e�cient implementation of this ADT.

13.5 AVL Tree Solutions

1. (a)

10

0

5

+1

15

-1

6

0

12

0

(b) We perform the �left-right� sequence of 2 rotations after adding 13.

10

0

5

+1

13

0

6

0

12

0

15

0

(c) After adding 7 we perform the �right-right� rotation which is simply the re�ectionof the �left-left� rotation.

149

Page 150: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

10

0

6

0

13

0

5

0

7

0

12

0

15

0

(d) After the removal the root is unbalanced. We will treat this like the �right-right�case and rotate the 13 into the root.

13

-1

10

+1

15

0

12

0

2. Add all of the elements to the AVL Tree, then perform an inOrder traversal. If we wantto handle duplicates, we can either extend our AVL Tree/BST to allow duplicates, orinstead of storing values, we can store lists of values that are equivalent with respectto the ordering. This method of sorting isn't used.

3. Note that after an add operation, our rotations returned the e�ected subtree to thesame height it was before the add operation. Thus no ancestors above the lowestunbalanced node had to be �xed. After a remove operation the e�ected subtree mayhave a lower height than before, and thus ancestors of the lowest unbalanced node mayneed to be �xed as well.

4. minNodes(h) = 1 + minNodes(h − 1) + minNodes(h − 2). To see why, note that wemust have a subtree of height h− 1 so that the whole tree has height h. Secondly, thesmallest we can make the other subtree is height h− 2 due to the balance constraint.This can be used to show that minNodes grows faster than the Fibonacci sequence,which grows exponentially. This in turn can be used to show that the height of anAVL tree is Θ(lg n).

5. Use an AVL Tree. Adds simply add nodes to the tree. To dequeue we simply removethe smallest value. Both operations require Θ(lg n) in the worst-case.

150

Page 151: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

13.6 Data Structure: (Binary) Min Heap

Above we saw how to implement a PriorityQueue using an AVL Tree. Here we will give anew data structure that can be used to implement a PriorityQueue and is way more e�cientin practice (lower constants embedded in the Θ-terms). We will allow duplicates values.

A min heap is a binary tree with the following two constraints:

1. Ordering: Every node is equal to or smaller than its children.

2. Completenees: Every level of the tree is full but the last level. In the last level thenodes �ll up the leftmost positions.

The second constraint sounds odd, but it will enable a very e�cient implementation. Insteadof using binary tree nodes to store our values, we will just use an array containing all of theelements as they would appear in a level-by-level (breadth �rst) traversal.

Consider the following min heap:

4

5 7

9 12 9

We then store this in an ArrayList.

4

0

5

1

7

2

9

3

12

4

9

5

The nice thing about this format is that we can easily �nd the children and parent of anynode. Suppose you are at the node with index k in the array.

1. Left child: 2k + 1

2. Right child: 2k + 2

3. Parent: (k − 1)/2 (Java integer division; gives 0 on root)

We will justify the left child formula. The rest will follow from that. Note that level of nodesat depth d contains the indices 2d− 1 through 2d+1− 2. Thus the kth node in that level hasindex 2d − 1 + (k − 1). Applying the left child formula, we obtain index

2(2d − 1 + (k − 1)) + 1 = 2d+1 − 1 + 2(k − 1).

This is the index in level d + 1 just after the 2(k − 1) children of the nodes preceding ouroriginal node in level d.

151

Page 152: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

The nice formulas above are made possible by the array storage format. Since our minheaps have the completeness property, the array format isn't wasteful.

What remains is to implement the two main operations of the min heap: add and re-moveMin. The key to all heap operations is to �rst guarantee that the completeness propertyholds. After the �shape� is correct, we then make a few updates to �x the ordering constraint.Suppose we want to add the node 1 to our heap above. We �rst add 1 in the next availablespot in the lowest level:

4

5 7

9 12 9 1

Next we need to �x the ordering constraint. We use an operation called �sift-up�. Takethe newly added node an compare it with its parent. If it is smaller, swap, and repeat theprocess on the parent. This is depicted below.

4

5 7

9 12 9 1

1

5 4

9 12 9 7

To remove the mininum we �rst swap the top value with the last value. Then we can safelyremove the last value and maintain the shape. Finally, we correct the ordering constraintby checking if the root is bigger than its smallest child. If so, swap and then repeat on thenode you swapped with. This process is sometimes called �sift-down�.

13.7 Min Heap Exercises

1. Consider the following min heap.

5

9 6

14 11 7 8

15 16 12 15

(a) What is the index of 11 in the corresponding array?

(b) Add an 8 to the min heap.

(c) Then add a 1 to the min heap.

(d) Then removeMin from the min heap.

152

Page 153: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

(e) Then removeMin from the min heap.

2. A max heap is just like a min heap, but each node must be larger than its children.Explain why a max heap data structure is unnecessary if you already have a min heap.

3. What are the worst-case runtimes of add and removeMin?

4. Assuming you have access to a min-heap, show how to sort a list of Comparable valuesin worst-case time Θ(n lg n).

5. How long does it take to �nd a value in a min heap?

6. (?) Sometimes it is useful to remove an arbitrary element from a min heap given itsindex. Explain how to do this in worst-case Θ(lg n) time.

7. (??) Given an array of n comparable values, show how to turn it into a min heap.There is a Θ(n) worst-case implementation.

13.8 Min Heap Solutions

1. (a) 4

(b) No sifting is required.

5

9 6

14 11 7 8

15 16 12 15 8

(c) Here we must sift-up performing 3 swaps.

1

9 5

14 11 6 8

15 16 12 15 8 7

(d) We swap the 7 into the root, remove the 1, and then sift-down (swap 7 with 5then 6).

5

9 6

14 11 7 8

15 16 12 15 8

153

Page 154: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

(e) We swap the 5 with the 8, remove the 5, and then sift-down (swap 8 with 6 then7).

6

9 7

14 11 8 8

15 16 12 15

2. Just use a min heap but reverse the ordering.

3. Both are Θ(lg n) since our tree is always well-balanced.

4. Add all elements to a min heap, and then repeatedly call removeMin to pull them outin order. This is called Heapsort (better than our AVL Tree sorting algorithm abovedue to the low constant on all heap operations).

5. Θ(n) in the worst-case (consider a really large value that isn't in the heap) since theheap ordering property doesn't aid in searches like the BST property does.

6. We use the following steps.

(a) Swap the item to be removed with the last item and then remove the last item.

(b) The swap may have broken the ordering property so:

i. Check if the swapped item is smaller than its parent. If so, do the sift-upprocedure on it.

ii. Otherwise, do the sift-down procedure on it.

7. The slow method is to just call add n times giving a worst-case Θ(n lg n) runtime. Abetter method is to loop backwards through the array and run the sift-down procedureon every value. To see why the runtime is Θ(n) we consider the work done by sift-downat every node. For simplicity, let's assume every level of the heap is full. Let the heightof a node be the height of the subtree it is the root of. All nodes will require at most Csteps to compare them with their children in the sift-down procedure. Nodes of heightat least one will require at most an extra C steps, since they they could undergo aswap. Nodes of height at least two will require an extra C steps on top of that, and soforth. But, each time we increase the height we halve the number of nodes we consider.Since

Cn + Cn/2 + Cn/4 + · · · = 2Cn = Θ(n)

the result follows.

154

Page 155: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

14 Lecture 14: Basic Sorting

14.1 Review Exercises

1. How do you declare a balanced search tree based map in Java with key String andvalue Integer (i.e., create a variable of that type)? How do you declare a max heap inJava of Integers?

2. Give an algorithm for getting the kth largest element of a list of numbers.

3. Start with an empty AVL tree and add the following values: 1,2,3,4,5,6,7. What doesthe �nal AVL tree look like?

4. (Hard Interview Question) You are watching the price of a �nancial instrument overtime. You have its price after each second over several days (given in an array p oflength n). At each second s you want to know what its highest price was over theprevious k seconds. Output an array of size n − k + 1 of the various maxes. Moreprecisely, the �rst entry should be the max of the prices with indices 0, . . . , k− 1 in p.The second entry will be the max of the prices with indices 1, . . . , k in p, and so forth.

public static double[] maxes(double[] prices, int k)

(a) (??) Give a Θ(n log k) implementation.

(b) (? ? ?) Give a Θ(n) implementation.

14.2 Review Solutions

1. Tree-based map:

TreeMap<K,V> map = new TreeMap<>();!

Max heap:

PriorityQueue<Integer> pq =new PriorityQueue<>(Collections.reverseOrder());

2. One algorithm maintains a min heap of size k. Each time we add the next element tothe heap we removeMin to return the heap to size k. At the end the minimum elementis the kth largest. This gives a runtime of Θ(n lg k) and uses Θ(k) memory.

A faster algorithm is to make a max heap out of all of the numbers, and removeMax ktimes. Runtime is Θ(n + k lg n) (since making the heap is Θ(n)) but the space usageis Θ(n). We will see a di�erent way to do this related to quicksort later.

3.

155

Page 156: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

4

0

2

0

6

0

1

0

3

0

5

0

7

0

4. (a) We keep a sliding window of width k. We also keep a max heap that stores all ofthe elements of the window. When we slide the window we must add the next priceand remove the last price. To remove a speci�c item from the heap we require aspecial heap that gives us handles into it (like node references). This is sometimescalled an augmented heap. When you add an element to the heap you are given aHandle object. The heap maintains an array of Handle objects, one for each heapelement (that is, the data in the heap array has type Handle). Each Handle objecthas a value �eld, and an index �eld that says where the corresponding value liesin the heap array. Every time we swap in sift-up or sift-down we will update thecorresponding handle indices. We then implement a remove method that takesa Handle and allows for Θ(lg k) removal, where k is the heap size. If we wantto support a safer interface, we can invalidate handles when the correspondingobject is removed by setting the index to −1.

(b) This implementation is similar, but harder than our implementation of a stackthat let's us retrieve the max in Θ(1) time. We will maintain 2 structures, a queueof size k that holds the sliding window, and a deque of maxes. When an elementis added to the window, we check if it is larger than the front of the deque ofmaxes. While it is strictly larger than the front of the deque of maxes we removethe front of the deque. Then we add the new element to the front of the deque.When we remove an element from the sliding window, we check the back of thedeque, and remove it from the back of the deque if it matches. Using this systemtwo properties are maintained:

i. The deque of maxes is always in descending order from back to front.

ii. The back of the deque of maxes always has the maximum value in the slidingwindow.

The �rst property above gives this data structure/algorithm its name, the mono-tonic queue. Note that a single add can cause up to k removals from the deque.That said, when processing the entire array we will remove at most n values fromthe deque. Due to this amortized analysis we see the worst-case runtime is Θ(n).

156

Page 157: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

14.3 Other Trees

We will not be covering Tries or B-Trees in the course, but they are worth looking into ifyou are curious.

14.4 Basic Sorting: Selection, Insertion and Bubble Sort

Our �rst sort we will look at is selection sort. It works by �nding the maximum element ofan array, swapping it into the last position, and then repeats that process on the �rst n− 1elements of the array. That is, it then �nds the second largest element, and swaps it intothe next to last position, and so forth.

We have already discussed the idea of insertion sort earlier in the class. It uses theconcept of inserting a new element into an already sorted list. Given any list of numbers,if we focus on the sublist consisting only of its �rst element we trivially obtain a sortedlist. Insertion sort then inserts the second element into this sorted list so that the �rst twoelements form a sorted list. Then it inserts the third, and so forth.

Our �nal basic sorting algorithm is bubble sort. We give a naive implementation below:

public static void swap(int[] arr, int a, int b) {int tmp = arr[a];arr[a] = arr[b];arr[b] = tmp;

}public static void bubbleSort(int[] arr) {for (int i = 0; i < arr.length; ++i) {for (int j = 1; j < arr.length; ++j) {if (arr[j]<arr[j−1]) swap(arr,j−1,j);

}}

}

14.5 Basic Sorting Exercises

1. Consider the array {4,3,2,1}.

(a) Show step-by-step what selection sort does on it.

(b) Show step-by-step what insertion sort does on it.

(c) Show step-by-step what bubble sort does on it.

2. (a) Implement �ndMax which returns the index of the largest element in an arraybetween indices a and b, inclusive.

public static int �ndMax(int[] arr, int a, int b)

(b) Implement selection sort. What is the runtime?

157

Page 158: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

public static void selectionSort(int[] arr)

(c) Implement insertion sort. What is the runtime?

public static void insertionSort(int[] arr)

3. Is there a way to greatly reduce the worst-case number of comparisons in insertionsort?

4. (?) Prove that bubble sort actually sorts.

5. Give a slightly faster implementation of bubble sort that shortens the loops above.

14.6 Basic Sorting Solutions

1. The steps are below.

(a) Selection sort

4

0

3

1

2

2

1

3

1

0

3

1

2

2

4

3

1

0

2

1

3

2

4

3

1

0

2

1

3

2

4

3

(b) Insertion sort

4

0

3

1

2

2

1

3

3

0

4

1

2

2

1

3

2

0

3

1

4

2

1

3

1

0

2

1

3

2

4

3

158

Page 159: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

(c) Bubble sort

4

0

3

1

2

2

1

3

3

0

2

1

1

2

4

3

2

0

1

1

3

2

4

3

1

0

2

1

3

2

4

3

2. The implementations of all parts are given below.

Sorts.java

import java.util.Arrays;

public class Sorts{

public static void swap(int[] arr, int a, int b){

int tmp = arr[a];arr[a] = arr[b];arr[b] = tmp;

}//Returns index of max value between indices//a and b, inclusivepublic static int �ndMax(int[] arr, int a, int b){

int maxI = a;for (int i = a+1; i <= b; ++i)

if (arr[i] > arr[maxI]) maxI = i;return maxI;

}public static void selectionSort(int[] arr){

for (int i = arr.length−1; i >= 1; −−i)swap(arr,i,�ndMax(arr,0,i));

}//Inserts value at index i into the sorted array//in indices 0,...,i−1public static void insert(int[] arr, int i)

159

Page 160: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

{int j = i;while (j > 0 && arr[i] < arr[j−1]) j−−;int v = arr[i];for (int x = i; x > j; −−x) arr[x] = arr[x−1];arr[j] = v;

}public static void insertionSort(int[] arr){

for (int i = 1; i < arr.length; ++i)insert(arr,i);

}public static void bubbleSort(int[] arr){

for (int i = 0; i < arr.length−1; ++i)for (int j = 1; j < arr.length − i; ++j)

if (arr[j−1] > arr[j])swap(arr,j−1,j);

}public static void main(String[] args){

int[] arr1 = {2,4,1,5,9,7,3,10,6,8};int[] arr2 = Arrays.copyOf(arr1, arr1.length);int[] arr3 = Arrays.copyOf(arr1, arr1.length);selectionSort(arr1);insertionSort(arr2);bubbleSort(arr3);System.out.println(Arrays.toString(arr1));System.out.println(Arrays.toString(arr2));System.out.println(Arrays.toString(arr3));

}}

Selection sort and insertion sort both have a Θ(n2) wosrt-case runtime. In the best-caseinsertion sort can be Θ(n) but selection sort is always Θ(n2).

3. Binary search for the insertion point.

4. After the kth iteration of the outer loop, the last k elements of the array are in theircorrect positions.

5. Implemented above with the other sorts.

160

Page 161: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

15 Lecture 15: More Sorting

15.1 Review Exercises

1. Given 2 sorted lists, return the number of elements they have in common.

public static int numShared(int[] a, int[] b)

2. Given 2 sorted lists, return a new sorted list that contains the elements in both lists.

public static int[] merge(int[] a, int[] b)

3. Given an array arr of length n consider all pairs of distinct indices We consider thepair i, j an inversion if i < j and arr[i]>arr[j]. Note there are

(n2

)pairs i, j with i < j.

(a) What is the minimum possible number of inversions? How do you achieve thisvalue?

(b) What is the maximum possible number of inversions? How do you achieve thisvalue?

(c) (?) One of the sorts we learned has runtime that is Θ(m + n) where m is thenumber of inversions in the array. Which algorithm is it?

(d) (??) Suppose you randomly shu�e an array of n distinct integers. How manyinversions do you expect it to have?

4. For each of the following, determine whether the given sort is stable. Recall that asort is stable if elements that are equal with respect to the ordering are left in theiroriginal relative order.

(a) Selection sort.

(b) Insertion sort.

(c) Bubble sort.

5. Show how to force any sort to be stable by changing the data type.

15.2 Review Solutions

1. Code follows:

public static int numShared(int[] a, int[] b) {int i = 0, j = 0, cnt = 0;while (i < a.length && j < b.length) {if (a[i] == b[j]) {cnt++;i++;j++;

161

Page 162: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

}else if (a[i] < b[j]) i++;else j++;

}}

The runtime is Θ(m + n) where m is the length of the �rst list and n is the length ofthe second.

2. Code follows:

public static int[] merge(int[] a, int[] b) {int m = a.length, n = b.length;int[] ret = new int[m+n];int i = 0, j = 0;for (int k = 0; k < m+n; ++k) {if (i >= m) ret[k] = a[j++];else if (j >= n || a[i] <= b[j]) ret[k]=a[i++];else ret[k] = a[j++];

}return ret;

}

The runtime is Θ(m + n) where m is the length of the �rst list and n is the length ofthe second.

3. (a) 0 if the array is sorted.

(b)(n2

)if the array is reverse sorted.

(c) Insertion sort.

(d) Using concepts from probability, each pair is equally likely to be an inversion ornot, so the result is 1

2

(n2

). More precisely, let Xij be the indicator variable that

the pair i, j is an inversion. Then apply linearity of expectation.

4. (a) Not stable: 10, 1, 1. The �rst step swaps the 10 and the second 1 breaking stability.

(b) Yes, by never swapping equal items (or by never inserting before an item of equalvalue).

(c) Yes, by never swapping equal items.

5. Make each element a pair of the initial value, and the initial index. Then make theComparator use the initial ordering, but defer to the index for equal items.

15.3 Merge Sort

Earlier we saw that we can merge two sorted lists of length m and n into a sorted list oflength m + n in Θ(m + n) time. We can use this procedure to design a sorting algorithmcalled merge sort. The idea is as follows:

162

Page 163: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

1. Split list evenly (or o� by 1) into two halves: left and right.

2. Recursively call merge sort on each half.

3. Merge the halves together to produce a full sorted array.

This style of algorithm is called divide-and-conquer. To analyze the runtime, we can considerthe implicit binary tree lurking in the background (called a recursion tree). Let each call tomerge sort denote a node of the tree. Each call splits the array into a left and right half, andthus creates two children for a given node. Excluding the recursive call, each call to mergesort does Θ(n) work in the merging step. Thus Θ(n) work is done by each level of the tree.The tree will have Θ(lg n) levels giving a runtime of Θ(n lg n).

Below we give an implementation that uses Θ(n) extra space. To do this we try tomanage memory a bit more e�ciently.

MergeSort.java

import java.util.Arrays;import java.util.Random;

public class MergeSort{

//Merge sorted lists in data[L..M] and data[M+1..R]//using tmp as temporary storagepublic static void merge(int[] data, int L, int M, int R, int[] tmp){

int N = R−L+1;int a = L, b = M+1;for (int i = 0; i < N; ++i){

if (a > M) tmp[i] = data[b++];else if (b > R || data[a] <= data[b]) tmp[i] = data[a++];else tmp[i] = data[b++];

}for (int i = 0; i < N; ++i) data[L+i] = tmp[i];

}public static void mergeSort(int[] data){

mergeSort(data,0,data.length−1, new int[data.length]);}//Merge sort data[L..R], tmp is auxiliary storagepublic static void mergeSort(int[] data, int L, int R, int[] tmp){

if (L == R) return;int M = (L+R)/2; // M<RmergeSort(data,L,M,tmp);mergeSort(data,M+1,R,tmp);

163

Page 164: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

merge(data,L,M,R,tmp);}

public static void main(String[] args){

Random ran = new Random();int[] arr = {1,2,3,4,5,6,7,8,9,10};ArrayUtils.shu�e(arr, ran);System.out.println(Arrays.toString(arr));mergeSort(arr);System.out.println(Arrays.toString(arr));

}}

Note that unlike selection, insertion, bubble and heap sort, merge sort requires extra memoryto do the sorting. We say that the other sorts are in-place but merge sort isn't. To see thatheap sort can be done in-place, note that we can just treat the original array as our heap'sdata array.

Often, once the lists get small enough, some implementations of merge sort will changeto insertion sort which can run faster on small lists (like smaller than 7).

15.4 Merge Sort Exercises

1. Show step-by-step how merge sort orders the elements of 8, 7, . . . , 2, 1.

2. Is merge sort stable?

3. (?) Explain how to augment the merge sort algorithm to also compute the number ofinversions in the array.

4. (??) Give an algorithm that merges k sorted lists of length n into a combined sortedlist of length kn in Θ(kn log k) time.

5. Given an array arr and an index (called the pivot) order the array so all elements lessthan or equal to the pivot are on its left side, and all elements greater than the pivotare on its right side. Return the new location of the pivot (which will likely need tomove to obtain the above property). Your runtime should be Θ(n) but you may useextra space.

For example, if the list begins as 5, 4, 3, 2, 1 and index 3 (the value 2) is the pivot thenyou can reorder the array to be 1, 2, 5, 4, 3 with the pivot now at index 1.

public static int partition(int[] arr, int pivot)

164

Page 165: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

15.5 Merge Sort Solutions

1. Depicted below is the implicit tree existing in the background of merge sort.

arr[0..7]

arr[0..3] arr[4..7]

arr[0..1] arr[2..3] arr[4..5] arr[6..7]

arr[0] arr[1] arr[2] arr[3] arr[4] arr[5] arr[6] arr[7]

Merge sort does a postorder traversal of this tree. Here are the steps.

80

71

62

53

44

35

26

17

70

81

62

53

44

35

26

17

70

81

52

63

44

35

26

17

merge

50

61

72

83

44

35

26

17

50

61

72

83

34

45

26

17

50

61

72

83

34

45

16

27

merge

50

61

72

83

14

25

36

47

merge

10

21

32

43

54

65

76

87

2. Yes. The key step that ensures stability is that we prefer the left list to the right whenwe merge two sorted lists.

3. At a high level, each inversion in the list occurs in the left half, the right half, or crossesthe middle. Recursively we compute the inversions in each half. The middle crossinginversions are computed in the call to merge.

165

Page 166: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

Here we describe the code more precisely. Inversions are detected when merge choosesan element of the right list over the left list. Each time an element of the rightlist is chosen we add k to the total number of inversions, where k is the numberof remaining elements in the left list. We then augment merge to return the totalnumber of inversions it �nds. We augment mergeSort to sum the inversions from itstwo recursive calls, and the call to merge, and then returns the sum.

4. Build a min heap of the lists that uses the �rst unmerged element for the comparisons(you can store an index/iterator with each list of what element it is up to). Each stepof the merge you call removeMin to obtain the list containing the smallest unmergeditem. After using the item, if there are still unused elements in that list, re-add thelist to the min heap.

Another solution simply pairs up the lists and merges them, and repeats this processtrying to merge lists of equal length (as if you were midway through the merge sortalgorithm on your way up the implicit tree).

5. A simple implementation constructs two new lists containing the elements below thepivot and greater the pivot and then copies them back into the original array. Here wegive an implementation using Θ(1) memory.

public static int partition(int[] arr, int pivot) {int pv = arr[pivot];swap(arr,0,pivot);int i = 1, j = arr.length−1;while (i <= j) {if (arr[i] <= pivot) i++;else if (arr[j] > pivot) j−−;else swap(arr,i++,j−−);

}swap(arr,0,i−1);

}

The code above partitions in-place and has a Θ(n) runtime.

15.6 Quick Sort

The idea of quick sort is to repeatedly call the partition function above. The process is asfollows:

1. Choose a pivot index (somehow) and partition the array around it.

2. Recursively call quick sort on the left and right parts of the partition.

If the pivots are chosen well (close to the median) then the left and right parts will be roughlyhalf of the remaining values, and we can get a Θ(n lg n) runtime as we did in merge sort. If

166

Page 167: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

the partitions are chosen poorly then the runtime can deteriorate to Θ(n2). The issue is thatapriori we don't know where the median is. Here are some potential choices for the pivot:

1. Choose the �rst element in the list.

2. Take the �rst, middle, and last element and choose their median (called median-of-three).

3. Randomly choose an element from the list.

4. Find the actual median and use it.

The �rst element is quick and easy to choose, but it also can deteriorate on some simplecases. Median-of-three does better on many arrays, but can still deteriorate, especially ifthe list to sort is supplied by an adversary. Random choice can defeat an adversary, andhas an expected runtime of Θ(n lg n), but this is only an average case. Sometimes it willdeteriorate to Θ(n2). There is a somewhat complex algorithm that will �nd the median of alist of numbers in linear time. If used, this will guarantee a worst-case runtime of Θ(n lg n)for quick sort. That said, this method has a very large constant associated with it, and israrely if ever used.

Below we give an implementation of quick sort:

QuickSort.java

import java.util.Arrays;import java.util.Random;

public class QuickSort{

public static void swap(int[] arr, int i, int j){

int tmp = arr[i];arr[i] = arr[j];arr[j] = tmp;

}//Partitions arr[L..R] around pivot in location ppublic static int partition(int[] arr, int L, int R, int p){

int pivot = arr[p];swap(arr,p,L); //Put pivot in �rst spotint i = L+1, j = R;while (i <= j){

if (arr[i] <= pivot) i++;else if (arr[j] > pivot) j−−;else swap(arr,i++,j−−);

}

167

Page 168: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

//Now i points after last element <= pivotswap(arr,L,i−1);return i−1;

}public static void quickSort(int[] arr, Random ran){

quickSort(arr, 0, arr.length−1, ran);}public static void quickSort(int[] arr, int L, int R, Random ran){

if (L>=R) return;int p = ran.nextInt(R−L+1)+L;p = partition(arr, L, R, p);quickSort(arr,L,p−1,ran);quickSort(arr,p+1,R,ran);

}public static void main(String[] args){

Random ran = new Random();int[] arr = {1,2,3,4,5,6,7,8,9,10};ArrayUtils.shu�e(arr, ran);System.out.println(Arrays.toString(arr));quickSort(arr,ran);System.out.println(Arrays.toString(arr));

}}

Note that the implementation above is in-place (since partition is in-place), but is unsta-ble (since partition is unstable). As with merge sort, quick sort implementations will oftenchange to insertion sort once the list size gets small enough.

The Java API uses variant of quicksort with 2 pivots (dual-pivot quicksort) for sortingprimitive arrays and a variant of merge sort for objects (called Timsort). Some C++ STLsort implementations use introsort, a variant of quicksort that will change to heapsort ifthere are too many poor pivot choices in a row (indicating a weird adversarial case).

15.7 Quick Sort Exercises

1. Run quick sort on 5, 1, 7, 4, 3, 2, 6 where you always choose the �rst element of eachsublist as the pivot. You can partition using whichever implementation you want.

2. Suppose the �rst element is your choice of pivot. Give an example of a list of length nthat gives Θ(n2) runtime.

3. (??) Show how to use partition to �nd the kth smallest element of a list in expectedlinear time (called quick select). Your code will modify the underlying array.

168

Page 169: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

4. (??) Bogosort is an algorithm that keeps randomly shu�ing the array until it is sorted:

while(!isSorted(arr)) shu�e(arr);

Given an array that isn't sorted, what is the expected runtime of Bogosort?

15.8 Quick Sort Solutions

1. The partitions performed are shown below.

5

0

1

1

7

2

4

3

3

4

2

5

6

6

pivot value = 5

3

0

1

1

2

2

4

3

5

4

7

5

6

6

pivot value = 3

2

0

1

1

3

2

4

3

5

4

7

5

6

6

pivot value = 2

1

0

2

1

3

2

4

3

5

4

7

5

6

6

pivot value = 7

1

0

2

1

3

2

4

3

5

4

6

5

7

6

2. For the partition code above (and most implementations of partition), a sorted list orreverse sorted list will give Θ(n2) runtime.

3. The idea is that after partition is called, the pivot is in its correct place in the sortedlist. Then, similar to binary search, you can choose which side to continue to searchon. Code follows:

QuickSelect.java

import java.util.Arrays;import java.util.Random;

public class QuickSelect {//Selects element k (in sorted order)

169

Page 170: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

//Modi�es arrpublic static int quickSelect(int[] arr, int k,

Random ran){

return quickSelect(arr,k,0,arr.length−1,ran);}public static int quickSelect(int[] arr, int k,

int a, int b, Random ran){

int n = b−a+1;if (n == 1) return arr[a];int p = a+ran.nextInt(n);p = QuickSort.partition(arr, a, b, p);if (p−a == k) return arr[p];if (p−a > k)

return quickSelect(arr,k,a,p−1,ran);else

return quickSelect(arr,k−(p−a+1),p+1,b,ran);}public static void main(String[] args){

Random ran = new Random();int[] arr = {1,2,3,4,5,6,7,8,9,10};ArrayUtils.shu�e(arr, ran);System.out.println(Arrays.toString(arr));for (int k = 0; k < arr.length; ++k){

if (k > 0) System.out.print(", ");int[] tmp = Arrays.copyOf(arr, arr.length);System.out.print(quickSelect(tmp,k,ran));

}System.out.println();

}}

Similar to quick sort, this is Θ(n2) in the worst-case.

4. Essentially we keep �ipping a coin that has a 1/n! chance of coming up heads. Theexpected number of �ips is n!. Each �ip requires Θ(n) runtime, so the total expectedruntime is Θ(n · n!). To see why n! iterations are expected, let X denote the numberof iterations (a random variable). Then we have

E[X] = 1 +n!− 1

n!E[X]

giving E[X] = n!.

170

Page 171: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

15.9 Lower Bounds on Sorting

Here we sketch the argument that if all you have is an array of Comparables (or a Com-parator) you cannot do better than n lg n in the worst case. Suppose we have an array ofn distinct items. It can have any one of n! di�erent orderings. Each time we perform acomparison of two items a, b in the list, we divide the orderings into two piles: orderingsconsistent with a < b and orderings consistent with b < a. In the worst case, we will end upwith the larger pile. Thus to minimize our worst-case runtime we will try to make the pilesas close to even as possible. This creates a balanced binary tree with n! leaves. The heightis thus Θ(lg(n!)) which is the same as Θ(n lg n). As the height determines the worst-caseruntime, we are done.

15.10 Counting Sort

If we know more about the values than just how to compare them we can do better thann lg n. For instance, supppose we have a list of n integers between 0 and k − 1, inclusive,where k is not prohibitively large. Then we can sort in Θ(n) time by using an auxiliary arrayof size Θ(k) that simply counts the number of occurrences of each element. Below we seethat we can also sort objects with integer keys stably using a similar idea.

15.11 Lower Bounds and Counting Sort Exercises

1. Suppose you have a list of n integers between 1000000900 and 1000010000. How canyou sort them in Θ(n) time?

2. Suppose you have an array of Student objects given below.

Student.java

public class Student{

private int ID;private String name;public Student(int ID, String name){

this.ID = ID;this.name = name;

}public int getID() { return ID; }public String getName() { return name; }public String toString() { return "("+ID+","+name+")"; }

}

Suppose further that you have an array of n Students and all IDs are between 0 andk − 1, where k is not enormous. How can you stably sort the Students by ID.

171

Page 172: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

3. (?) Suppose you have n integers between 0 and n2 − 1, inclusive. How can you sortthem in Θ(n) time with Θ(n) memory? Will it work up to n3 − 1?

4. (??) Show how to sort an array n of Java ints in Θ(n) time with Θ(n) memory.

5. (??) Show that lg(n!) = Θ(n lg n).

15.12 Counting Sort Solutions

1. Subtract 1000000900 from all values and use counting sort.

2. Proceed like counting sort, but the array is an array of lists (called buckets). Eachtime you put an element in the appropriate slot it gets appended to the end of the list.This algorithm is called bucket sort. Note that we cannot simply count the number ofelements with each ID since we lose the name information.

Alternatively, we can use counting sort directly, but after we count how many of eachstudent ID occurs, we use it to compute the positions in the �nal list. Code follows:

CountingSortStudents.java

import java.util.Arrays;

public class CountingSortStudents{

public static int getMaxID(Student[] arr){

int max = arr[0].getID();for (int i = 1; i < arr.length; ++i)

if (arr[i].getID() > max)max = arr[i].getID();

return max;}//Assume IDs are non−negative and max isn't too bigpublic static void countingSort(Student[] arr){

int max = getMaxID(arr);int[] counts = new int[max+1];for (Student s : arr) counts[s.getID()]++;//Fill counts with starting positionsint total = 0;for (int i = 0; i < counts.length; ++i){

int old = total;total += counts[i];counts[i] = old;

}

172

Page 173: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

Student[] copy = Arrays.copyOf(arr, arr.length);for (Student s: copy){

arr[counts[s.getID()]] = s;counts[s.getID()]++;

}}

public static void main(String[] args){

Student a = new Student(1,"A");Student b = new Student(1,"B");Student c = new Student(2,"C");Student d = new Student(2,"D");Student e = new Student(3,"E");Student f = new Student(3,"F");Student[] arr = {f,d,c,e,b,a};countingSort(arr);System.out.println(Arrays.toString(arr));

}}

3. Think of each integer as having the form x = an + b. That is, we are representing itin base n. Using counting sort, sort all of the integers by their b-values (that is, modn). Then use counting sort again (as in the previous part) to stabily sort all of theintegers by their a-values (that is, sort by x/n). The same type of algorithm will workfor n3 − 1 as well. We simply write x = an2 + bn + c and sort 3 times: �rst by c, thenby b, then by a.

4. Java ints have 32 bits. First sort by the lowest order bit. Then stably by the nextbit, and so forth. After 32 rounds of counting sort all elements will be sorted. This iscalled (binary) radix sort.

5. Note thatlg(n!) = lg(1) + lg(2) + lg(3) + · · ·+ lg(n− 1) + lg(n).

Then we have

n

2lg(n/2) ≤ lg(1) + lg(2) + lg(3) + · · ·+ lg(n− 1) + lg(n) ≤ n lg(n).

Thus lg(n!) = Θ(n lg n).

173

Page 174: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

16 Lecture 16: Graphs, Adjacency Matrices and Lists,

DFS

16.1 Review Exercises

1. Suppose you are merging the two sorted lists 1,3,5 and 2,3,4. After 3 elements havebeen added to the merged list, which elements remain in each list?

2. Partition the list 9,4,1,7,2,3,5 around the median.

3. (?) Explain how to give an iterative (non-recursive) implementation of quick sort.

4. (?) Explain how to give an iterative (non-recursive) implementation of merge sort.

16.2 Review Solutions

1. The �rst list will have 5 and the second will have 3,4.

2. 2,3,1,4,7,9,5 is a valid partition about the median.

3. You can use a stack to simulate the implicit tree traversal that quick sort performs.Instead of making a recursive call we simply push a pair of indices onto the stack (theleft and right indices of the subarray to quick sort). While the stack is non-empty, thealgorithm pops the next pair of indices from the stack, partitions the correspondingsubarray, and pushes two new pairs determining the left and right halves. The stackbegins with the pair (0, n− 1).

Alternatively, we can use a queue and perform a level-by-level traversal of the implicittree. This is the same algorithm as above but we replace the stack with a queue. Thestack method should be more memory e�cient when the implicit tree is fairly wellbalanced.

4. We can also perform a level-by-level traversal here, but we start with the leaves andmove upwards. Initially we push all of the index pairs corresponding to the leaves onthe queue. That is, we push (0, 0) then (1, 1), etc. The algorithm dequeues 2 pairs o�the queue at a time, merges the corresponding lists, and then enqueues the combinedpair. We stop when only 1 pair remains on the queue. The one technical detail is thatwe can only merge adjacent subarrays, so if the two pairs we get aren't adjacent, wesimply enqueue the �rst, and dequeue a new pair (this occurs when we have an oddnumber of subarrays at a given level of the implicit tree).

Below we give iterative implementations of both merge and quick sort using int arraysto store the pairs of indices.

IterativeSorts.java

import java.util.ArrayDeque;

174

Page 175: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

import java.util.Arrays;import java.util.Random;

public class IterativeSorts{

public static void mergeSort(int[] arr){

ArrayDeque<int[]> queue = new ArrayDeque<>();int[] tmp = new int[arr.length];for (int i = 0; i < arr.length; ++i)

queue.add(new int[]{i,i});while (queue.size() > 1){

int[] f = queue.poll();int[] s = queue.poll();if (f[1]+1 != s[0]) {

queue.add(f);f = s;s = queue.poll();

}SortUtils.merge(arr, f[0], f[1], s[1], tmp);f[1] = s[1];queue.add(f);

}}public static void quickSort(int[] arr, Random ran){

ArrayDeque<int[]> stack = new ArrayDeque<>();stack.add(new int[]{0,arr.length−1});while (!stack.isEmpty()){

int[] inds = stack.pollLast();if (inds[0] >= inds[1]) continue;int p = ran.nextInt(inds[1]−inds[0]+1)+inds[0];p = SortUtils.partition(arr,inds[0],inds[1],p);stack.add(new int[]{inds[0],p−1});stack.add(new int[]{p+1,inds[1]});

}}public static void main(String[] args){

Random ran = new Random();int[] arr = {1,2,3,4,5,6,7,8,9,10,11};SortUtils.shu�e(arr, ran);System.out.println(Arrays.toString(arr));

175

Page 176: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

mergeSort(arr);System.out.println(Arrays.toString(arr));SortUtils.shu�e(arr, ran);System.out.println(Arrays.toString(arr));quickSort(arr,ran);System.out.println(Arrays.toString(arr));

}}

16.3 Graphs: De�nitions and Terminology

A graph is a set of vertices V (sometimes called nodes) and a set of edges E, which are linksbetween the nodes. We often use the numbers 0, 1, . . . , n−1 to represent the vertices. Graphsare either directed (edges have a direction) or undirected (edges don't have a direction).Pictorially, vertices are typically drawn as circles. Edges in a directed graph are drawn aslines with arrows connecting the corresponding vertices, and edges in an undirected graphare just lines between vertices. A loop is a directed edge from a vertex to itself. Undirectedgraphs do not have loops. Sometimes we willl want to assign weights or costs to each edge.Such a graph is called a weighted graph. Sometimes graphs have multiple edges (in the samedirection) between two vertices. By default, we will assume our graphs do not have multipleedges.

The degree of a vertex is the number of edges it touches (formally we say the number ofedges incident on it). For a directed graph, a vertex v has separate indegree and outdegreefor the number of edges coming in to v, and the number of edges leaving v, respectively.

A path from v to w is a sequence of distinct vertices starting with v and ending with wsuch that each subsequent vertex is connected by an edge (the trivial path has 1 vertex).The length of a path is the number of edges on it. If the graph is directed, the edges must goin the correct direction. More precisely, if the vertices of the path are x0, x1, . . . , xn then thedirected edges on the path must go from xi to xi+1. A walk is the same as a path, except thevertices need not be distinct. A cycle is the same as a path, but the �rst and last verticesare the same (all others are distinct). We require a cycle to have at least 1 edge (the smallestpossible cycle is a single loop), and for all the edges to be distinct. The length of a cycle orwalk is again the number of edges in it.

An undirected graph is called connected if every pair of vertices is connected by a pathfrom one vertex to the other. A directed graph is called strongly connected if there is a(directed) path between every pair of vertices v, w. That is, if you �x vertices x, y there willbe a path from x to y and a path from y to x. In an undirected graph, if you �x a vertexv and consider the set C of all vertices w reachable from v (i.e., all vertices w such thatthere is a path from v to w) then C is called a connected component. This means that anundirected graph is connected i� it has 1 connected component. The analogous concept fora directed graph is called a strongly connected component.

A non-empty undirected graph is called a tree if it is connected and has no cycles (acyclic).A tree is called rooted if a node is distinguished as the root. An important property of a tree

176

Page 177: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

(which could also be used as a de�nition) is that for every pair of vertices there is exactly 1path connecting them. To see why, note there must be at least one path by connectedness.If there were 2 or more paths, we could use parts of those paths to form a cycle. A directedtree is a directed graph such that each pair of vertices is connected by exactly 1 path in onedirection. That is, for any �xed vertices v, w either there is a path from v to w or a pathfrom w to v but not both. When we use the term tree, we mean undirected tree.

16.4 Graph Terminology Exercises

1. For each of the following graphs, determine which of the following properties holds:undirected, directed, connected, acyclic, strongly connected. Also give the number ofconnected (or strongly connected) components, the length of the longest path in thegraph, and, if it has a cycle, the length of the shortest cycle (called the girth).

(a)

0

1

2

3

4 5

6

(b)

0

1 2

3

4 5

6

(c)

177

Page 178: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

0 1

2

3

4

5 6

7

8

9

2. Draw a tree where 1 vertex has degree 5 and the rest have degree 1.

3. Draw an undirected graph with 2 connected components such that every vertex hasdegree 2.

4. Suppose a graph with n vertices has the maximum possible edges (as usual we assumeno multiple edges). Give that number of edges if the graph is:

(a) Undirected

(b) Directed

(c) Undirected, but has 2 components.

(d) Undirected with 2 components of equal size.

5. As usual, here the term tree means undirected tree.

(a) Show that removing any edge from a tree will leave a disconnected graph. Thusa tree is a minimal connected graph. [Hint: Suppose not.]

(b) Show that a tree with at least 2 vertices must have a vertex of degree 1. [Hint:Suppose not.]

(c) (?) Show that a tree with n vertices must have n− 1 edges.

16.5 Graph Terminology Solutions

1. (a) Undirected, acyclic, 2 connected components. The longest path has length 3.

Note that each connected component forms a tree. This is why an undirectedacyclic graph is called a forest.

(b) Directed, 5 strongly connected components. The longest path has length 5, andthe shortest cycle has length 1.

(c) Undirected, 2 connected components. The longest path has length 4 and theshortest cycle has length 3.

2. This is called a star graph with 5 points.

178

Page 179: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

0 1

2

3

4

5

3.

0 1

2

3

4

5 6

7

8

9

4. (a)(n2

)(b) n2

(c)(n−12

)(d) 2

(n/22

)When a graph is close to the maximum number of edges we call it dense. If a graphhas a low number of edges we call it sparse.

5. (a) Suppose removing the edge between v and w does not disconnect the graph. Thenthere must be a path p in the graph that doesn't include the edge between v and w.But the path p along with edge vw forms a cycle. This contradicts the assumptionthat our graph was acyclic.

(b) Suppose every vertex has degree at least 2. Start at a vertex v0 and constructa path by repeatedly going to a new vertex you haven't visited before. At somepoint this stops at the vertex vk since all of its neighbors have been visited already.But there is an unused edge coming out of vk to one of the vi for i < k since thedegree of vk is at least 2. This shows the original graph had the cycle

vi, vi+1, . . . , vk, vi.

179

Page 180: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

(c) Proof by induction. For the base case, note that a tree of size 1 has 0 edges. Forthe inductive case, assume all trees of size n have n − 1 edges. Let T be sometree with n + 1 vertices. By the previous part it must have a vertex of degree 0.Removing this vertex (and the associated edge) leaves a smaller tree with n − 1edges (by induction). Thus T has n edges.

An alternative idea goes as follows. Suppose tree T has n vertices. We will growthe tree T by adding one edge at a time. Each edge we add must be between 2di�erent connected components since we cannot form a cycle. This must occurexactly n− 1 times to leave us with 1 connected component, since each edge willdecrease the number of connected components by 1.

16.6 Data Structures: Adjacency Matrices and Lists

In this section we discuss how to store a graph. In both of the following data structureswe will store directed graphs. If we want to store undirected graphs, we will represent eachundirected edge as 2 directed edges (in both directions).

An adjacency matrix is an n×n boolean matrix. Let adj denote this matrix. If adj[i][j] istrue, then there is a directed edge from i to j. To model a weighted graph, we can store anauxiliary matrix of costs (i.e., costs[i][j] stores the cost of the edge from i to j). We cannotmodel multiple edges with an adjacency matrix.

An adjacency list as an array of lists of vertices. In Java, we can represent this as anArrayList<ArrayList<Integer>> adj. The list adj.get(v) stores all vertices w such that there isan edge from v to w. To store weights we can use an auxiliary data structure: an adjacencylist of costs, or an adjacency matrix of costs. As an alternative, we can store Edge objects inour adjacency list which contain the terminating vertex and the weight. To model multipleedges we simply allow repeats in our lists.

Adjacency lists are more popular than adjacency matrices but they have their tradeo�s,as we will see in a moment.

16.7 Graph Data Structures Exercises

1. For the graph below, show how it would be stored in an adjacency matrix (for bothedges and weights) and in an adjacency list (for both edges and weights).

0

1 2 3

180

Page 181: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

2. What are the worst-case space requirements for an adjacency matrix and an adjacencylist.

3. Give the worst-case runtimes of the following operations for an adjacency matrix andan adjacency list.

(a) Determine if there is an edge from i to j.

(b) Output all of the edges of the graph.

(c) List the neighbors of a vertex v (i.e., the vertices w such that there is an edgefrom v to w).

(d) List all vertices that have v as a neighbor (i.e., the vertices w such that there isan edge from w to v).

(e) Output the degrees of every vertex.

4. (?) Think about how to compute if an undirected graph is connected. You can useeither an adjacency matrix of an adjacency list.

16.8 Graph Data Structures Solutions

1. The adjacency matrix is given below.

0

0

F

F

T

F

1

1

T

F

F

F

2

2

F

T

F

F3

3

F

F

T

F

The adjacency list is depicted below.

181

Page 182: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

0 1

1 2

2 0 3

3

2. Θ(|V |2) for an adjacency matrix and Θ(|V |+ |E|) for an adjacency list.

3. (a) Θ(1) for an adjacency matrix. For an adjacency list it is Θ(deg(i)) where deg(i)is the degree of i. This can be Θ(|V |) in the worst-case. The worst case occurswhen deg(i) = |V | − 1.

(b) Θ(|V |2) for an adjacency matrix and Θ(|V |+ |E|) for an adjacency list. Thus thelist is far superior when the graph is sparse.

(c) Θ(|V |) for an adjacency matrix and Θ(deg(v)) for an adjacency list.

(d) Θ(|V |) for an adjacency matrix (loop over the column), and Θ(|V | + |E|) for anadjacency list (may need to look at every edge).

(e) Θ(|V |2) for an adjacency matrix and Θ(|V |) for an adjacency list.

4. One method is to perform a DFS (see next section) of the graph and see if you canreach every node from the �rst node you started at.

16.9 Depth First Search (DFS)

As with trees, we now discuss how to traverse the vertices of a graph. Unlike our treetraversals from earlier in the class, we have to be careful not to repeatedly traverse the samenodes since we can have multiple ways to arrive at the same vertex. To do this we willmaintain an array that stores which vertices have been visited by our traversal. Thus, oncewe mark a vertex as visited, we will never allow it to be visited again preventing the issuementioned above. Below we give an implementation of DFS for an adjacency matrix.

DFS.java

public class DFS{

public static �nal int UNVISITED = 0;public static �nal int VISITING = 1;public static �nal int FINISHED = 2;

182

Page 183: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

public static void dfs(boolean[][] adj){

int[] state = new int[adj.length];//Loops over each node in the graph//in case we haven't visited it yetfor (int v = 0; v < adj.length; ++v)

dfs(adj,v,state);}

public static void dfs(boolean[][] adj,int v, int[] state)

{if (state[v] != UNVISITED) return;System.out.printf("Visiting Vertex %d\n",v);state[v] = VISITING;for (int w = 0; w < adj.length; ++w)

if (adj[v][w])dfs(adj,w,state);

System.out.printf("Finishing Vertex %d\n",v);state[v] = FINISHED;

}public static void main(String[] args){

boolean[][] adj = {{false,true,true},{true,false,true},{true,true,false}

};dfs(adj);

}}

Using DFS we can answer many interesting questions about a graph.

183

Page 184: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

17 Lecture 17: More DFS and BFS

17.1 DFS Exercises

1. (?) Show how to compute, for a given pair of vertices v and w, the number of walksof length 3 from v to w. Recall that on a walk you may reuse vertices and edges. Youcan use either an adjacency matrix or an adjacency list.

2. On the following graph, show the order in which DFS visits the nodes (assume anadjacency matrix).

0

1

2

5

6 73

4

3. Implement DFS for an adjacency list. What is the runtime of DFS for an adjacencymatrix vs an adjacency list?

4. Suppose we want to use a variant of DFS to print out all nodes in the same connectedcomponent as 0. How would you do this?

5. Show how to use a variant of DFS to count the number of connected components inan undirected graph.

6. (?) Show how to use DFS to determine if a directed graph has a cycle. What if it isundirected?

17.2 Solutions

1. (a) Adjacency List: First compute a boolean array that marks which vertices canreach the end in one step. This requires Θ(|V | + |E|) time. Then we perform aloop over the neighbors of v's neighbors. Each time these second order neighborsoccurs in the boolean array, we increment a counter. This requires Θ(|E|) time.Thus the entire runtime is Θ(|V |+ |E|) with Θ(|V |) extra memory.

(b) Adjacency Matrix: We can use the same algorithm with an adjacency matrix butwe don't need the extra array since it is just the w column of the matrix. Theiteration will require Θ(|V |2) time and Θ(1) memory.

2. 0,1,2,4,3,5,6,7

184

Page 185: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

3. Code is given below.

DFSList.java

import java.util.ArrayList;

public class DFSList{

public static �nal int UNVISITED = 0;public static �nal int VISITING = 1;public static �nal int FINISHED = 2;

public static void dfs(ArrayList<ArrayList<Integer>> adj){

int[] state = new int[adj.size()];//Loops over each node in the graph//in case we haven't visited it yetfor (int v = 0; v < adj.size(); ++v)

dfs(adj,v,state);}

public static void dfs(ArrayList<ArrayList<Integer>> adj,int v, int[] state)

{if (state[v] != UNVISITED) return;System.out.printf("Visiting State %d\n",v);state[v] = VISITING;for (int w : adj.get(v)) dfs(adj,w,state);System.out.printf("Finishing State %d\n",v);state[v] = FINISHED;

}public static void main(String[] args){

ArrayList<ArrayList<Integer>> adj = new ArrayList<>();for (int i = 0; i < 3; ++i) adj.add(new ArrayList<>());adj.get(0).add(1); adj.get(0).add(2);adj.get(1).add(2);adj.get(2).add(0); adj.get(2).add(1);dfs(adj);

}}

The runtime is Θ(|V |+ |E|) for the list and Θ(|V |2) for the matrix.

4. Remove the outer loop that repeatedly calls DFS on each vertex and only call it on 0.Then print whenever a vertex is visited.

185

Page 186: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

5. In the outer loop that repeatedly calls DFS on each vertex, count how many timesDFS is called on an unvisited vertex. This is the number of connected components.

6. First assume the graph is directed. Then a cycle occurs when you �nd an edge betweena vertex you are currently visiting, and a vertex that was previously visited but isn't�nished yet. The same algorithm works for an undirected graph, but we must ignorethe edge that took us to the current node. Let's get more precise about this.

When you call DFS, if you mark all of the vertices it visits and edges it traverses youend up with a forest. The outer loop iterates over each tree in the forest. In a directedgraph, the trees are directed and are out-trees. DFS also allows us to label each edgeof the graph based on how DFS treated it. These labels are:

(a) Tree edge: one of the edges traversed by the DFS when visiting a node.

(b) Back edge: an edge from a node to one of its ancestors in the DFS forest (thatisn't a tree edge in an undirected graph).

(c) Forward edge (only possible in a directed graph): an edge from a node to one ofits non-direct descendents in the forest (i.e., not a tree edge).

(d) Cross edge (only possible in a directed graph): all remaining edges. These connectvertices that have no ancestral relationship with respect to the DFS forest.

Using this terminology the cycle �nding algorithm can be stated very simply: check ifa back edge exists.

17.3 Breadth-First Search (BFS)

Breadth-�rst search is a graph traversal that is analogous to level-by-level search for trees.In an unweighted graph, de�ne the distance from v to w to be length of the shortest pathfrom v to w. If there is no path, call the distance ∞. When we apply breadth-�rst searchfrom a node v, we visit the other nodes reachable from v in order of their distances from v.Just as with our level-by-level code, our implementation of breadth-�rst search accomplishesthis by using a queue.

BFS.java

import java.util.ArrayDeque;

public class BFS{

public static �nal int INITIAL = 0;public static �nal int PROCESSING = 1;public static �nal int FINISHED = 2;

public static void bfs(boolean[][] adj){

186

Page 187: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

int[] state = new int[adj.length];//Loops over each node in the graph//in case we haven't visited it yetfor (int v = 0; v < adj.length; ++v)

bfs(adj,v,state);}

public static void bfs(boolean[][] adj,int v, int[] state)

{if (state[v] != INITIAL) return;ArrayDeque<Integer> q = new ArrayDeque<>();state[v] = PROCESSING;q.add(v);while (!q.isEmpty()) {

int w = q.poll();System.out.printf("Visiting Vertex %d\n",w);for (int x = 0; x < adj.length; ++x) {

if (adj[w][x] && state[x] == INITIAL) {q.add(x);state[x] = PROCESSING;

}}state[w] = FINISHED;

}}public static void main(String[] args){

boolean[][] adj = {{false,true,true},{true,false,true},{true,true,false}

};bfs(adj);

}}

17.4 BFS Exercises

1. Give the order that BFS visits the vertices of the following graph.

187

Page 188: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

0

1

2

5

6 73

4

2. Implement BFS for an adjacency list. What is the runtime of BFS for an adjacencymatrix vs an adjacency list?

3. Show how to use a variant of BFS to count the connected components of an undirectedgraph.

4. Show how to use a variant of BFS to �nd the distance from 0 to all other nodes in adirected (unweighted) graph. (You can use −1 to signify unreachable since ints don'tallow the value ∞.)

5. (?) You are given an n×n chessboard, and a location of a knight, and a target square.Give the smallest number of moves needed to get to the target square. The knight cannever leave the board. [Recall that a knight moves 2 squares horizontally or vertically,and then 1 square orthogonal to the intial direction. That is, it makes L-shaped moves.]

17.5 BFS Solutions

1. 0,1,3,2,4,5,6,7

2. Code is given below.

BFSList.java

import java.util.ArrayDeque;import java.util.ArrayList;import java.util.Arrays;

public class BFSList{

public static �nal int INITIAL = 0;public static �nal int PROCESSING = 1;public static �nal int FINISHED = 2;

public static void bfs(ArrayList<ArrayList<Integer>> adj){

int[] state = new int[adj.size()];//Loops over each node in the graph

188

Page 189: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

//in case we haven't visited it yetfor (int v = 0; v < adj.size(); ++v)

bfs(adj,v,state);}//BFS from v and returns distances to vpublic static int[] bfs(ArrayList<ArrayList<Integer>> adj,

int v, int[] state){

if (state[v] != INITIAL) return null;int[] dists = new int[state.length]; //Stores distancesArrays.�ll(dists, −1); //Initialize with −1sdists[0] = 0;ArrayDeque<Integer> q = new ArrayDeque<>();q.add(v);state[v] = PROCESSING;while (!q.isEmpty()) {

int w = q.poll();System.out.printf("Visiting Vertex %d\n",w);for (int x : adj.get(w)) {

if (state[x] == INITIAL) {q.add(x);state[x] = PROCESSING;dists[x] = dists[w] + 1;

}}state[w] = FINISHED;

}return dists;

}public static void main(String[] args){

ArrayList<ArrayList<Integer>> adj = new ArrayList<>();for (int i = 0; i < 3; ++i) adj.add(new ArrayList<>());adj.get(0).add(1); adj.get(0).add(2);adj.get(1).add(2);adj.get(2).add(0); adj.get(2).add(1);bfs(adj);

}}

The runtime is Θ(|V |+ |E|) for the list and Θ(|V |2) for the matrix.

3. Same as for DFS. Count the number of times that BFS is called on a vertex an unvisitedvertex in the outer loop.

4. Maintain an array of distances to the node 0 which is initialized to −1's. Set node 0

189

Page 190: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

to have distance 0. When you add a node w to the queue that is the neighbor of thenode v you are visiting, set the distance of v to be one higher than the distance of w.

5. We could make the entire chessboard into a graph that we store in an adjacency list,but there is a better way to go. Instead we will refer to each square by a pair ofcoordinates, and use the knight move rule to determine the neighbors of each square.The full chessboard graph will exist implicitly, but we will never actually store it. Codefollows:

KnightMoves.java

import java.util.ArrayDeque;import java.util.Arrays;

public class KnightMoves{

static int[][] dirs = {{2,1},{2,−1},{−2,1},{−2,−1},{1,2},{−1,2},{1,−2},{−1,−2}};

public static int bfs(int nr, int nc, int tr, int tc, int n){

ArrayDeque<int[]> queue = new ArrayDeque<>();int[][] dist = new int[n][n];for (int i = 0; i < n; ++i) Arrays.�ll(dist[i], −1);dist[nr][nc] = 0;queue.add(new int[]{nr,nc});while (!queue.isEmpty()){

int[] v = queue.poll();int vdist = dist[v[0]][v[1]];if (tr == v[0] && tc == v[1]) return vdist;for (int[] d : dirs){

int r = v[0] + d[0], c = v[1] + d[1];if (r >= n || r < 0 || c >= n || c < 0) continue;if (dist[r][c] >= 0) continue;dist[r][c] = vdist+1;queue.add(new int[]{r,c});

}}return −1; //Only if unreachable

}

public static void main(String[] args){

System.out.println(bfs(0,0,7,7,8));System.out.println(bfs(0,0,7,0,8));

}

190

Page 191: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

}

One speed we could perform is to simultaneously BFS from the knight and from thetarget and see when they meet. This can signi�cantly reduce the number of squaresthat must be visited (for large boards).

191

Page 192: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

18 Lecture 18: Dijkstra, Prim, Floyd-Warshall, and DAGs

18.1 Review Exercises

1. Consider the following graph.

0

1 2 3

456

Assume neighbors are visited in order of their vertex numbers, and that BFS is calledon vertex 0.

(a) What is the order in which BFS visits the vertices?

(b) What is in the BFS queue once vertex 1 is �nished?

2. (?) You have a directed unweighted graph representing a maze. Your goal is to leavethe maze as quickly as possible. There are several possible exits and several choicesyou can make for where to start. Explain how to compute the shortest number of stepsneeded if you can choose any one of the given start locations and any one of the givenexit locations.

3. Suppose you are given information about a set of locations. Each location is describedby a distinct String (its name) and each graph edge is given by referencing the names(like, �edge from Name1 to Name2�). Given a nice way to convert the names intointegers so that we can use our standard graph representations.

18.2 Review Solutions

1. (a) 0,1,2,3,6,5,4

(b) 2,3,6,5

192

Page 193: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

2. Put all start locations into the queue at the beginning of BFS, and give them alldistance 0. Stop when you �nd your �rst exit. Use the same method as in the previouslecture for keeping track of distances.

3. Use a HashMap to map between names and vertex labels. Here is a useful way toimplement it:

public static int getIndex(HashMap<String,Integer> map, String name) {if (map.containsKey(name)) return map.get(name);int s = map.size();map.put(name,s);return s;

}

18.3 Dijkstra's Algorithm

Suppose we have a weighted graph where all weights are non-negative. These weights will beinterpretted as the length or cost of each edge. We say the cost of a path is the sum of thecosts on its edges. The cost of getting from v to w is the cost of the smallest costing pathfrom v to w (we will denote the cost∞ if there is no path). Our goal is to �x a vertex v andcompute the cost of getting to all other (reachable) nodes. If we do a simple BFS, it willvisit nodes based on how many edges away from v they are, and not based on cost. Instead,we will replace the queue in our BFS with a priority queue that orders the contained verticesby cost. The algorithm will work as follows.

18.3.1 Dijkstra Version A

1. Add v to the priority queue with a cost of 0 and all other nodes with a cost of ∞.

2. Repeat the following while the queue is non-empty:

(a) Remove the lowest cost element w from the queue.

(b) Output w and its cost c.

(c) Consider each neighbor x of w that we haven't output yet. We know it is possibleto reach x from v with a cost of c+ d where d is the cost of the edge from w to x.

(d) See if c + d is lower than the current cost of x, and if so, update its value in thepriority queue.

If we use a binary heap to implement Version A, then we must use an augmented heap toallow for key updating. Alternatively, we can use a slightly di�erent version of the algorithmthat is comparable in speed.

193

Page 194: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

18.3.2 Dijkstra Version B

1. Add v to the priority queue with a cost of 0.

2. Repeat the following while the queue is non-empty:

(a) Remove the lowest cost element w from the queue.

(b) If we have already output w, continue to the next iteration of the loop.

(c) Otherwise, output w and its cost c.

(d) Consider each neighbor x of w that we haven't output yet. We know it is possibleto reach x from v with a cost of c+ d where d is the cost of the edge from w to x.

(e) Add w to the queue with cost c + d.

3. All vertices we haven't output have distance ∞.

In Version B we may add a node to the priority queue several times, but we only processthe �rst one we dequeue. Code follows:

Dijkstra.java

import java.util.ArrayList;import java.util.Arrays;import java.util.PriorityQueue;

public class Dijkstra{

static class Item implements Comparable<Item>{

private double cost;private int v;public Item(double c, int v){

this.cost = c;this.v = v;

}@Overridepublic int compareTo(Item o) { return Double.compare(cost, o.cost); }

}public static double[] dijkstra(ArrayList<ArrayList<Integer>> adj,

ArrayList<ArrayList<Double>> costs, int v){

int N = adj.size();double[] ret = new double[N];Arrays.�ll(ret, Double.POSITIVE_INFINITY);PriorityQueue<Item> pq = new PriorityQueue<>();pq.add(new Item(0, v));

194

Page 195: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

while (!pq.isEmpty()){

Item it = pq.poll();if (!Double.isIn�nite(ret[it.v])) continue;//When we remove a vertex for the �rst time//Set its value in retret[it.v] = it.cost;ArrayList<Integer> nbrs = adj.get(it.v);double cost = it.cost;for (int i = 0; i < nbrs.size(); ++i) {

int w = nbrs.get(i);double edgeCost = costs.get(it.v).get(i);pq.add(new Item(cost + edgeCost, w));

}}return ret;

}

//Returns −1 if all −1. Otherwise returns index of smallest non−negative.static int �ndMin(double[] pq){

int ind = −1;for (int i = 0; i < pq.length; ++i)

if (pq[i] >= 0 && (ind == −1 || pq[i] < pq[ind]))ind = i;

return ind;}//Uses an array as the priority queue instead of a binary heap.public static double[] dijkstra2(ArrayList<ArrayList<Integer>> adj,

ArrayList<ArrayList<Double>> costs, int v){

int N = adj.size();double[] ret = new double[N];Arrays.�ll(ret, Double.POSITIVE_INFINITY);double[] pq = new double[N];Arrays.�ll(pq,−1);pq[v] = 0;int minInd = v;while (minInd != −1){

ret[minInd] = pq[minInd];pq[minInd] = −1;for (int i = 0; i < adj.get(minInd).size(); ++i){

int w = adj.get(minInd).get(i);

195

Page 196: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

if (Double.isIn�nite(ret[w])){

double newVal = ret[minInd] + costs.get(minInd).get(i);if (pq[w] == −1 || newVal < pq[w])

pq[w] = newVal;}

}minInd = �ndMin(pq);

}return ret;

}public static void main(String[] args){

int N = 5;ArrayList<ArrayList<Integer>> adj = new ArrayList<>();ArrayList<ArrayList<Double>> costs = new ArrayList<>();for (int i = 0; i < N; ++i) adj.add(new ArrayList<>());for (int i = 0; i < N; ++i) costs.add(new ArrayList<>());adj.get(0).add(1); adj.get(0).add(2);adj.get(1).add(3); adj.get(2).add(3);costs.get(0).add(3.0); costs.get(0).add(2.0);costs.get(1).add(6.0); costs.get(2).add(6.5);double[] ds1 = dijkstra(adj,costs,0);double[] ds2 = dijkstra2(adj,costs,0);System.out.println(Arrays.toString(ds1));System.out.println(Arrays.toString(ds2));

}}

In dijkstra above we implement version B in a min heap. In dijkstra2 above we use versionA with an array implementation of a priority queue (instead of a min heap). See question 3below.

18.4 Dijkstra Exercises

1. Give the order in which Dijkstra visits the nodes of the following graph assuming itbegins at node 2.

196

Page 197: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

6

3

5

1

2

2

3

7

6

1

0

4 5

2. What is the runtime of Version A and Version B assuming a min heap is used with anadjacency list?

3. Suppose we use a simple array of length |V | to implement our priority queue as indijkstra2 above. Each entry of the array stores the distance from the starting node tothe vertex associated with the index, or −1 if the vertex isn't in the priority queue.To �nd the minimum we perform a linear search over the array. What is the runtimeof dijkstra2 above?

4. Give an example of a graph allowing negative weights which shows Dijkstra's algorithmcan fail.

18.5 Dijkstra Solutions

1. 2,1,3,0,4 with distances 0,2,5,6,7. The vertex 5 has distance ∞.

2. Version A has runtime Θ(|V |+|E| log |V |) and Version B has runtime Θ(|V |+|E| log |E|).Note that |E| ≤ |V |2 so log |E| ≤ 2 log |V |.There is a special kind of heap called a Fibonacci heap that brings this down to Θ(|E|+|V | log |V |), but it is far more complicated to implement and the heap operations havelarger constants on them.

3. The runtime is Θ(|V |2). For dense graphs this can be faster than the heap implemen-tations.

4.

0

1

1 22 31

10 -103

197

Page 198: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

18.6 Prim's Algorithm

Given any undirected connected graph it is possible to remove edges until it becomes a tree(which will always have |V |−1 edges). If the graph is weighted, we can then ask what is thecheapest tree we can end up with. Here cheapest means the edges of the resulting tree havethe smallest possible sum. This is called the minimum spanning tree problem. Conveniently,we can e�ciently solve this problem by making a small change to Dijkstra's algorithm (calledPrim's algorithm), and we can even handle negative weight edges. The algorithm works bygrowing the tree one edge at time. We arbitrarily choose a starting vertex, the root of ourtree. At each step we �nd the smallest costing edge that connects the tree we have builtthus far to a new vertex that is not yet part of our tree. After |V | − 1 steps we will haveadded all of the vertices to our tree.

18.7 Prim's Exercises

1. Show what edges Prim's algorithm chooses in the following graph when we start fromvertex 2. When ties occur, use lower numbered vertices.

6

3

5

1

2

2

3

7

6

1

0

4

2. What change must be made to our Dijkstra code above to obtain Prim's algorithm?

3. What is the runtime of Prim's algorithm?

4. Suppose instead we wanted the maximum spanning tree. How could we compute thisusing Prim's algorithm?

18.8 Prim's Solutions

1. We obtain the MST below:

198

Page 199: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

2

1

2

33

4

5

0

1

2. Here we give the min heap implementation of Prim's algorithm. Note that we no longerneed the distance to v when updating the cost of v's neighbors.

Prim.java

import java.util.ArrayList;import java.util.PriorityQueue;

public class Prim{

static class Item implements Comparable<Item>{

private double cost;private int v;public Item(double c, int v){

this.cost = c;this.v = v;

}@Overridepublic int compareTo(Item o) { return Double.compare(cost, o.cost); }

}public static double prim(ArrayList<ArrayList<Integer>> adj,

ArrayList<ArrayList<Double>> costs){

int N = adj.size(), cnt = 0;boolean[] done = new boolean[N];double ret = 0;PriorityQueue<Item> pq = new PriorityQueue<>();pq.add(new Item(0, 0));while (!pq.isEmpty()){

199

Page 200: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

Item it = pq.poll();if (done[it.v]) continue;done[it.v] = true;cnt++;ret += it.cost;for (int i = 0; i < adj.get(it.v).size(); ++i) {

int x = adj.get(it.v).get(i);double newVal = costs.get(it.v).get(i);pq.add(new Item(newVal, x));

}}return cnt == N ? ret : Double.POSITIVE_INFINITY;

}}

3. Same as Dijkstra: Θ(|E| + |E| log |V |) with a min heap, and Θ(|V |2) with an array,(and Θ(|E|+ |V | log |V |) with a Fibonacci heap).

4. Either change the heap to a max heap or negate every edge and then run standardPrim's followed by unnegating.

18.9 Floyd-Warshall Algorithm

Suppose we have a directed weighted graph (allowing negative weight edges). Let dist be a2d matrix of doubles storing the distances between vertices, with ∞ signifying that no edgeexists. Consider the following deceptively simple code:

int N = dist.length;for (int i = 0; i < N; ++i)for (int j = 0; j < N; ++j)for (int k = 0; k < N; ++k)

dist[j][k] = Math.min(dist[j][k],dist[j][i]+dist[i][k]);

This is called the Floyd-Warshall algorithm.

18.10 Floyd-Warshall Exercises

1. What is the runtime of Floyd-Warshall?

2. Assume the graph has no negative cycles (a cycle whose edge weight sum is negative).After 1 iteration of the outer loop (the i loop) what can be said about the matrix dist?

3. Assume the graph has no negative cycles. After p iterations of the outer loop, whatcan be said about the matrix dist?

4. Assume the graph has no negative cycles. What does the Floyd-Warshall algorithmcompute?

200

Page 201: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

5. How can you use Floyd-Warshall to detect which nodes have negative cycles throughthem?

6. (Segue Question) Give a directed acyclic graph such that, if we forgot about the arrowson the edges, we would not have a tree.

18.11 Floyd-Warshall Solutions

1. Θ(|V |3)

2. The matrix dist[v][w] contains the length of the shortest path from v to w that mayuse vertex 0 as an intermediate vertex.

3. The matrix dist[v][w] contains the length of the shortest path from v to w that mayuse vertex 0, 1, . . . , p− 1 as intermediate vertices

4. The lengths of the shortest paths between all pairs of vertices.

5.

0

1

2

3

18.12 Directed Acyclic Graphs (DAGs)

An undirected acyclic graph is a forest where each component is a tree. Directed acyclicgraphs can have more complex structure, but are still simpler than arbitrary directed graphs.Here are some applications of DAGs:

1. Draw an edge from one cell C of a spreadsheet to cell D if the formula in C uses thevalue in D. The spreadsheet can be evaluated if the graph of cells forms a DAG.

2. You are working at a business, and there are many tasks to complete in your roadmap.If task S must be completed before task T we draw an edge from S to T . The graphof tasks must form a DAG or it is impossible to complete.

3. Draw an edge from course A to course B if A is a prerequisite for B. Then the graphof courses must form a DAG.

201

Page 202: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

18.13 DAG Exercises

1. How can you determine if a directed graph is a DAG?

2. (?) Suppose you wanted to list a sequence of courses for a student to take one afteranother. You cannot list any course before any of its prerequisites. Give an algorithmfor outputing such a list.

3. (??) Suppose you can take an arbitrary number of classes per semester. That said,you must satisfy all prerequisites. Given a graph of the courses and their prerequisites,determine the smallest number of semesters required to complete all courses.

18.14 DAG Solutions

1. Run DFS and test for back edges. The graph is a DAG exactly when DFS has no backedges.

2. Run DFS and list the vertices in the order that DFS �nishes them. Then reverse thelist.

3. The answer is equivalent to one more than the longest directed path in the associatedDAG. To compute the longest path we will maintain an array that for each vertexstores the longest path in the DAG starting from that vertex. To populate the arraywe will use DFS:

(a) If DFS gets to a node that was already visited, return the value in the array.

(b) If a node has outdegree 0, then we return 0 and populate the array.

(c) Otherwise, take the max over DFS called on each child, add one, and populatethe array.

In a general directed graph, �nding the longest path is an NP-Complete problem (i.e.,doesn't seem like it will have an e�cient solution).

202

Page 203: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

19 Lecture 19: Tarjan's SCC Algorithm

19.1 Review Exercises

1. Suppose we have a directed weighted graph with no negative cycles and we have justrun Floyd-Warshall on it. Let d denote the resulting n × n matrix. Show how to dothe following using d.

(a) Output the size of the component containing vertex v, assuming the graph isundirected.

(b) Output whether the graph has a cycle.

(c) Output the strongly connected component containing vertex v.

(d) Output the number of strongly connected components.

2. Consider the following graph.

0

6

1

4

2

2

3

8

4

9

1

6

(a) Show how Prim's algorithm �nds the minimum spanning tree starting from vertex2. In case of a tie, prefer lower-numbered vertices.

(b) Compute the distance of getting from vertex 4 to all other vertices using Dijkstra'salgorithm. In case of a tie, prefer lower-numbered vertices.

3. Consider the following memoized code for computing the kth Fibonacci number:

static long[] cache; //Initialized with −1sstatic long �b(int k) {if (k <= 1) return k;if (cache[k] > −1) return cache[k];

203

Page 204: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

long ret = �b(k−1)+�b(k−2);return cache[k] = ret;

}

Consider a graph of the values 0, 1, . . .. There is a directed edge from vertex v to vertexw if �b(v) calls �b(w). Draw the graph of all nodes reachable from 5.

19.2 Review Solutions

1. (a) One plus the number of non-in�nite entries in row v that are o�-diagonal (i.e.,don't consider dist[v][v]).

(b) Check if any of the diagonal entries aren't ∞ and output true if yes.

(c) Output all w such that dist[v][w] and dist[w][v] both aren't ∞.

(d) Make a boolean array of size |V | to mark vertices. Start with vertex 0 and markall elements in its strongly connected component using the previous part. Then�nd the next unmarked vertex and repeat.

2. (a) Prim's algorithm will add the vertices in the following order: 2,0,1,3,4.

(b) Dijkstra's algorithm will visit the vertices in the following order: 4,0,2,1,3 withdistances 0,6,7,9,9.

3.

5 4

3

2

1

4 2 0

19.3 (??) Tarjan's Algorithm for Strongly Connected Components

Before we discuss the algorithm we will introduce the concept of a DFS number. As DFStraverses a graph it visits the vertices in some order. Starting with the number 0, we numbereach vertex according to the order in which DFS visits it. This is called the DFS number ofthe vertex, which we can write as dfsNum[v].

Tarjan's algorithm works by running DFS on the graph and outputs each strongly con-nected component when that component's lowest DFS numbered vertex is �nished by DFS.To help in outputting the SCCs, Tarjan's algorithm maintains a stack S of vertices. We willalso maintain an array, dfsLow, that approximately stores, for each vertex v, the lowest DFSnumber of any unpopped vertex reachable from v (including v). The algorithm works asfollows:

204

Page 205: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

1. Run DFS.

(a) When DFS visits an unvisited vertex v, update dfsNum[v] accordingly, and pushv on the stack S.

(b) As usual, recursively call DFS on all unpopped neighbors. By taking the min overunpopped neighbors, also compute dfsLow[v].

(c) When DFS �nishes v do the following:

i. If v can reach an unpopped vertex with lower DFS number than v then donothing.

ii. Otherwise, pop and output all vertices from the stack S, stopping when wepop vertex v. Change the state of each popped vertex to �popped�. Theseare the vertices of the strongly connected component containing v.

The key to this algorithm is understanding how DFS traverses a graph. When a vertexv is �nished by DFS, all reachable vertices are �nished (descendents or have no ancestralrelationship) or are visited and un�nished (ancestors). The �nished vertices, if unpopped,must all be able to reach v or vertices that have not been �nished yet. In other words, all�nished vertices in the stack must be in the same SCC as v. Also note that the �nishedvertices in the stack must have been pushed after v since the stack is ordered by dfsNum.These ideas can be used to prove that our popping code exactly removes the SCC containingv.

Source code follows:

TarjanSCC.java

import java.util.ArrayList;

public class TarjanSCC{

static �nal int UNVISITED = 0;static �nal int VISITED = 1;static �nal int FINISHED = 2;static �nal int POPPED = 3;

static int num;public static void tarjanSCC(ArrayList<ArrayList<Integer>> adj){

int N = adj.size();num = 0;int[] state = new int[N];int[] dfsNum = new int[N];int[] dfsLow = new int[N];ArrayList<Integer> stack = new ArrayList<>();for (int v = 0; v < N; ++v)

tarjanSCC(adj, v, state, dfsNum, dfsLow, stack);

205

Page 206: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

}//Returns dfsLow[v]public static int tarjanSCC(ArrayList<ArrayList<Integer>> adj,

int v, int[] state, int[] dfsNum,int[] dfsLow, ArrayList<Integer> stack)

{if (state[v] != UNVISITED) return dfsLow[v];state[v] = VISITED;dfsNum[v] = num++;dfsLow[v] = dfsNum[v];stack.add(v);for (int w : adj.get(v)){

if (state[w] == POPPED) continue;int wNum = tarjanSCC(adj, w, state, dfsNum, dfsLow, stack);dfsLow[v] = Math.min(dfsLow[v], wNum);

}state[v] = FINISHED;if (dfsLow[v] >= dfsNum[v]){

System.out.print("SCC:");while (true){

int w = stack.remove(stack.size()−1);state[w] = POPPED;System.out.print(" "+w);if (w == v) break;

}System.out.println();

}else {} //dfsLow[v] < dfsNum[v]return dfsLow[v];

}}

19.4 Tarjan SCC Exercises

1. Run Tarjan's SCC algorithm on the following graph.

206

Page 207: Data Structures Brett Bernstein 1 Lecture 1: Introduction ...brettb/dsSum2016/AllNotes.pdf · Data Structures Brett Bernstein 1 Lecture 1: Introduction and a Review/Enrichment of

0

1

3 4

1

0

2

5

1 2

655

7

2

(a) What are the DFS numbers for each vertex?

(b) Give the strongly connected components in the order that Tarjan's algorithmoutputs them.

2. (??) Given any directed graph G we can create a new graph H where each vertex ofH is an SCC of G. There is a directed edge from SCC C1 to SCC C2 in H if there isan edge from some vertex of C1 to some vertex of C2 in G. What type of graph mustH be?

19.5 Tarjan SCC Solutions

1. (a) The order DFS visits the vertices is 0,1,3,4,2,5,7,6 so the DFS numbers for 0, 1, . . . , 7are 0, 1, 4, 2, 3, 5, 7, 6.

(b) 4,3,1 then 6,7,5,2 and then 0.

2. The resulting graph H is a DAG. If there was a cycle in H, then all of the compo-nents in the cycle could be merged into a strongly connected component, and this is acontradiction.

207