02b: Algorithms: Big-O
ICS211, Spring 2013
Dr. Zach
(switch view)
Status
- Today: last day to add; due dates kicking in.
- LAs have open lab hours (on Syllabus).
- Coding stds (discussed in lab)
Comparing Algorithms
- (See/add to Laulima forums discussion for important factors)
- Efficiency
- if both solve same problem
- time, but also space/memory
- First thought: just code them both and time them!
Comparing Implementations
- Many implementation details captured in timing (benchmarking)
- language factors (start-up time, garbage collection, compiler optimizations)
- coder differences (loop types, methods used, etc.)
- hardware factors (across machines, hardware boundaries)
- data set size
- timing accuracy limits
- So benchmarking does not compare algorithm performance
Benchmarking is still useful when comparing two implementations in very specific contexts. But those measurements don't let you compare algorithms, nor are they useful in different contexts (hardware, language, etc.).
Comparing Algorithms: Number of steps involved?
- But what is a single step?
- x = x + 4
- x += 4
- x = x - 8 * 2
- swap(x, y)
- Need even higher level of analysis
Comparing Algorithms: upper-bound of growth rate!
- How does the algorithm perform under stress? (add more elements to process)
- Basically categorize by growth rate: linear, quadratic, logarithmic, etc.
- ignore the finer details
- still a good way to compare to different algorithms
- gives us a single measure
- measure is a function, so informative over a range of possible values (scenarios)
- certain guarantee: upper bound of worst-case
- Start with counting "steps", but then drop the details to see only measure change with problem size
Example (variant of p.81)
for (int k = 0; k < n; k++) {
//statement 1
//statement 2
//...
//statement 5
}
//...25 more statements...
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
//statement 1
//statement 2
}
}
Time to run = T(n) = n*5 + 25 + n*n*2 = 2n2 + 5n + 25
(assuming each statement takes 1 "tick" to run)
Big-O Defined
Here, I defined Big-O for T(n) in terms of f(n). In other contexts, you'll see the big-O of any function f(n) defined in terms of g(n). Same thing. Just replace f(n) with g(n) above, and then T(n) with f(n). You end up with:
f(n) = O(g(n)) as n->∞ iff ...(etc)...
which is the definition format you'll often see in other sources.
Big-O Definition Example
- Problem: T(n) = 2n2 + 5n + 25
- From def: T(n) = O(f(n)) iff T(n) <= c * f(n) for some n > n0 and c
- T(n) <= cf(n): 2n2 + 5n + 25 <= c * f(n)
- Select n0 = 5 and c = 4 and f(n) = n2 (or other higher values)
- T(n) <= cf(n): 2n2 + 5n + 25 <= 4 * n2
- For n = n0 = 5: 50 + 25 + 25 <= 4 * 25. (100 <= 100)
- For any higher n (such as 6): 72 + 30 + 25 <= 4 * 36. (127 <= 144)
- And so on...
- Therefore: T(n) = O(n2) for c = 4 and n0=5
(Note we could have selected f(n) = 8n2 + n OR f(n) = n4 here instead. Valid, but convention (especially in this class) is to choose the simplest and lowest-order f(n) that you can.)
Big-O Definition Revisited
- Big-O is upper-bound. (Big-Ω = lower-bound, Big-Θ = always; both upper and lower.)
- Big-O also used for space growth and more generally for other functions.
- asymptotic (that is, usually as n -> ∞)
- Can have more than one variable (ie, m and n).
Informal Approach
- T(n) = 2n2 + 5n + 25 = O(2n2 + 5n + 25) (c = 1, n0 = 0)
- Drop all lower-order terms: T(n) = O(2n2) (by selecting some higher c and n0)
- Drop any coefficients: T(n) = O(n2) (by selecting some even higher c)
- So you can usually just look at the code and see the big-O... but some tricks to this.
Summary: Big-O tells us the algorithm won't grow any faster (get any worse) than a given f(n), no matter how many more processed elements are added.
Big-O Example: Search (p.78)
/** Returns index of target found in x array, or -1 if not found */
public static int search(int[] x, int target) {
for (int i = 0; i < x.length; i++) {
if (x[i] == target) {
return i;
}
}
return -1; //looped through whole array and didn't find target
}
- What corresponds to n (variable input size) here? x.length
- What's best/worst/average case in terms of n?
- Usually with big-O, we care about upper bound of the worst case scenario
- Big-O?
O(n)
Big-O Example: Back and Forth
/** Prints the given array forward and then backwards. */
public static void backAndForth(int[] nums) {
//forward
for (int n : nums) {
System.out.print(n);
System.out.print(" ");
}
System.out.println();
//backwards
for (int i = nums.length - 1; i >= 0; i--) {
System.out.print(nums[i]);
System.out.print(" ");
}
System.out.println();
}
- What corresponds to n (input size) here? nums.length
- Big-O?
O(n) - two loops, but sequential (2n), not nested (n^2)
Big-O Example: Box
/** Prints a box of #s of width * height. */
public static void drawBox(int width, int height) {
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
System.out.print('#');
}
System.out.println();
}
}
- What corresponds to n (variable input size) here?
m = width, n = height
- Big-O? O(m * n) (or O(n^2) if assuming very worst case n == m)
- As a square? m == n, so O(n^2)
Big-O Example: Many boxes
/** Prints 5 boxes of the given size. */
public static void drawBoxes(int size) {
for (int i = 0; i < 5; i++) {
drawBox(size, size);
System.out.println();
}
}
- What corresponds to n here?
size
- Big-O? O(n^2) if including drawBox too (which we probably should)
- What if we consider the contents of drawBox vs if we don't?
If treating drawBox call as a single step, then O(1) (since for loop always executes 5 times and does not vary with n)
Big-O Example: Many boxes variant
/** Prints the given number of boxes. */
public static void drawBoxes(int many) {
for (int i = 0; i < many; i++) {
drawBox(10, 10);
System.out.println();
}
}
- What corresponds to n here?
many
- Big-O?
O(n) (since drawBox is a constant cost now: 10 x 10)
- If we replaced drawBox(10,10) with drawBox(many, many)? O(n^3)
Big-O Example: Find duplicates
/** Returns whether there are any repeated values in the given array. */
public static boolean findDuplicates(int[] nums) {
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] == nums[j]) {
return true;
}
}
}
return false;
}
- What corresponds to n here?
nums.length
- Big-O?
O(n^2) (even though second loop does not start at 0)
Lessons from Examples
- Look for loops, since they act as "multipliers" on their contained statements.
- The number of other statements doesn't really matter ("constants" that will get dropped)
- But not every loop does more work as the size of the input increases!
- (Soon you'll have apply this to recursive calls, so it's not only loops that cause big-O growth.)
- Common big-Os: O(1), O(log n), O(n), O(n log n), O(n2), O(n3), O(2n), O(n!).
Some Big-O Limitations: vs reality
- Constants are dropped (but may be very large in practice)
- 2000n = O(n) vs 2n2 = O(n2)
- n=10: 20,000 vs 200
- n=1000: 2 million vs 2 million
- n=10,000: 20 million vs 200 million
- As n goes to infinity (but usually n is small in practice)
- Usually upper-bound of worst-case (but sometimes worst-case is rare in practice)
Java Review
Arrays and reading from a file
Arrays
int[] nums = {5, 6, 90};
int[] ages = new int[10];
int size = nums.length;
nums[1] = 60;
for (int i = 0; i < nums.length; i++) { //use .length, not 3 here
nums[i]++;
}
for (int n : nums) {
System.out.println(n);
}
Again, review: should already know this.
Arrays of Arrays (Multi-dimensional)
char[][] ticTacToe = new char[3][3];
tictactoe[1][1] = 'X';
char[] middleRow = ticTacToe[1];
System.out.println(middleRow[1]);
int[][] matrix = new int[4][]; //sub-arrays can be different size if created later
for (int i = 0; i < matrix.length; i++) {
matrix[i] = new int[i + 1];
}
matrix[1][1] = 9;
Command Line Arguments
public class Hello {
public static void main(String[] args) {
//...
}
}
- Running on the command line:
java Hello
- JVM starts up, loads Hello.class, and calls the main method
- JVM passes any remaining arguments as elements of args array
- cmd line args == arguments to whole program (what gets done with them depends on program)
Cmd line args examples
java Hello one two
(args: ["one", "two"])
java Hello 3
(args: ["3"])
java Hello
(args: [])
- To use command line args: run your program on the command line!
- (Or figure out how to pass arguments in your IDE.)
Reading from a file
import java.util.Scanner;
import java.io.File;
import java.io.IOException;
public class PrintFile {
/** Prints to the screen the contents of the file named input.txt. */
public static void main(String[] args) {
try {
Scanner file = new Scanner(new File("input.txt"));
while (file.hasNextLine()) {
String line = file.nextLine();
System.out.println(line);
}
file.close();
}catch (IOException e) {
System.out.println("Could not read from file: " + e.getMessage());
}
}
}
Reading from a file details
- Need to catch IOException (or FileNotFoundException), a checked exception
- Need to use hasNextLine() to find out if there is more to read from file.
- See Scanner class in API docs for more:
hasNext(), hasNextInt()
, etc.
- Here, I hard-coded "input.txt" as a literal.
- Careful:
new Scanner(new File(...))
. (Don't forget the new File
part)
- Using Scanner: just one way to do it
Reading from a file: Alternative
import java.io.IOException;
import java.io.FileReader;
import java.io.BufferedReader;
public class Hello {
/** Prints to the screen the contents of the file named input.txt. */
public static void main(String[] args) {
try {
BufferedReader file = new BufferedReader(new FileReader("input.txt"));
String line = file.readLine();
while (line != null) {
System.out.println(line);
line = file.readLine();
}
}catch (IOException e) {
System.out.println("Could not read from file: " + e.getMessage());
}
}
}
Interesting aside
String line = file.readLine(); //initializer
while (line != null) { //condition
System.out.println(line);
line = file.readLine(); //"increment"
}
Those are the components of a for loop...
for (String line = file.readLine(); line != null; line = file.readLine()) {
System.out.println(line);
}
Could have done something similar with a Scanner too (tho not quite as gracefully):
for (String line = null; file.hasNextLine(); line = file.nextLine()) {
if (line != null) {
System.out.println(line);
}
}
- I wouldn't argue that this for loop approach is much clearer than the original while loop
- This example is just a reminder: for loop structures can be used for more than incr/decr counter variables
Summary
- classifying (and thus comparing) algorithms with big-O
- vs comparing implementations in specific contexts using benchmarking
- informally deriving big-O by looking at and reasoning about code
- Java review: arrays, cmd line args, reading text from a file
For next time...
- Quiz 02 (to be posted; due before Wed class)
- A02 to be posted (due in 1 week; uses today's Java stuff)
- Lab x 2
- Monday == Holiday (MLKjr Day)
- As always, deadlines will be tracked course website main page