Quick Overview
Download
Report
Transcript Quick Overview
CIT 594 Quick Overview
Jan 12, 2012
Pointers and References
Machine addresses
:
Computer memory consists of
one long list of addressable
bytes
A pointer is a data item that
contains an address
3FA71CF6
• A reference is a data item that
contains an address
3FA71CF6
• C has pointers, but Java has
references
• So what’s the difference?
3FA71CF2
3FA71CF3
3FA71CF4
3FA71CF5
3FA71CF6
3FA71CF7
3FA71CF8
3FA71CF9
3FA71CFA
3FA71CFB
3FA71CFC
3FA71CFD
3FA71CFE
3FA71CFF
3FA71D00
3FA71D01
:
C (and C++) vs. Java
In C you can dereference (follow) a pointer
In Java you can dereference (follow) a reference
In C you can assign one pointer variable to another
In Java you can assign one reference variable to another
In C you can determine whether one pointer is larger than, equal to, or
smaller than another pointer
In Java you can determine whether one reference is equal to another reference
In C you can create a pointer to anything
In Java you can have references to objects
In C you can do integer arithmetic on pointers
References
An Abstract Data Type (ADT) has a set of values and a
set of operations on those values
Pointers and references have the same set of values (memory
addresses)
Pointers have more defined operations than references
Pointers are more flexible and more general than references
References are safer than pointers (from error or malicious misuse)
References allow automatic garbage collection (pointers don’t)
A (non-abstract) Data Type also has an implementation
The implementations of pointers and references are similar
Java references carry information about the thing referenced;
in C, it’s up to the compiler to figure out what it can
Data structures
Basically, pointers and references are the same thing; they point
to (refer to) something else in memory
A Data Structure is a description of how data is organized in
memory
Many (not all) data structures are built from objects pointing/referring to
one another
Understanding pointers (references) is fundamental to this course
If this course were taught in C or C++ instead of Java, all the
“nuts and bolts” would be the same
This course is in Java, but it’s not about Java
You need to know how to create your own data structures
I will also teach some Java-specific packages
In real life, it’s stupid to redo work that’s already been done for you
A trivial example
We use a lot of references in Java:
class Person {
String name;
Person spouse;
Person (String n) {
name = n;
}
}
…
Person john = new Person("John");
Person mary = new Person("Mary");
john.spouse = mary;
mary.spouse = john;
"John"
john
name
spouse
"Mary"
mary
name
spouse
A more serious example
A binary tree is a data
structure in which every node
(object) has zero, one, or two
children (references to other
nodes)
Arithmetic expressions can be
represented as binary trees
To evaluate an arithmetic
expression:
If it is a leaf, return its value
Otherwise, evaluate its two
subtrees, and perform the
indicated operation
+
/
12
null
null
7
null
null
4
null
null
A binary tree representing the
arithmetic expression 12/4+7
Binary trees in Java
public class BinaryTree {
public Object value; // the information in this node
private BinaryTree leftChild;
private BinaryTree rightChild;
// Constructors and methods...
}
To make binary trees as useful as possible, we make the value in
a node an Object
As implementers of the BinaryTree class, our job is to make sure
that the structure of the binary tree is always valid
We don’t really care what the user puts in the value field
Size of objects
Objects in Java have, generally speaking, a fixed size
The size of all primitives is known
Objects don’t actually “contain” other objects—they just
have references to other objects, and a reference is 4 bytes
So what about Vectors?
A Vector is like an array, but it gets bigger as you put things
into it
A Vector actually has two “sizes”—its capacity, which is
how many references it can hold, and its size, which is how
many references are in it right now
When you exceed the capacity of a Vector, Java creates a
new Vector for you
The magic of Vectors
Suppose you have two
references to a Vector, and
you use one of them to add
elements to the Vector
What happens if Java decides
to replace this Vector with a
bigger one?
v1
v2
It looks like the second reference is a “dangling pointer,” referring
to nothing
This doesn’t happen! Java protects you from this error
But how?
Vector’s secret trick
A reference to a Vector is
actually a reference to a
reference to a Vector
v1
v2
In this way, the “real” reference has to be changed in only one
place, and all the other, indirect references automatically work
It’s clever, but it isn’t magic
Recursion
Jan 12, 2012
Definitions I
A recursive definition is a definition in which the thing
being defined occurs as part of its own definition
Example:
An atom is a name or a number
A list consists of:
An open parenthesis, "("
Zero or more atoms or lists, and
A close parenthesis, ")"
14
Definitions II
Indirect recursion is when a thing is defined in terms of
other things, but those other things are defined in terms
of the first thing
Example: A list is:
An open parenthesis,
Zero or more Symbolic expressions, and
A close parenthesis
A Symbolic expression is an atom or a list
15
Recursive functions...er, methods
The mathematical definition of factorial is:
factorial(n) is
We can define this in Java as:
1, if n <= 1
n * factorial(n-1) otherwise
long factorial(long n) {
if (n <= 1) return 1;
else return n * factorial(n – 1);
}
This is a recursive function because it calls itself
Recursive functions are completely legal in Java
16
Anatomy of a recursion
Base case: does some work
without making a recursive
call
long factorial(long n) {
if (n <= 1) return 1;
else return n * factorial(n – 1);
}
Extra work to convert the
result of the recursive call
into the result of this call
Recursive case: recurs
with a simpler
parameter
17
The four rules
Do the base cases first
Recur only with simpler cases
Don't modify and use non-local variables
You can modify them or use them, just not both
Remember, parameters count as local variables,
but if a parameter is a reference to an object, only the
reference is local—not the referenced object
Don't look down
18
Do the base cases first
Every recursive function must have some things it can do without
recursion
These are the simple, or base, cases
Test for these cases, and do them first
The important part here is testing before you recur; the actual work can be
done in any order
long factorial(long n) {
if (n > 1) return n * factorial(n – 1);
else return 1;
}
However, it’s usually better style to do the base cases first
This is just writing ordinary, nonrecursive code
19
Recur only with a simpler case
If the problem isn't simple enough to be a base case,
break it into two parts:
A simpler problem of the same kind (for example, a smaller
number, or a shorter list)
Extra work not solved by the simpler problem
Combine the results of the recursion and the extra work
into a complete solution
“Simpler” means “more like a base case”
20
Don’t modify and use non-local variables
Consider the following code fragment:
int n = 10;
...
int factorial() {
if (n <= 1) return 1;
else {
n = n – 1;
return (n + 1) * factorial();
}
}
It is very difficult to determine (without trying it)
whether this method works
The problem is keeping track of the value of n at all the
various levels of the recursion
21
Don't look down
When you write or debug a recursive function, think
about this level only
Wherever there is a recursive call, assume that it works
correctly
If you can get this level correct, you will automatically
get all levels correct
You really can't understand more than one level at a
time, so don’t even try
22
We have small heads*
It's hard enough to understand one level of one function
at a time
It's almost impossible to keep track of many levels of
the same function all at once
But you can understand one level of one function at a
time...
...and that's all you need to understand in order to use
recursion well
*According to Edsger Dijkstra
23
Types of Algorithms
Algorithm classification
Algorithms that use a similar problem-solving approach
can be grouped together
We’ll talk about a classification scheme for algorithms
This classification scheme is neither exhaustive nor
disjoint
The purpose is not to be able to classify an algorithm as
one type or another, but to highlight the various ways in
which a problem can be attacked
25
A short list of categories
Algorithm types we will consider include:
Simple recursive algorithms
Backtracking algorithms
Divide and conquer algorithms
Dynamic programming algorithms
Greedy algorithms
Branch and bound algorithms
Brute force algorithms
Randomized algorithms
26
Analysis of Algorithms
Time and space
To analyze an algorithm means:
developing a formula for predicting how fast an
algorithm is, based on the size of the input (time
complexity), and/or
developing a formula for predicting how much memory
an algorithm requires, based on the size of the input
(space complexity)
Usually time is our biggest concern
Most algorithms require a fixed amount of space
28
What does “size of the input” mean?
If we are searching an array, the “size” of the input
could be the size of the array
If we are merging two arrays, the “size” could be the
sum of the two array sizes
If we are computing the nth Fibonacci number, or the
nth factorial, the “size” is n
We choose the “size” to be a parameter that determines
the actual time (or space) required
It is usually obvious what this parameter is
Sometimes we need two or more parameters
29
Characteristic operations
In computing time complexity, one good approach
is to count characteristic operations
What a “characteristic operation” is depends on the
particular problem
If searching, it might be comparing two values
If sorting an array, it might be:
comparing two values
swapping the contents of two array locations
both of the above
Sometimes we just look at how many times the
innermost loop is executed
30
Exact values
It is sometimes possible, in assembly language, to
compute exact time and space requirements
We know exactly how many bytes and how many cycles
each machine instruction takes
For a problem with a known sequence of steps (factorial,
Fibonacci), we can determine how many instructions of
each type are required
However, often the exact sequence of steps cannot
be known in advance
The steps required to sort an array depend on the actual
numbers in the array (which we do not know in advance)
31
Higher-level languages
In a higher-level language (such as Java), we do not
know how long each operation takes
Which is faster, x < 10 or x <= 9 ?
We don’t know exactly what the compiler does with this
The compiler almost certainly optimizes the test anyway
(replacing the slower version with the faster one)
In a higher-level language we cannot do an exact
analysis
Our timing analyses will use major oversimplifications
Nevertheless, we can get some very useful results
32
Average, best, and worst cases
Usually we would like to find the average time to perform an
algorithm
However,
Sometimes the “average” isn’t well defined
Example: Sorting an “average” array
Time typically depends on how out of order the array is
How out of order is the “average” unsorted array?
Sometimes finding the average is too difficult
Often we have to be satisfied with finding the worst (longest)
time required
Sometimes this is even what we want (say, for time-critical operations)
The best (fastest) case is seldom of interest
33
Constant time
Constant time means there is some constant k such
that this operation always takes k nanoseconds
A Java statement takes constant time if:
It does not include a loop
It does not include calling a method whose time is
unknown or is not a constant
If a statement involves a choice (if or switch)
among operations, each of which takes constant
time, we consider the statement to take constant time
This is consistent with worst-case analysis
34
Linear time
We may not be able to predict to the nanosecond how long a
Java program will take, but do know some things about
timing:
for (i = 0, j = 1; i < n; i++) {
j = j * i;
}
This loop takes time k*n + c, for some constants k and c
k : How long it takes to go through the loop once
(the time for j = j * i, plus loop overhead)
n : The number of times through the loop
(we can use this as the “size” of the problem)
c : The time it takes to initialize the loop
The total time k*n + c is linear in n
35
Constant time is (usually)
better than linear time
Suppose we have two algorithms to solve a task:
Which is better?
Algorithm A takes 5000 time units
Algorithm B takes 100*n time units
Clearly, algorithm B is better if our problem size is small,
that is, if n < 50
Algorithm A is better for larger problems, with n > 50
So B is better on small problems that are quick anyway
But A is better for large problems, where it matters more
We usually care most about very large problems
But not always!
36
What about the constants?
An added constant, f(n)+c, becomes less and less
important as n gets larger
A constant multiplier, k*f(n), does not get less
important, but...
Improving k gives a linear speedup (cutting k in half cuts the
time required in half)
Improving k is usually accomplished by careful code
optimization, not by better algorithms
We aren’t that concerned with only linear speedups!
Bottom line: Forget the constants!
37
Simplifying the formulae
Throwing out the constants is one of two things we
do in analysis of algorithms
By throwing out constants, we simplify 12n2 + 35 to
just n2
Our timing formula is a polynomial, and may have
terms of various orders (constant, linear, quadratic,
cubic, etc.)
We usually discard all but the highest-order term
We simplify n2 + 3n + 5 to just n2
38
Big O notation
When we have a polynomial that describes the time
requirements of an algorithm, we simplify it by:
Throwing out all but the highest-order term
Throwing out all the constants
If an algorithm takes 12n3+4n2+8n+35 time, we
simplify this formula to just n3
We say the algorithm requires O(n3) time
We call this Big O notation
(More accurately, it’s Big , but we’ll talk about that later)
39
Big O for subset algorithm
Recall that, if n is the size of the set, and m is the size of
the (possible) subset:
We go through the loop in subset m times, calling
member each time
We go through the loop in member n times
Hence, the actual running time should be k*(m*n) + c,
for some constants k and c
We say that subset takes O(m*n) time
40
Can we justify Big O notation?
Big O notation is a huge simplification; can we
justify it?
It only makes sense for large problem sizes
For sufficiently large problem sizes, the
highest-order term swamps all the rest!
Consider R = x2 + 3x + 5 as x varies:
x=0
x2 = 0 3x = 0 5 = 5 R = 5
x = 10
x2 = 1003x = 30 5 = 5 R = 135
x = 100
x2 = 10000
3x = 300
x = 1000
x2 = 1000000
3x = 3000
x = 10,000 x2 = 108 3x = 3*104
5=5
x = 100,000 x2 = 1010
3x = 3*105
10,000,300,005
5 = 5 R = 10,305
5 = 5 R = 1,003,005
R = 100,030,005
5=5 R=
41
y = x2 + 3x + 5, for x=1..10
42
y = x2 + 3x + 5, for x=1..20
43
Common time complexities
BETTER
O(1)
O(log n)
O(n)
O(n log n)
O(n2)
O(n3)
O(2n)
constant time
log time
linear time
log linear time
quadratic time
cubic time
exponential time
WORSE
44
The End