Considered ArraySimpleStack
public class ArraySimpleStack<E> implements SimpleStack<E> {
private int capacity;
private int size = 0;
private Object[] contents;
...
public void push(E x) {
if (size == capacity) {
increaseCapacity();
}
contents[size] = x;
++size;
}
increaseCapacity()
private void increaseCapacity() {
// create a new array with larger capacity
Object[] bigContents = new Object[2 * capacity];
// copy contents to bigContents
for (int i = 0; i < capacity; ++i) {
bigContents[i] = contents[i];
}
// set contents to refer to the new array
contents = bigContents;
// update this.capacity accordingly
capacity = 2 * capacity;
}
What is the running time of
SimpleStack<Integer> stk = new ArraySimpleStack<Integer>();
for (int i = 1; i <= n; i++) {
stk.push(i);
}
push
SimpleStack<Integer> stk = new ArraySimpleStack<Integer>();
for (int i = 1; i <= n; i++) {
stk.push(i);
}
push
is $O(n)$
push
es have running time $n \cdot O(n) = O(n^2)$push
are performed in $O(1)$ timepush
$n$ times empirical running time looks like $O(n)$, not $O(n^2)$.“cost” of an operation $\approx$ running time of operation
Idea. Don’t look at worst-case cost of each operation individually
instead look at worst-case cost of any sequence of operations
amortized cost is the average cost per operation of any such sequence
Cost of living:
Income:
Question:
Open a bank account!
Each day, can do:
I can afford to live off $100 a day if every day I can pay that day’s expenses and maintain non-negative bank account balance
$\implies$ amortized cost of living is (at most) $100 / day
push(x)
Assume initially size = capacity = 1
for (int i = 1; i <= n; i++) {
stk.push(i);
}
What are costs of operations?
push
public void push(E x) {
if (size == capacity) {
increaseCapacity();
}
contents[size] = x;
++size;
}
increaseCapacity
when size
$= n$ is $C_n$
What is $C_n$ in big O notation?
increaseCapacity()
Code private void increaseCapacity() {
// create a new array with larger capacity
Object[] bigContents = new Object[2 * capacity];
// copy contents to bigContents
for (int i = 0; i < capacity; ++i) {
bigContents[i] = contents[i];
}
// set contents to refer to the new array
contents = bigContents;
// update this.capacity accordingly
capacity = 2 * capacity;
}
push
public void push(E x) {
if (size == capacity) {
increaseCapacity();
}
contents[size] = x;
++size;
}
Suppose current capacity is $n$
Each push
until next resize
Question. How much to add?
At next increaseCapacity()
call, what is account balance?
How to pay $C_n$ for increaseCapacity()
?
If $n/2$ was last resize, each push
until size is $n$:
push
On push when size is $n$
push
increaseCapacity()
In both scenarios
Amortized complexity is a measure of average cost of operations when averaged over any sequence of operations
Moral. Even if individual operations can be expensive, if expensive operations are infrequent, then a data structure may still be efficient.
Question. I read that Shakespeare coined the term indistinguishable. Would it be faster to search OED or Shakespeare to see if Shakespeare used indistinguishable?
What makes searching a dictionary preferable to search a novel for a word?
You’ve considered 2 set implementations
LinkedSimpleUSet
MTFSimpleUSet
Time to access an element is proportional to its position in list
In dictionary example:
A set that stores elements in sorted order
What data structure allows us to “jump” as in searching a dictionary?
Sorted Set ADT
SimpleUSet
add
, remove
, find
findMin
findMax
find(x)
returns the smallest element y
in the set that is no larger than x
(null
if no such y
)Comparable
InterfaceTo indicate that elements of class E
can be compared, E
must implement the Comparable<E>
interface:
public interface Comparable<T> {
int compareTo(T o);
}
This interface is built in to Java!
Interpretation:
x.compareTo(y) < 0
indicates that x
is “smaller than” y
x.compareTo(y) > 0
indicates that x
is “larger than” y
x.compareTo(y) == 0
indicates that x
is semantically equivalent to y
x.compareTo(y) == 0
if and only if x.equals(y)
Integer
public class Integer implements Comparable<Integer> {
private int value;
@Override
int compareTo(Integer x) {
return value - x.value;
}
@Override
boolean equals(Object o) {
if (!(o instanceOf Integer)) return false;
Integer x = (Integer) o;
return (value == x.value);
}
}
public interface SimpleSSet<E extends Comparable<E>> extends SimpleUSet<E> {
@Override /* comments explain how find differs from parent method */
E find(E x);
E findMin();
E findMax();
}
SimpleSSet
Question. What data structure should we use to store elements?
… add(x)
?
… remove(x)
?
… find(x)
?
Find the index where x
would be located
Define int getIndex(x)
method
How to find(x)
?
How to add(x)
?
Once getIndex
is implemented add
, remove
, find
can be made to work
getIndex
will not affect add
/remove
/find
codeSuggestion. First implement/test with simple getIndex
method, then design/test more sophisticated getIndex
implementations
Maxim. Premature optimization is the root of all evil.
ArraySimpleSSet
ImplementationSee code!
getIndex
How to search a sorted array like a dictionary?
Sub-routine search(arr, i, k)
:
arr
a sorted arrayi < k
indicesx
between indices i
and k
in arr
Sub-routine search(arr, i, k)
:
getIndex()
ArraySimpleSSet.java
Compare running times of find
between unordered set implementation (ArraySimpleUSet
) and ArraySimpleSSet
with binary search